@decantr/cli
Advanced tools
| import { | ||
| createProjectHealthReport, | ||
| listWorkspaceAppCandidateDetails | ||
| } from "./chunk-WAGVDMJV.js"; | ||
| // src/commands/workspace.ts | ||
| import { execFileSync } from "child_process"; | ||
| import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs"; | ||
| import { dirname, join, relative, resolve } from "path"; | ||
| import { WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL } from "@decantr/verifier"; | ||
| var BOLD = "\x1B[1m"; | ||
| var DIM = "\x1B[2m"; | ||
| var GREEN = "\x1B[32m"; | ||
| var RED = "\x1B[31m"; | ||
| var YELLOW = "\x1B[33m"; | ||
| var RESET = "\x1B[0m"; | ||
| var WORKSPACE_HEALTH_SCHEMA_URL = WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL; | ||
| var DEFAULT_IGNORES = /* @__PURE__ */ new Set([ | ||
| ".git", | ||
| ".next", | ||
| ".turbo", | ||
| ".vercel", | ||
| "coverage", | ||
| "dist", | ||
| "node_modules", | ||
| "playwright-report" | ||
| ]); | ||
| function workspaceConfigPath(root) { | ||
| return join(root, ".decantr", "workspace.json"); | ||
| } | ||
| function readWorkspaceConfig(root) { | ||
| const path = workspaceConfigPath(root); | ||
| if (!existsSync(path)) return null; | ||
| return JSON.parse(readFileSync(path, "utf-8")); | ||
| } | ||
| function normalizeProjectPath(raw) { | ||
| const normalized = raw.replace(/^\.\/+/, "").replace(/\/+$/, ""); | ||
| if (!normalized || normalized.startsWith("/") || normalized.includes("..") || normalized.includes("\\") || /\s/.test(normalized)) { | ||
| throw new Error(`Invalid workspace project path: ${raw}`); | ||
| } | ||
| return normalized; | ||
| } | ||
| function projectIdFromPath(path) { | ||
| return path.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "project"; | ||
| } | ||
| function discoverProjectPaths(root, config) { | ||
| const ignored = /* @__PURE__ */ new Set([...config?.ignore ?? [], ...DEFAULT_IGNORES]); | ||
| const results = /* @__PURE__ */ new Set(); | ||
| function walk(dir, depth) { | ||
| if (depth > 6) return; | ||
| const rel = relative(root, dir).replace(/\\/g, "/"); | ||
| if (rel && [...ignored].some((entry) => rel === entry || rel.startsWith(`${entry}/`))) return; | ||
| if (existsSync(join(dir, "decantr.essence.json"))) { | ||
| results.add(rel || "."); | ||
| return; | ||
| } | ||
| for (const entry of readdirSync(dir, { withFileTypes: true })) { | ||
| if (!entry.isDirectory() || entry.name.startsWith(".")) continue; | ||
| if (ignored.has(entry.name)) continue; | ||
| walk(join(dir, entry.name), depth + 1); | ||
| } | ||
| } | ||
| walk(root, 0); | ||
| return [...results].sort(); | ||
| } | ||
| function listWorkspaceProjects(root = process.cwd()) { | ||
| const workspaceRoot = resolve(root); | ||
| const config = readWorkspaceConfig(workspaceRoot); | ||
| const byPath = /* @__PURE__ */ new Map(); | ||
| for (const project of config?.projects ?? []) { | ||
| const path = normalizeProjectPath(project.path); | ||
| byPath.set(path, { | ||
| id: project.id ?? projectIdFromPath(path), | ||
| path, | ||
| absolutePath: resolve(workspaceRoot, path), | ||
| owner: project.owner ?? null, | ||
| tags: project.tags ?? [], | ||
| criticality: project.criticality ?? "normal", | ||
| browser: project.browser ?? config?.browser ?? false, | ||
| source: "manifest" | ||
| }); | ||
| } | ||
| for (const path of discoverProjectPaths(workspaceRoot, config)) { | ||
| if (byPath.has(path)) continue; | ||
| byPath.set(path, { | ||
| id: projectIdFromPath(path), | ||
| path, | ||
| absolutePath: resolve(workspaceRoot, path), | ||
| owner: null, | ||
| tags: [], | ||
| criticality: "normal", | ||
| browser: config?.browser ?? false, | ||
| source: "auto" | ||
| }); | ||
| } | ||
| return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path)); | ||
| } | ||
| function listWorkspaceCandidates(root = process.cwd(), projects = listWorkspaceProjects(root)) { | ||
| const attached = new Set(projects.map((project) => project.path)); | ||
| return listWorkspaceAppCandidateDetails(root).map((candidate, index) => ({ | ||
| path: candidate.path, | ||
| attached: attached.has(candidate.path), | ||
| suggestedAdoptCommand: `decantr adopt --project ${candidate.path} --yes`, | ||
| rank: index + 1, | ||
| score: candidate.score, | ||
| category: candidate.category, | ||
| reason: candidate.reason | ||
| })); | ||
| } | ||
| function changedPaths(root, since) { | ||
| try { | ||
| const output = execFileSync("git", ["diff", "--name-only", since, "--"], { | ||
| cwd: root, | ||
| encoding: "utf-8", | ||
| stdio: ["ignore", "pipe", "ignore"] | ||
| }); | ||
| return new Set( | ||
| output.split("\n").map((line) => line.trim()).filter(Boolean) | ||
| ); | ||
| } catch { | ||
| return /* @__PURE__ */ new Set(); | ||
| } | ||
| } | ||
| function projectChanged(project, changed) { | ||
| if (changed.size === 0) return false; | ||
| const prefix = project.path === "." ? "" : `${project.path}/`; | ||
| for (const path of changed) { | ||
| if (project.path === "." || path === project.path || path.startsWith(prefix)) return true; | ||
| } | ||
| return false; | ||
| } | ||
| async function withTimeout(promise, timeoutMs, label) { | ||
| let timeout; | ||
| const timer = new Promise((_, reject) => { | ||
| timeout = setTimeout( | ||
| () => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), | ||
| timeoutMs | ||
| ); | ||
| }); | ||
| try { | ||
| return await Promise.race([promise, timer]); | ||
| } finally { | ||
| if (timeout) clearTimeout(timeout); | ||
| } | ||
| } | ||
| async function mapLimited(items, concurrency, fn) { | ||
| const results = new Array(items.length); | ||
| let next = 0; | ||
| async function worker() { | ||
| while (next < items.length) { | ||
| const index = next++; | ||
| results[index] = await fn(items[index]); | ||
| } | ||
| } | ||
| await Promise.all(Array.from({ length: Math.max(1, concurrency) }, () => worker())); | ||
| return results; | ||
| } | ||
| async function createWorkspaceHealthReport(root = process.cwd(), options = {}) { | ||
| const workspaceRoot = resolve(root); | ||
| const config = readWorkspaceConfig(workspaceRoot); | ||
| const since = options.since ?? "origin/main"; | ||
| const changed = options.changedOnly ? changedPaths(workspaceRoot, since) : /* @__PURE__ */ new Set(); | ||
| const allProjects = listWorkspaceProjects(workspaceRoot); | ||
| const projects = options.changedOnly ? allProjects.filter((project) => projectChanged(project, changed)) : allProjects; | ||
| const concurrency = options.concurrency ?? config?.concurrency ?? 4; | ||
| const timeoutMs = options.timeoutMs ?? config?.timeoutMs ?? 12e4; | ||
| const checked = await mapLimited(projects, concurrency, async (project) => { | ||
| const startedAt = Date.now(); | ||
| try { | ||
| const report = await withTimeout( | ||
| createProjectHealthReport(project.absolutePath, { | ||
| browser: options.browser ?? project.browser | ||
| }), | ||
| timeoutMs, | ||
| project.path | ||
| ); | ||
| return { | ||
| id: project.id, | ||
| path: project.path, | ||
| status: report.status, | ||
| score: report.score, | ||
| errorCount: report.summary.errorCount, | ||
| warnCount: report.summary.warnCount, | ||
| infoCount: report.summary.infoCount, | ||
| findingCount: report.summary.findingCount, | ||
| durationMs: Date.now() - startedAt, | ||
| changed: options.changedOnly ? projectChanged(project, changed) : false, | ||
| source: project.source, | ||
| error: null, | ||
| loopState: report.loop.state, | ||
| loopNextAction: report.loop.nextActions[0] ?? null | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| id: project.id, | ||
| path: project.path, | ||
| status: "failed", | ||
| score: 0, | ||
| errorCount: 1, | ||
| warnCount: 0, | ||
| infoCount: 0, | ||
| findingCount: 1, | ||
| durationMs: Date.now() - startedAt, | ||
| changed: options.changedOnly ? projectChanged(project, changed) : false, | ||
| source: project.source, | ||
| error: error.message, | ||
| loopState: "blocked_missing_context", | ||
| loopNextAction: "Fix the project health failure, then rerun workspace health." | ||
| }; | ||
| } | ||
| }); | ||
| const summary = { | ||
| projectCount: allProjects.length, | ||
| checkedCount: checked.length, | ||
| healthyCount: checked.filter((project) => project.status === "healthy").length, | ||
| warningCount: checked.filter((project) => project.status === "warning").length, | ||
| errorCount: checked.filter((project) => project.status === "error").length, | ||
| failedCount: checked.filter((project) => project.status === "failed").length | ||
| }; | ||
| const blockedCount = checked.filter( | ||
| (project) => project.loopState?.startsWith("blocked") || project.loopState === "human_resolution_required" | ||
| ).length; | ||
| const repairRequiredCount = checked.filter( | ||
| (project) => project.loopState === "repair_required" | ||
| ).length; | ||
| const workspaceLoopState = checked.length === 0 ? "needs_context" : blockedCount > 0 ? "human_resolution_required" : repairRequiredCount > 0 || summary.errorCount > 0 || summary.warningCount > 0 ? "repair_required" : "verified"; | ||
| const workspaceLoopStatus = workspaceLoopState === "human_resolution_required" || workspaceLoopState.startsWith("blocked") ? "blocked" : summary.errorCount > 0 || summary.failedCount > 0 ? "error" : summary.warningCount > 0 ? "warning" : "healthy"; | ||
| return { | ||
| $schema: WORKSPACE_HEALTH_SCHEMA_URL, | ||
| generatedAt: (/* @__PURE__ */ new Date()).toISOString(), | ||
| workspaceRoot, | ||
| changedOnly: options.changedOnly ?? false, | ||
| since: options.changedOnly ? since : null, | ||
| summary, | ||
| loop: { | ||
| state: workspaceLoopState, | ||
| status: workspaceLoopStatus, | ||
| projectCount: checked.length, | ||
| blockedCount, | ||
| repairRequiredCount, | ||
| nextActions: [ | ||
| workspaceLoopState === "verified" ? "Workspace loop verified." : 'Open the highest-risk project, run `decantr task <route> "<intent>"`, repair, then rerun `decantr verify`.' | ||
| ] | ||
| }, | ||
| projects: checked | ||
| }; | ||
| } | ||
| function formatWorkspaceHealthText(report) { | ||
| const lines = [ | ||
| `${BOLD}Decantr Workspace Health${RESET}`, | ||
| "", | ||
| `Projects: ${report.summary.checkedCount}/${report.summary.projectCount}`, | ||
| `Healthy: ${report.summary.healthyCount} | Warnings: ${report.summary.warningCount} | Errors: ${report.summary.errorCount} | Failed: ${report.summary.failedCount}`, | ||
| `Loop: ${report.loop.state} | blocked ${report.loop.blockedCount} | repair ${report.loop.repairRequiredCount}`, | ||
| "" | ||
| ]; | ||
| for (const project of report.projects) { | ||
| const color = project.status === "healthy" ? GREEN : project.status === "warning" ? YELLOW : RED; | ||
| lines.push( | ||
| `${color}${String(project.status).toUpperCase()}${RESET} ${project.path} score ${project.score}/100 findings ${project.findingCount}` | ||
| ); | ||
| if (project.error) lines.push(` ${DIM}${project.error}${RESET}`); | ||
| } | ||
| return `${lines.join("\n")} | ||
| `; | ||
| } | ||
| function formatWorkspaceHealthMarkdown(report) { | ||
| const lines = [ | ||
| "# Decantr Workspace Health", | ||
| "", | ||
| `- Projects checked: **${report.summary.checkedCount}/${report.summary.projectCount}**`, | ||
| `- Healthy: ${report.summary.healthyCount}`, | ||
| `- Warnings: ${report.summary.warningCount}`, | ||
| `- Errors: ${report.summary.errorCount}`, | ||
| `- Failed: ${report.summary.failedCount}`, | ||
| "", | ||
| "| Project | Status | Score | Findings | Source |", | ||
| "| --- | --- | ---: | ---: | --- |" | ||
| ]; | ||
| for (const project of report.projects) { | ||
| lines.push( | ||
| `| \`${project.path}\` | ${project.status} | ${project.score} | ${project.findingCount} | ${project.source} |` | ||
| ); | ||
| } | ||
| return `${lines.join("\n")} | ||
| `; | ||
| } | ||
| function shouldFailWorkspaceHealth(report, failOn = "error") { | ||
| if (failOn === "none") return false; | ||
| if (report.summary.failedCount > 0 || report.summary.errorCount > 0) return true; | ||
| return failOn === "warn" && report.summary.warningCount > 0; | ||
| } | ||
| function parseHealthFailOn(value) { | ||
| if (value === "warn" || value === "none") return value; | ||
| return "error"; | ||
| } | ||
| function parseWorkspaceArgs(args) { | ||
| const subcommand = args[1] === "health" ? "health" : "list"; | ||
| const options = { subcommand }; | ||
| for (let index = 2; index < args.length; index += 1) { | ||
| const arg = args[index]; | ||
| if (arg === "--json") options.json = true; | ||
| else if (arg === "--markdown") options.markdown = true; | ||
| else if (arg === "--ci") options.ci = true; | ||
| else if (arg === "--browser") options.browser = true; | ||
| else if (arg === "--changed") options.changedOnly = true; | ||
| else if (arg === "--since" && args[index + 1]) options.since = args[++index]; | ||
| else if (arg.startsWith("--since=")) options.since = arg.split("=")[1]; | ||
| else if (arg === "--output" && args[index + 1]) options.output = args[++index]; | ||
| else if (arg.startsWith("--output=")) options.output = arg.split("=")[1]; | ||
| else if (arg === "--fail-on" && args[index + 1]) | ||
| options.failOn = parseHealthFailOn(args[++index]); | ||
| else if (arg.startsWith("--fail-on=")) options.failOn = parseHealthFailOn(arg.split("=")[1]); | ||
| else if (arg === "--concurrency" && args[index + 1]) | ||
| options.concurrency = Number(args[++index]); | ||
| else if (arg.startsWith("--concurrency=")) options.concurrency = Number(arg.split("=")[1]); | ||
| else if (arg === "--timeout-ms" && args[index + 1]) options.timeoutMs = Number(args[++index]); | ||
| else if (arg.startsWith("--timeout-ms=")) options.timeoutMs = Number(arg.split("=")[1]); | ||
| } | ||
| return options; | ||
| } | ||
| async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"]) { | ||
| const options = parseWorkspaceArgs(args); | ||
| if (options.subcommand === "list") { | ||
| const projects = listWorkspaceProjects(workspaceRoot); | ||
| const candidates = listWorkspaceCandidates(workspaceRoot, projects); | ||
| const unattachedCandidates = candidates.filter((candidate) => !candidate.attached); | ||
| const payload2 = `${JSON.stringify({ projects, candidates }, null, 2)} | ||
| `; | ||
| if (options.json) { | ||
| process.stdout.write(payload2); | ||
| return; | ||
| } | ||
| console.log(`${BOLD}Decantr workspace projects${RESET}`); | ||
| console.log(""); | ||
| console.log("Attached Decantr projects:"); | ||
| if (projects.length === 0) { | ||
| console.log(` ${DIM}(none yet)${RESET}`); | ||
| } else { | ||
| for (const project of projects) { | ||
| console.log(` ${project.path} ${DIM}${project.source}${RESET}`); | ||
| } | ||
| } | ||
| if (candidates.length > 0) { | ||
| console.log(""); | ||
| console.log("App candidates:"); | ||
| for (const candidate of candidates) { | ||
| const status = candidate.attached ? `${GREEN}attached${RESET}` : `${YELLOW}unattached${RESET}`; | ||
| console.log( | ||
| ` #${candidate.rank} ${candidate.path} ${DIM}${status} \xB7 ${candidate.category} \xB7 score ${candidate.score}${RESET}` | ||
| ); | ||
| } | ||
| } | ||
| if (unattachedCandidates.length > 0) { | ||
| console.log(""); | ||
| console.log(projects.length > 0 ? "Attach another app:" : "Start by attaching one app:"); | ||
| console.log(` ${unattachedCandidates[0].suggestedAdoptCommand}`); | ||
| } | ||
| return; | ||
| } | ||
| const report = await createWorkspaceHealthReport(workspaceRoot, options); | ||
| const payload = options.json ? `${JSON.stringify(report, null, 2)} | ||
| ` : options.markdown ? formatWorkspaceHealthMarkdown(report) : formatWorkspaceHealthText(report); | ||
| if (options.output) { | ||
| mkdirSync(dirname(resolve(workspaceRoot, options.output)), { recursive: true }); | ||
| writeFileSync(resolve(workspaceRoot, options.output), payload, "utf-8"); | ||
| if (!options.ci) | ||
| console.log(`${GREEN}Wrote Decantr workspace health:${RESET} ${options.output}`); | ||
| } else { | ||
| process.stdout.write(payload); | ||
| } | ||
| if (options.ci && shouldFailWorkspaceHealth(report, options.failOn ?? "error")) { | ||
| process.exitCode = 1; | ||
| } | ||
| } | ||
| export { | ||
| listWorkspaceProjects, | ||
| listWorkspaceCandidates, | ||
| createWorkspaceHealthReport, | ||
| formatWorkspaceHealthText, | ||
| formatWorkspaceHealthMarkdown, | ||
| shouldFailWorkspaceHealth, | ||
| parseWorkspaceArgs, | ||
| cmdWorkspace | ||
| }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { | ||
| cmdHealth, | ||
| collectDesignTokenEvidence, | ||
| createProjectEvidenceBundle, | ||
| createProjectHealthReport, | ||
| formatDiagnosticCatalogJson, | ||
| formatDiagnosticCatalogMarkdown, | ||
| formatDiagnosticCatalogText, | ||
| formatProjectHealthJson, | ||
| formatProjectHealthMarkdown, | ||
| formatProjectHealthText, | ||
| parseHealthArgs, | ||
| renderProjectHealthCiWorkflow, | ||
| shouldFailHealth, | ||
| writeProjectHealthCiWorkflow | ||
| } from "./chunk-WAGVDMJV.js"; | ||
| import "./chunk-24JR4ZNG.js"; | ||
| export { | ||
| cmdHealth, | ||
| collectDesignTokenEvidence, | ||
| createProjectEvidenceBundle, | ||
| createProjectHealthReport, | ||
| formatDiagnosticCatalogJson, | ||
| formatDiagnosticCatalogMarkdown, | ||
| formatDiagnosticCatalogText, | ||
| formatProjectHealthJson, | ||
| formatProjectHealthMarkdown, | ||
| formatProjectHealthText, | ||
| parseHealthArgs, | ||
| renderProjectHealthCiWorkflow, | ||
| shouldFailHealth, | ||
| writeProjectHealthCiWorkflow | ||
| }; |
Sorry, the diff of this file is too big to display
| import { | ||
| cmdWorkspace, | ||
| createWorkspaceHealthReport, | ||
| formatWorkspaceHealthMarkdown, | ||
| formatWorkspaceHealthText, | ||
| listWorkspaceCandidates, | ||
| listWorkspaceProjects, | ||
| parseWorkspaceArgs, | ||
| shouldFailWorkspaceHealth | ||
| } from "./chunk-56HUEUO3.js"; | ||
| import "./chunk-WAGVDMJV.js"; | ||
| import "./chunk-24JR4ZNG.js"; | ||
| export { | ||
| cmdWorkspace, | ||
| createWorkspaceHealthReport, | ||
| formatWorkspaceHealthMarkdown, | ||
| formatWorkspaceHealthText, | ||
| listWorkspaceCandidates, | ||
| listWorkspaceProjects, | ||
| parseWorkspaceArgs, | ||
| shouldFailWorkspaceHealth | ||
| }; |
+3
-3
| #!/usr/bin/env node | ||
| import "./chunk-4O74E2PS.js"; | ||
| import "./chunk-TGVBWTDK.js"; | ||
| import "./chunk-SIDKK73N.js"; | ||
| import "./chunk-FIWVRMZN.js"; | ||
| import "./chunk-U62GGKUO.js"; | ||
| import "./chunk-56HUEUO3.js"; | ||
| import "./chunk-WAGVDMJV.js"; | ||
| import "./chunk-24JR4ZNG.js"; |
+3
-3
@@ -1,5 +0,5 @@ | ||
| import "./chunk-4O74E2PS.js"; | ||
| import "./chunk-TGVBWTDK.js"; | ||
| import "./chunk-SIDKK73N.js"; | ||
| import "./chunk-FIWVRMZN.js"; | ||
| import "./chunk-U62GGKUO.js"; | ||
| import "./chunk-56HUEUO3.js"; | ||
| import "./chunk-WAGVDMJV.js"; | ||
| import "./chunk-24JR4ZNG.js"; |
+5
-5
| { | ||
| "name": "@decantr/cli", | ||
| "version": "3.5.5", | ||
| "version": "3.6.0", | ||
| "description": "Decantr CLI - adopt, verify, graph, and govern frontend codebases touched by AI agents", | ||
@@ -52,7 +52,7 @@ "keywords": [ | ||
| "ajv": "^8.20.0", | ||
| "@decantr/core": "3.5.0", | ||
| "@decantr/core": "3.6.0", | ||
| "@decantr/telemetry": "3.4.0", | ||
| "@decantr/essence-spec": "3.4.0", | ||
| "@decantr/registry": "3.4.0", | ||
| "@decantr/telemetry": "3.4.0", | ||
| "@decantr/verifier": "3.5.5" | ||
| "@decantr/verifier": "3.6.0", | ||
| "@decantr/registry": "3.4.0" | ||
| }, | ||
@@ -59,0 +59,0 @@ "scripts": { |
+4
-2
@@ -26,3 +26,3 @@ # @decantr/cli | ||
| Use `decantr studio` after adoption when you want a local Control Room for routes, findings, evidence, authority, and next actions. Use `decantr doctor` when the next step is unclear, `decantr task <route> "<intent>"` before asking an LLM to modify a route, `decantr verify` after the edit, `decantr resolve` when source and contract disagree, and `decantr ci` in required automation. If runtime source and Decantr context disagree, report the drift instead of guessing; in Brownfield the existing source is observed truth, accepted local law/style bridge is project authority where present, Essence V4 is the structural contract, and hosted packs stay advisory until mapped into local law. Use `decantr graph` when you want the Decantr 3 typed Contract graph, typed graph diff summary, manifest, content-addressed snapshot history, and cache-friendly contract capsule written under `.decantr/graph`; the capsule includes a bounded SourceArtifact path index so agents can discover valid file-impact handles without reading the full snapshot, and `--capsule-source-limit <count>` can tune that index for large repos. Add `--route /feed --task "improve loading" --json` when you want the exact task-ranked route-scoped subgraph an agent should inspect before editing, `--node cmp:button --impact --json` when you need the graph-shaped blast radius for a component, token, rule, finding, or source artifact, or `--file src/app/page.tsx --impact --json` when the agent knows the source file it is about to change. Route and impact ranking use deterministic weighted traversal plus local personalized PageRank and task boosts. Use `--snapshot-id <id>` to inspect a replayable history snapshot and `--compare-to <id> --include-diff-ops --json` to compare the selected/current graph against a prior snapshot. Use `decantr codify --from-audit --style-bridge` when you want project-owned UI patterns, optional `behavior_obligations`, local rules, and token/class bridge mappings such as button/card/shell/theme standards to appear in future task context and verification. Once accepted, that local law is the first Hybrid lane: the app still owns source and styling, but Decantr treats accepted local patterns, behavior obligations, rules, and style bridge mappings as project authority. | ||
| In monorepos, app-scoped commands accept `--project <app-path>`. `setup` shows attach guidance before adoption and the day-two loop after adoption. Hosted pack hydration also follows the essence path: `decantr registry compile-packs apps/web/decantr.essence.json --write-context` writes into `apps/web/.decantr/context`. In contract-only/offline Brownfield, deferred hosted packs are optional context unless a present manifest references missing files. | ||
| In monorepos, app-scoped commands accept `--project <app-path>`. `setup` shows attach guidance before adoption and the day-two loop after adoption. Candidate discovery ranks product UI apps ahead of docs, Storybook, API, MCP helper, workbench, and package surfaces; `decantr workspace list --json` includes rank, category, score, and reason metadata so automation can explain why `apps/web`, `apps/remix`, or `apps/dashboard` was suggested first. Hosted pack hydration also follows the essence path: `decantr registry compile-packs apps/web/decantr.essence.json --write-context` writes into `apps/web/.decantr/context`. In contract-only/offline Brownfield, deferred hosted packs are optional context unless a present manifest references missing files. | ||
| Use `decantr init`, `decantr analyze`, `decantr check`, and `decantr health` as advanced primitives when you need direct control over one step. | ||
@@ -107,2 +107,3 @@ | ||
| - supports explicit workflow lanes: greenfield blueprint, greenfield contract-only, brownfield adoption, Hybrid local law, Hybrid style bridge, Hybrid Decantr CSS, and hybrid composition | ||
| - ranks monorepo app candidates with explainable metadata so product UI apps come before docs, Storybook, API, helper packages, and workbench surfaces | ||
| - generates execution-pack context files for AI coding assistants | ||
@@ -114,2 +115,3 @@ - generates typed Contract graph artifacts, replayable snapshot history, graph diffs, manifests, source-file impact context, style-bridge Token nodes, behavior-obligation LocalRule nodes, and `contract-capsule.json` for agent sessions | ||
| - searches the registry and showcase benchmark corpus | ||
| - runs real-world corpus harnesses with timing percentiles, slow-command budgets, root-smoke/app-scoped classification, and stable failure categories | ||
| - filters blueprints through public portfolio sets: `All`, `Featured`, `Certified`, and opt-in `Labs` | ||
@@ -190,3 +192,3 @@ - syncs paginated hosted vocabulary content into a full slug-keyed local cache for offline guards and context generation | ||
| `decantr health` remains the advanced project observability primitive. It composes the existing verifier audit, guard checks, brownfield route drift checks, runtime evidence, component reuse drift, accepted style bridge drift, accepted behavior-obligation checks, typed Contract graph freshness, and execution-pack files into a v2 `ProjectHealthReport` with status, score, route summary, pack summary, findings, stable diagnostic codes, typed repair IDs, evidence tier, authority resolution, loop readiness, and AI-ready remediation prompts. The graph freshness slice emits `GRAPH001` / `regenerate-typed-graph` when an attached app has missing, stale, or non-derivable `.decantr/graph` artifacts. The component reuse slice emits `COMP001` / `import-existing-component` when production source locally redeclares a common primitive that already exists as an exported reusable component, and `COMP010` / `replace-raw-control-with-local-component` when production JSX renders raw controls such as `<button>` while the project already owns a reusable primitive. The behavior-obligation slice emits `A11Y010`, `A11Y011`, `INT010`, `INT011`, `INT012`, `INT013`, and `COMP020` for high-confidence dialog/form regressions such as missing accessible names, missing label associations, missing visible destructive consequence copy, missing cancel affordances, missing submitting guards, implicit form button types, or bypassed project-owned interaction primitives. The style bridge slice emits `TOKEN010` / `replace-arbitrary-style-with-bridge-token` when production JSX, common class helpers, hardcoded inline color styles, or hardcoded visual values in CSS/module stylesheets bypass `.decantr/style-bridge.json` after it has been accepted as project-owned style authority. The baseline slice emits `VISUAL010` / `review-visual-baseline-drift` when `--since-baseline` detects changed screenshot hashes. When `.decantr/graph/graph.snapshot.json` exists, each finding is anchored to the most specific graph node Decantr can resolve, and JSON, markdown, text output, repair prompts, and Evidence Bundles carry that anchor. `decantr graph` also writes content-addressed history snapshots under `.decantr/graph/snapshots/` so repeated graph runs can be replayed across an AI edit sequence. When `.decantr/analysis.json` exists, `decantr graph` links observed routes/pages to implementation source artifacts and links exported reusable component declarations to their source files. When browser evidence writes `.decantr/evidence/visual-manifest.json`, `decantr graph` ingests it as local route/page Evidence nodes without uploading screenshots. When `.decantr/evidence/latest.json` exists, `decantr graph` can also materialize saved findings, evidence strings, graph anchors, repair IDs, and referenced repair/read target files as typed graph nodes and edges. When `.decantr/health-baseline-diff.json` exists, baseline changed files become file-level temporal evidence in the graph. | ||
| `decantr health` remains the advanced project observability primitive. It composes the existing verifier audit, guard checks, brownfield route drift checks, runtime evidence, component reuse drift, accepted style bridge drift, accepted behavior-obligation checks, typed Contract graph freshness, and execution-pack files into a v2 `ProjectHealthReport` with status, score, route summary, pack summary, findings, stable diagnostic codes, typed repair IDs, evidence tier, authority resolution, loop readiness, and AI-ready remediation prompts. The graph freshness slice emits `GRAPH001` / `regenerate-typed-graph` when an attached app has missing, stale, or non-derivable `.decantr/graph` artifacts. The component reuse slice emits `COMP001` / `import-existing-component` when production source locally redeclares a common primitive that already exists as an exported reusable component, and `COMP010` / `replace-raw-control-with-local-component` when production JSX renders generic raw controls such as `<button>` or text-like `<input>` while the project already owns a reusable primitive. Specialized inputs such as file, hidden, checkbox, radio, color, range, and Dropzone `getInputProps()` controls are not treated as generic `Input` drift. The behavior-obligation slice emits `A11Y010`, `A11Y011`, `INT010`, `INT011`, `INT012`, `INT013`, and `COMP020` for high-confidence dialog/form regressions such as missing accessible names, missing label associations, missing visible destructive consequence copy, missing cancel affordances, missing submitting guards, implicit form button types, or bypassed project-owned interaction primitives. The style bridge slice emits `TOKEN010` / `replace-arbitrary-style-with-bridge-token` when production JSX, common class helpers, hardcoded inline color styles, or hardcoded visual values in CSS/module stylesheets bypass `.decantr/style-bridge.json` after it has been accepted as project-owned style authority. The baseline slice emits `VISUAL010` / `review-visual-baseline-drift` when `--since-baseline` detects changed screenshot hashes. When `.decantr/graph/graph.snapshot.json` exists, each finding is anchored to the most specific graph node Decantr can resolve, and JSON, markdown, text output, repair prompts, and Evidence Bundles carry that anchor. `decantr graph` also writes content-addressed history snapshots under `.decantr/graph/snapshots/` so repeated graph runs can be replayed across an AI edit sequence. When `.decantr/analysis.json` exists, `decantr graph` links observed routes/pages to implementation source artifacts and links exported reusable component declarations to their source files. When browser evidence writes `.decantr/evidence/visual-manifest.json`, `decantr graph` ingests it as local route/page Evidence nodes without uploading screenshots. When `.decantr/evidence/latest.json` exists, `decantr graph` can also materialize saved findings, evidence strings, graph anchors, repair IDs, and referenced repair/read target files as typed graph nodes and edges. When `.decantr/health-baseline-diff.json` exists, baseline changed files become file-level temporal evidence in the graph. | ||
@@ -193,0 +195,0 @@ ```bash |
Sorry, the diff of this file is too big to display
| import { | ||
| createProjectHealthReport, | ||
| listWorkspaceAppCandidates | ||
| } from "./chunk-U62GGKUO.js"; | ||
| // src/commands/workspace.ts | ||
| import { execFileSync } from "child_process"; | ||
| import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs"; | ||
| import { dirname, join, relative, resolve } from "path"; | ||
| import { WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL } from "@decantr/verifier"; | ||
| var BOLD = "\x1B[1m"; | ||
| var DIM = "\x1B[2m"; | ||
| var GREEN = "\x1B[32m"; | ||
| var RED = "\x1B[31m"; | ||
| var YELLOW = "\x1B[33m"; | ||
| var RESET = "\x1B[0m"; | ||
| var WORKSPACE_HEALTH_SCHEMA_URL = WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL; | ||
| var DEFAULT_IGNORES = /* @__PURE__ */ new Set([ | ||
| ".git", | ||
| ".next", | ||
| ".turbo", | ||
| ".vercel", | ||
| "coverage", | ||
| "dist", | ||
| "node_modules", | ||
| "playwright-report" | ||
| ]); | ||
| function workspaceConfigPath(root) { | ||
| return join(root, ".decantr", "workspace.json"); | ||
| } | ||
| function readWorkspaceConfig(root) { | ||
| const path = workspaceConfigPath(root); | ||
| if (!existsSync(path)) return null; | ||
| return JSON.parse(readFileSync(path, "utf-8")); | ||
| } | ||
| function normalizeProjectPath(raw) { | ||
| const normalized = raw.replace(/^\.\/+/, "").replace(/\/+$/, ""); | ||
| if (!normalized || normalized.startsWith("/") || normalized.includes("..") || normalized.includes("\\") || /\s/.test(normalized)) { | ||
| throw new Error(`Invalid workspace project path: ${raw}`); | ||
| } | ||
| return normalized; | ||
| } | ||
| function projectIdFromPath(path) { | ||
| return path.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "project"; | ||
| } | ||
| function discoverProjectPaths(root, config) { | ||
| const ignored = /* @__PURE__ */ new Set([...config?.ignore ?? [], ...DEFAULT_IGNORES]); | ||
| const results = /* @__PURE__ */ new Set(); | ||
| function walk(dir, depth) { | ||
| if (depth > 6) return; | ||
| const rel = relative(root, dir).replace(/\\/g, "/"); | ||
| if (rel && [...ignored].some((entry) => rel === entry || rel.startsWith(`${entry}/`))) return; | ||
| if (existsSync(join(dir, "decantr.essence.json"))) { | ||
| results.add(rel || "."); | ||
| return; | ||
| } | ||
| for (const entry of readdirSync(dir, { withFileTypes: true })) { | ||
| if (!entry.isDirectory() || entry.name.startsWith(".")) continue; | ||
| if (ignored.has(entry.name)) continue; | ||
| walk(join(dir, entry.name), depth + 1); | ||
| } | ||
| } | ||
| walk(root, 0); | ||
| return [...results].sort(); | ||
| } | ||
| function listWorkspaceProjects(root = process.cwd()) { | ||
| const workspaceRoot = resolve(root); | ||
| const config = readWorkspaceConfig(workspaceRoot); | ||
| const byPath = /* @__PURE__ */ new Map(); | ||
| for (const project of config?.projects ?? []) { | ||
| const path = normalizeProjectPath(project.path); | ||
| byPath.set(path, { | ||
| id: project.id ?? projectIdFromPath(path), | ||
| path, | ||
| absolutePath: resolve(workspaceRoot, path), | ||
| owner: project.owner ?? null, | ||
| tags: project.tags ?? [], | ||
| criticality: project.criticality ?? "normal", | ||
| browser: project.browser ?? config?.browser ?? false, | ||
| source: "manifest" | ||
| }); | ||
| } | ||
| for (const path of discoverProjectPaths(workspaceRoot, config)) { | ||
| if (byPath.has(path)) continue; | ||
| byPath.set(path, { | ||
| id: projectIdFromPath(path), | ||
| path, | ||
| absolutePath: resolve(workspaceRoot, path), | ||
| owner: null, | ||
| tags: [], | ||
| criticality: "normal", | ||
| browser: config?.browser ?? false, | ||
| source: "auto" | ||
| }); | ||
| } | ||
| return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path)); | ||
| } | ||
| function listWorkspaceCandidates(root = process.cwd(), projects = listWorkspaceProjects(root)) { | ||
| const attached = new Set(projects.map((project) => project.path)); | ||
| return listWorkspaceAppCandidates(root).map((path) => ({ | ||
| path, | ||
| attached: attached.has(path), | ||
| suggestedAdoptCommand: `decantr adopt --project ${path} --yes` | ||
| })); | ||
| } | ||
| function changedPaths(root, since) { | ||
| try { | ||
| const output = execFileSync("git", ["diff", "--name-only", since, "--"], { | ||
| cwd: root, | ||
| encoding: "utf-8", | ||
| stdio: ["ignore", "pipe", "ignore"] | ||
| }); | ||
| return new Set( | ||
| output.split("\n").map((line) => line.trim()).filter(Boolean) | ||
| ); | ||
| } catch { | ||
| return /* @__PURE__ */ new Set(); | ||
| } | ||
| } | ||
| function projectChanged(project, changed) { | ||
| if (changed.size === 0) return false; | ||
| const prefix = project.path === "." ? "" : `${project.path}/`; | ||
| for (const path of changed) { | ||
| if (project.path === "." || path === project.path || path.startsWith(prefix)) return true; | ||
| } | ||
| return false; | ||
| } | ||
| async function withTimeout(promise, timeoutMs, label) { | ||
| let timeout; | ||
| const timer = new Promise((_, reject) => { | ||
| timeout = setTimeout( | ||
| () => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), | ||
| timeoutMs | ||
| ); | ||
| }); | ||
| try { | ||
| return await Promise.race([promise, timer]); | ||
| } finally { | ||
| if (timeout) clearTimeout(timeout); | ||
| } | ||
| } | ||
| async function mapLimited(items, concurrency, fn) { | ||
| const results = new Array(items.length); | ||
| let next = 0; | ||
| async function worker() { | ||
| while (next < items.length) { | ||
| const index = next++; | ||
| results[index] = await fn(items[index]); | ||
| } | ||
| } | ||
| await Promise.all(Array.from({ length: Math.max(1, concurrency) }, () => worker())); | ||
| return results; | ||
| } | ||
| async function createWorkspaceHealthReport(root = process.cwd(), options = {}) { | ||
| const workspaceRoot = resolve(root); | ||
| const config = readWorkspaceConfig(workspaceRoot); | ||
| const since = options.since ?? "origin/main"; | ||
| const changed = options.changedOnly ? changedPaths(workspaceRoot, since) : /* @__PURE__ */ new Set(); | ||
| const allProjects = listWorkspaceProjects(workspaceRoot); | ||
| const projects = options.changedOnly ? allProjects.filter((project) => projectChanged(project, changed)) : allProjects; | ||
| const concurrency = options.concurrency ?? config?.concurrency ?? 4; | ||
| const timeoutMs = options.timeoutMs ?? config?.timeoutMs ?? 12e4; | ||
| const checked = await mapLimited(projects, concurrency, async (project) => { | ||
| const startedAt = Date.now(); | ||
| try { | ||
| const report = await withTimeout( | ||
| createProjectHealthReport(project.absolutePath, { | ||
| browser: options.browser ?? project.browser | ||
| }), | ||
| timeoutMs, | ||
| project.path | ||
| ); | ||
| return { | ||
| id: project.id, | ||
| path: project.path, | ||
| status: report.status, | ||
| score: report.score, | ||
| errorCount: report.summary.errorCount, | ||
| warnCount: report.summary.warnCount, | ||
| infoCount: report.summary.infoCount, | ||
| findingCount: report.summary.findingCount, | ||
| durationMs: Date.now() - startedAt, | ||
| changed: options.changedOnly ? projectChanged(project, changed) : false, | ||
| source: project.source, | ||
| error: null, | ||
| loopState: report.loop.state, | ||
| loopNextAction: report.loop.nextActions[0] ?? null | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| id: project.id, | ||
| path: project.path, | ||
| status: "failed", | ||
| score: 0, | ||
| errorCount: 1, | ||
| warnCount: 0, | ||
| infoCount: 0, | ||
| findingCount: 1, | ||
| durationMs: Date.now() - startedAt, | ||
| changed: options.changedOnly ? projectChanged(project, changed) : false, | ||
| source: project.source, | ||
| error: error.message, | ||
| loopState: "blocked_missing_context", | ||
| loopNextAction: "Fix the project health failure, then rerun workspace health." | ||
| }; | ||
| } | ||
| }); | ||
| const summary = { | ||
| projectCount: allProjects.length, | ||
| checkedCount: checked.length, | ||
| healthyCount: checked.filter((project) => project.status === "healthy").length, | ||
| warningCount: checked.filter((project) => project.status === "warning").length, | ||
| errorCount: checked.filter((project) => project.status === "error").length, | ||
| failedCount: checked.filter((project) => project.status === "failed").length | ||
| }; | ||
| const blockedCount = checked.filter( | ||
| (project) => project.loopState?.startsWith("blocked") || project.loopState === "human_resolution_required" | ||
| ).length; | ||
| const repairRequiredCount = checked.filter( | ||
| (project) => project.loopState === "repair_required" | ||
| ).length; | ||
| const workspaceLoopState = checked.length === 0 ? "needs_context" : blockedCount > 0 ? "human_resolution_required" : repairRequiredCount > 0 || summary.errorCount > 0 || summary.warningCount > 0 ? "repair_required" : "verified"; | ||
| const workspaceLoopStatus = workspaceLoopState === "human_resolution_required" || workspaceLoopState.startsWith("blocked") ? "blocked" : summary.errorCount > 0 || summary.failedCount > 0 ? "error" : summary.warningCount > 0 ? "warning" : "healthy"; | ||
| return { | ||
| $schema: WORKSPACE_HEALTH_SCHEMA_URL, | ||
| generatedAt: (/* @__PURE__ */ new Date()).toISOString(), | ||
| workspaceRoot, | ||
| changedOnly: options.changedOnly ?? false, | ||
| since: options.changedOnly ? since : null, | ||
| summary, | ||
| loop: { | ||
| state: workspaceLoopState, | ||
| status: workspaceLoopStatus, | ||
| projectCount: checked.length, | ||
| blockedCount, | ||
| repairRequiredCount, | ||
| nextActions: [ | ||
| workspaceLoopState === "verified" ? "Workspace loop verified." : 'Open the highest-risk project, run `decantr task <route> "<intent>"`, repair, then rerun `decantr verify`.' | ||
| ] | ||
| }, | ||
| projects: checked | ||
| }; | ||
| } | ||
| function formatWorkspaceHealthText(report) { | ||
| const lines = [ | ||
| `${BOLD}Decantr Workspace Health${RESET}`, | ||
| "", | ||
| `Projects: ${report.summary.checkedCount}/${report.summary.projectCount}`, | ||
| `Healthy: ${report.summary.healthyCount} | Warnings: ${report.summary.warningCount} | Errors: ${report.summary.errorCount} | Failed: ${report.summary.failedCount}`, | ||
| `Loop: ${report.loop.state} | blocked ${report.loop.blockedCount} | repair ${report.loop.repairRequiredCount}`, | ||
| "" | ||
| ]; | ||
| for (const project of report.projects) { | ||
| const color = project.status === "healthy" ? GREEN : project.status === "warning" ? YELLOW : RED; | ||
| lines.push( | ||
| `${color}${String(project.status).toUpperCase()}${RESET} ${project.path} score ${project.score}/100 findings ${project.findingCount}` | ||
| ); | ||
| if (project.error) lines.push(` ${DIM}${project.error}${RESET}`); | ||
| } | ||
| return `${lines.join("\n")} | ||
| `; | ||
| } | ||
| function formatWorkspaceHealthMarkdown(report) { | ||
| const lines = [ | ||
| "# Decantr Workspace Health", | ||
| "", | ||
| `- Projects checked: **${report.summary.checkedCount}/${report.summary.projectCount}**`, | ||
| `- Healthy: ${report.summary.healthyCount}`, | ||
| `- Warnings: ${report.summary.warningCount}`, | ||
| `- Errors: ${report.summary.errorCount}`, | ||
| `- Failed: ${report.summary.failedCount}`, | ||
| "", | ||
| "| Project | Status | Score | Findings | Source |", | ||
| "| --- | --- | ---: | ---: | --- |" | ||
| ]; | ||
| for (const project of report.projects) { | ||
| lines.push( | ||
| `| \`${project.path}\` | ${project.status} | ${project.score} | ${project.findingCount} | ${project.source} |` | ||
| ); | ||
| } | ||
| return `${lines.join("\n")} | ||
| `; | ||
| } | ||
| function shouldFailWorkspaceHealth(report, failOn = "error") { | ||
| if (failOn === "none") return false; | ||
| if (report.summary.failedCount > 0 || report.summary.errorCount > 0) return true; | ||
| return failOn === "warn" && report.summary.warningCount > 0; | ||
| } | ||
| function parseHealthFailOn(value) { | ||
| if (value === "warn" || value === "none") return value; | ||
| return "error"; | ||
| } | ||
| function parseWorkspaceArgs(args) { | ||
| const subcommand = args[1] === "health" ? "health" : "list"; | ||
| const options = { subcommand }; | ||
| for (let index = 2; index < args.length; index += 1) { | ||
| const arg = args[index]; | ||
| if (arg === "--json") options.json = true; | ||
| else if (arg === "--markdown") options.markdown = true; | ||
| else if (arg === "--ci") options.ci = true; | ||
| else if (arg === "--browser") options.browser = true; | ||
| else if (arg === "--changed") options.changedOnly = true; | ||
| else if (arg === "--since" && args[index + 1]) options.since = args[++index]; | ||
| else if (arg.startsWith("--since=")) options.since = arg.split("=")[1]; | ||
| else if (arg === "--output" && args[index + 1]) options.output = args[++index]; | ||
| else if (arg.startsWith("--output=")) options.output = arg.split("=")[1]; | ||
| else if (arg === "--fail-on" && args[index + 1]) | ||
| options.failOn = parseHealthFailOn(args[++index]); | ||
| else if (arg.startsWith("--fail-on=")) options.failOn = parseHealthFailOn(arg.split("=")[1]); | ||
| else if (arg === "--concurrency" && args[index + 1]) | ||
| options.concurrency = Number(args[++index]); | ||
| else if (arg.startsWith("--concurrency=")) options.concurrency = Number(arg.split("=")[1]); | ||
| else if (arg === "--timeout-ms" && args[index + 1]) options.timeoutMs = Number(args[++index]); | ||
| else if (arg.startsWith("--timeout-ms=")) options.timeoutMs = Number(arg.split("=")[1]); | ||
| } | ||
| return options; | ||
| } | ||
| async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"]) { | ||
| const options = parseWorkspaceArgs(args); | ||
| if (options.subcommand === "list") { | ||
| const projects = listWorkspaceProjects(workspaceRoot); | ||
| const candidates = listWorkspaceCandidates(workspaceRoot, projects); | ||
| const unattachedCandidates = candidates.filter((candidate) => !candidate.attached); | ||
| const payload2 = `${JSON.stringify({ projects, candidates }, null, 2)} | ||
| `; | ||
| if (options.json) { | ||
| process.stdout.write(payload2); | ||
| return; | ||
| } | ||
| console.log(`${BOLD}Decantr workspace projects${RESET}`); | ||
| console.log(""); | ||
| console.log("Attached Decantr projects:"); | ||
| if (projects.length === 0) { | ||
| console.log(` ${DIM}(none yet)${RESET}`); | ||
| } else { | ||
| for (const project of projects) { | ||
| console.log(` ${project.path} ${DIM}${project.source}${RESET}`); | ||
| } | ||
| } | ||
| if (candidates.length > 0) { | ||
| console.log(""); | ||
| console.log("App candidates:"); | ||
| for (const candidate of candidates) { | ||
| const status = candidate.attached ? `${GREEN}attached${RESET}` : `${YELLOW}unattached${RESET}`; | ||
| console.log(` ${candidate.path} ${DIM}${status}${RESET}`); | ||
| } | ||
| } | ||
| if (unattachedCandidates.length > 0) { | ||
| console.log(""); | ||
| console.log(projects.length > 0 ? "Attach another app:" : "Start by attaching one app:"); | ||
| console.log(` ${unattachedCandidates[0].suggestedAdoptCommand}`); | ||
| } | ||
| return; | ||
| } | ||
| const report = await createWorkspaceHealthReport(workspaceRoot, options); | ||
| const payload = options.json ? `${JSON.stringify(report, null, 2)} | ||
| ` : options.markdown ? formatWorkspaceHealthMarkdown(report) : formatWorkspaceHealthText(report); | ||
| if (options.output) { | ||
| mkdirSync(dirname(resolve(workspaceRoot, options.output)), { recursive: true }); | ||
| writeFileSync(resolve(workspaceRoot, options.output), payload, "utf-8"); | ||
| if (!options.ci) | ||
| console.log(`${GREEN}Wrote Decantr workspace health:${RESET} ${options.output}`); | ||
| } else { | ||
| process.stdout.write(payload); | ||
| } | ||
| if (options.ci && shouldFailWorkspaceHealth(report, options.failOn ?? "error")) { | ||
| process.exitCode = 1; | ||
| } | ||
| } | ||
| export { | ||
| listWorkspaceProjects, | ||
| listWorkspaceCandidates, | ||
| createWorkspaceHealthReport, | ||
| formatWorkspaceHealthText, | ||
| formatWorkspaceHealthMarkdown, | ||
| shouldFailWorkspaceHealth, | ||
| parseWorkspaceArgs, | ||
| cmdWorkspace | ||
| }; |
Sorry, the diff of this file is too big to display
| import { | ||
| cmdHealth, | ||
| collectDesignTokenEvidence, | ||
| createProjectEvidenceBundle, | ||
| createProjectHealthReport, | ||
| formatDiagnosticCatalogJson, | ||
| formatDiagnosticCatalogMarkdown, | ||
| formatDiagnosticCatalogText, | ||
| formatProjectHealthJson, | ||
| formatProjectHealthMarkdown, | ||
| formatProjectHealthText, | ||
| parseHealthArgs, | ||
| renderProjectHealthCiWorkflow, | ||
| shouldFailHealth, | ||
| writeProjectHealthCiWorkflow | ||
| } from "./chunk-U62GGKUO.js"; | ||
| import "./chunk-24JR4ZNG.js"; | ||
| export { | ||
| cmdHealth, | ||
| collectDesignTokenEvidence, | ||
| createProjectEvidenceBundle, | ||
| createProjectHealthReport, | ||
| formatDiagnosticCatalogJson, | ||
| formatDiagnosticCatalogMarkdown, | ||
| formatDiagnosticCatalogText, | ||
| formatProjectHealthJson, | ||
| formatProjectHealthMarkdown, | ||
| formatProjectHealthText, | ||
| parseHealthArgs, | ||
| renderProjectHealthCiWorkflow, | ||
| shouldFailHealth, | ||
| writeProjectHealthCiWorkflow | ||
| }; |
Sorry, the diff of this file is too big to display
| import { | ||
| cmdWorkspace, | ||
| createWorkspaceHealthReport, | ||
| formatWorkspaceHealthMarkdown, | ||
| formatWorkspaceHealthText, | ||
| listWorkspaceCandidates, | ||
| listWorkspaceProjects, | ||
| parseWorkspaceArgs, | ||
| shouldFailWorkspaceHealth | ||
| } from "./chunk-FIWVRMZN.js"; | ||
| import "./chunk-U62GGKUO.js"; | ||
| import "./chunk-24JR4ZNG.js"; | ||
| export { | ||
| cmdWorkspace, | ||
| createWorkspaceHealthReport, | ||
| formatWorkspaceHealthMarkdown, | ||
| formatWorkspaceHealthText, | ||
| listWorkspaceCandidates, | ||
| listWorkspaceProjects, | ||
| parseWorkspaceArgs, | ||
| shouldFailWorkspaceHealth | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1280737
0.36%30490
0.31%387
0.52%+ Added
+ Added
- Removed
- Removed
Updated
Updated