@decantr/cli 3.4.2 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/bin.js +4 -4
- package/dist/{chunk-PQKTJGYL.js → chunk-EUEQMXN5.js} +85 -8
- package/dist/{chunk-BDA6TWV3.js → chunk-MDQSTAP5.js} +36 -11
- package/dist/{chunk-2GCVVEQC.js → chunk-PM7DKABI.js} +27 -9
- package/dist/{chunk-32WTNJQU.js → chunk-S3ZDXBRM.js} +768 -470
- package/dist/{heal-OKZ5QZN7.js → heal-YXWB6LTI.js} +1 -1
- package/dist/{health-U7O6PCLZ.js → health-5S2L44SX.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{studio-X565XJKV.js → studio-EN347SLA.js} +352 -9
- package/dist/{workspace-NE232D4K.js → workspace-UIKTIWUN.js} +3 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Use `decantr setup` when you are unsure which path applies. It detects whether t
|
|
|
22
22
|
Use `decantr scan` when you want a zero-commit Brownfield preview. It reads local files, detects framework/routes/styling/static-hosting/assistant-rule signals, previews typed Contract graph readiness in memory when a Decantr contract already exists, reports the contract capsule source-handle count and limit, prints a terminal report, and writes no `.decantr` files or report artifacts. Add `--json` when automation needs the `ScanReportV1` payload.
|
|
23
23
|
Use `decantr new` for a greenfield workspace in a fresh directory. With a starter-kit blueprint or archetype it uses the runnable adapter and Decantr CSS; without certified vocabulary content it creates a contract-only workspace unless you explicitly pass `--adoption=decantr-css`.
|
|
24
24
|
Use `decantr adopt` when you already have an app and want Decantr governance without adopting a blueprint. Brownfield attach is proposal-driven: Decantr inventories the app, writes an observed essence proposal, hydrates hosted execution packs when online, and only applies the contract when you explicitly accept or merge it.
|
|
25
|
-
Use `decantr studio` after adoption when you want a local
|
|
25
|
+
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.
|
|
26
26
|
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.
|
|
27
27
|
Use `decantr init`, `decantr analyze`, `decantr check`, and `decantr health` as advanced primitives when you need direct control over one step.
|
|
28
28
|
|
|
@@ -50,6 +50,7 @@ decantr studio
|
|
|
50
50
|
decantr doctor
|
|
51
51
|
decantr ci --fail-on error
|
|
52
52
|
decantr ci init
|
|
53
|
+
decantr resolve
|
|
53
54
|
decantr codify --from-audit --style-bridge
|
|
54
55
|
decantr codify --map-pattern hero
|
|
55
56
|
decantr codify --accept
|
|
@@ -130,6 +131,7 @@ decantr adopt --yes
|
|
|
130
131
|
decantr adopt --project apps/web --yes
|
|
131
132
|
decantr doctor
|
|
132
133
|
decantr doctor --project apps/web
|
|
134
|
+
decantr resolve --project apps/web
|
|
133
135
|
decantr codify --from-audit --style-bridge
|
|
134
136
|
decantr codify --map-pattern hero --project apps/web
|
|
135
137
|
decantr codify --accept
|
|
@@ -182,7 +184,7 @@ decantr showcase verification --json
|
|
|
182
184
|
|
|
183
185
|
`decantr ci` is the blessed non-mutating automation gate. It runs the Project Health surface with adoption-mode-aware local law checks and emits a schema-backed CI report. `decantr ci init` writes root GitHub workflows or portable generic snippets using the detected package manager and pinned local CLI command instead of `@latest`; if the root manifest has not pinned Decantr yet, it prints the exact install command first.
|
|
184
186
|
|
|
185
|
-
`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 `ProjectHealthReport` with
|
|
187
|
+
`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.
|
|
186
188
|
|
|
187
189
|
```bash
|
|
188
190
|
decantr verify
|
|
@@ -230,7 +232,7 @@ decantr verify --workspace --changed --since origin/main
|
|
|
230
232
|
|
|
231
233
|
In observed Brownfield projects, common section shorthands are accepted for page and feature additions when they resolve unambiguously. For example, `decantr add page app/settings --route /settings --project apps/web` and `decantr add feature saved-recipes --section app --project apps/web` resolve `app` to the single primary section, such as `observed-primary`, so docs and LLM prompts do not have to guess generated section IDs first.
|
|
232
234
|
|
|
233
|
-
`decantr studio` starts a local-only
|
|
235
|
+
`decantr studio` starts a local-only Control Room powered by the same v2 report. It uses Node built-ins only and serves `GET /`, `GET /api/health`, `GET /api/control-room`, `GET /api/resolve`, `GET /api/evidence`, `GET /api/graph-impact`, `GET /api/task-preview`, `GET /api/proof`, and `POST /api/refresh`.
|
|
234
236
|
|
|
235
237
|
```bash
|
|
236
238
|
decantr studio
|
|
@@ -239,7 +241,7 @@ decantr studio --report decantr-health.json
|
|
|
239
241
|
decantr studio --workspace
|
|
240
242
|
```
|
|
241
243
|
|
|
242
|
-
Studio is for local triage, not Decantr admin telemetry. The
|
|
244
|
+
Studio is for local triage, not Decantr admin telemetry. The Control Room keeps the first decision simple: inspect loop state, next action, authority lane, blocking findings, evidence tier, graph impact, and copyable commands. The views cover Control Room, Routes, Graph Impact, Authority Resolver, Evidence, Repairs, and CI/Benchmarks without uploading source code, prompts, file paths, or project data.
|
|
243
245
|
|
|
244
246
|
Workspace Studio uses `decantr workspace health` behind `GET /api/workspace` and `POST /api/workspace/refresh` so large monorepos can triage many Decantr projects from one local dashboard.
|
|
245
247
|
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-S3ZDXBRM.js";
|
|
3
3
|
import "./chunk-SIDKK73N.js";
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
import "./chunk-MDQSTAP5.js";
|
|
5
|
+
import "./chunk-EUEQMXN5.js";
|
|
6
|
+
import "./chunk-PM7DKABI.js";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
sendProjectHealthCiFailedTelemetry,
|
|
4
4
|
sendProjectHealthPromptTelemetry,
|
|
5
5
|
sendProjectHealthReportTelemetry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-PM7DKABI.js";
|
|
7
7
|
|
|
8
8
|
// src/commands/health.ts
|
|
9
9
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -16,10 +16,14 @@ import {
|
|
|
16
16
|
anchorFindingsToGraph,
|
|
17
17
|
auditProject,
|
|
18
18
|
buildProjectHealthRepairPlan,
|
|
19
|
+
createAuthorityResolution,
|
|
19
20
|
createContractAssertions,
|
|
20
21
|
createEvidenceBundle,
|
|
22
|
+
createEvidenceTier,
|
|
23
|
+
createLoopReadiness,
|
|
21
24
|
deriveVerificationDiagnostic,
|
|
22
|
-
KNOWN_VERIFICATION_DIAGNOSTICS
|
|
25
|
+
KNOWN_VERIFICATION_DIAGNOSTICS,
|
|
26
|
+
PROJECT_HEALTH_REPORT_V2_SCHEMA_URL
|
|
23
27
|
} from "@decantr/verifier";
|
|
24
28
|
|
|
25
29
|
// src/workspace.ts
|
|
@@ -2729,18 +2733,20 @@ function buildGraphArtifacts(projectRoot, options = {}) {
|
|
|
2729
2733
|
const sources = sourceArtifacts(projectRoot, componentReuseAudit);
|
|
2730
2734
|
const combinedSourceHash = sourceHash(sources);
|
|
2731
2735
|
const previousSnapshot = readJsonFile3(paths.snapshot);
|
|
2732
|
-
const
|
|
2736
|
+
const previousDiff = readJsonFile3(paths.diff);
|
|
2737
|
+
const sourceUnchanged = previousSnapshot?.source_hash === combinedSourceHash;
|
|
2738
|
+
const createdAt = sourceUnchanged ? previousSnapshot.created_at : (/* @__PURE__ */ new Date()).toISOString();
|
|
2733
2739
|
const snapshotId = `graph:${combinedSourceHash.replace(/^sha256:/, "").slice(0, 12)}`;
|
|
2734
2740
|
paths = withSnapshotHistoryPath(paths, snapshotId);
|
|
2735
2741
|
const baseSnapshot = buildGraphSnapshotFromEssence(essence, {
|
|
2736
2742
|
snapshotId,
|
|
2737
|
-
parentId:
|
|
2743
|
+
parentId: sourceUnchanged ? previousSnapshot.parent_id : previousSnapshot ? previousSnapshot.id : void 0,
|
|
2738
2744
|
sourceHash: combinedSourceHash,
|
|
2739
2745
|
createdAt,
|
|
2740
2746
|
sourceArtifact: sources[0]
|
|
2741
2747
|
});
|
|
2742
2748
|
const snapshot = augmentProjectGraph(baseSnapshot, projectRoot, sources, componentReuseAudit);
|
|
2743
|
-
const diff = previousSnapshot ? diffGraphSnapshots(previousSnapshot, snapshot) : {
|
|
2749
|
+
const diff = sourceUnchanged && previousDiff?.to === snapshot.id ? previousDiff : previousSnapshot ? diffGraphSnapshots(previousSnapshot, snapshot) : {
|
|
2744
2750
|
$schema: GRAPH_DIFF_SCHEMA_URL,
|
|
2745
2751
|
id: `diff:${snapshot.id}:${snapshot.id}`,
|
|
2746
2752
|
from: snapshot.id,
|
|
@@ -3210,7 +3216,7 @@ var RED2 = "\x1B[31m";
|
|
|
3210
3216
|
var GREEN2 = "\x1B[32m";
|
|
3211
3217
|
var CYAN = "\x1B[36m";
|
|
3212
3218
|
var YELLOW = "\x1B[33m";
|
|
3213
|
-
var PROJECT_HEALTH_SCHEMA_URL =
|
|
3219
|
+
var PROJECT_HEALTH_SCHEMA_URL = PROJECT_HEALTH_REPORT_V2_SCHEMA_URL;
|
|
3214
3220
|
var DEFAULT_HEALTH_CI_WORKFLOW_PATH = ".github/workflows/decantr-health.yml";
|
|
3215
3221
|
var DEFAULT_HEALTH_CI_REPORT_PATH = "decantr-health.md";
|
|
3216
3222
|
var DEFAULT_HEALTH_CI_JSON_PATH = "decantr-health.json";
|
|
@@ -4860,7 +4866,7 @@ async function createProjectHealthReport(projectRoot = process.cwd(), options =
|
|
|
4860
4866
|
}));
|
|
4861
4867
|
const finalCounts = countFindings(repairPlanFindings);
|
|
4862
4868
|
const commandContext = commandContextForProject(projectRoot);
|
|
4863
|
-
|
|
4869
|
+
const baseReport = {
|
|
4864
4870
|
$schema: PROJECT_HEALTH_SCHEMA_URL,
|
|
4865
4871
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4866
4872
|
projectRoot,
|
|
@@ -4901,6 +4907,33 @@ async function createProjectHealthReport(projectRoot = process.cwd(), options =
|
|
|
4901
4907
|
},
|
|
4902
4908
|
findings: repairPlanFindings
|
|
4903
4909
|
};
|
|
4910
|
+
const evidenceTier = createEvidenceTier(baseReport, {
|
|
4911
|
+
runtimeProbeCount: browserVerification ? Math.max(1, browserVerification.evidence.screenshots.length) : void 0,
|
|
4912
|
+
visualArtifactCount: browserVerification?.evidence.screenshots.length ?? 0
|
|
4913
|
+
});
|
|
4914
|
+
const authority = createAuthorityResolution(baseReport);
|
|
4915
|
+
const loop = createLoopReadiness(baseReport, authority, evidenceTier);
|
|
4916
|
+
return {
|
|
4917
|
+
...baseReport,
|
|
4918
|
+
evidenceTier,
|
|
4919
|
+
authority,
|
|
4920
|
+
loop,
|
|
4921
|
+
findings: repairPlanFindings.map((finding) => {
|
|
4922
|
+
const conflict = authority.conflicts.find((entry) => entry.id === finding.id);
|
|
4923
|
+
return {
|
|
4924
|
+
...finding,
|
|
4925
|
+
evidenceTier,
|
|
4926
|
+
authorityLane: conflict?.lane ?? authority.activeLane,
|
|
4927
|
+
resolutionActions: conflict?.recommendedActions,
|
|
4928
|
+
privacy: {
|
|
4929
|
+
sourceIncluded: false,
|
|
4930
|
+
redacted: true,
|
|
4931
|
+
localOnly: true
|
|
4932
|
+
},
|
|
4933
|
+
loopVerdict: loop.state
|
|
4934
|
+
};
|
|
4935
|
+
})
|
|
4936
|
+
};
|
|
4904
4937
|
}
|
|
4905
4938
|
function colorForStatus(status) {
|
|
4906
4939
|
if (status === "healthy") return GREEN2;
|
|
@@ -4954,6 +4987,11 @@ function formatProjectHealthText(report) {
|
|
|
4954
4987
|
` Packs: manifest ${report.packs.manifestPresent ? "present" : "missing"} | review ${report.packs.reviewPackPresent ? "present" : "missing"} | pages ${report.packs.pagePackCount}`,
|
|
4955
4988
|
` Graph: ${report.graph.current === null ? "not attached" : report.graph.current ? "current" : "stale"} | capsule ${report.graph.capsulePresent ? "present" : "missing"} | sources ${report.graph.sourceArtifactCount}`,
|
|
4956
4989
|
"",
|
|
4990
|
+
`${BOLD}Control loop:${RESET2}`,
|
|
4991
|
+
` State: ${report.loop.state} | evidence ${report.evidenceTier.confidence.level} (${report.evidenceTier.confidence.score})`,
|
|
4992
|
+
` Authority: ${report.authority.activeLane} \u2014 ${report.authority.summary}`,
|
|
4993
|
+
` Next: ${report.loop.nextActions[0] ?? report.loop.verifyCommand}`,
|
|
4994
|
+
"",
|
|
4957
4995
|
`${BOLD}Findings:${RESET2}`
|
|
4958
4996
|
];
|
|
4959
4997
|
if (report.findings.length === 0) {
|
|
@@ -4986,6 +5024,7 @@ function formatProjectHealthText(report) {
|
|
|
4986
5024
|
}
|
|
4987
5025
|
lines.push("");
|
|
4988
5026
|
lines.push(`${BOLD}CI:${RESET2} ${report.ci.recommendedCommand}`);
|
|
5027
|
+
lines.push(`${BOLD}Loop verify:${RESET2} ${report.loop.verifyCommand}`);
|
|
4989
5028
|
return `${lines.join("\n")}
|
|
4990
5029
|
`;
|
|
4991
5030
|
}
|
|
@@ -5001,6 +5040,9 @@ function formatProjectHealthMarkdown(report) {
|
|
|
5001
5040
|
`- Runtime audit: ${report.summary.runtimeAuditChecked ? report.summary.runtimePassed ? "passed" : "failed" : "not checked"}`,
|
|
5002
5041
|
`- Packs: manifest ${report.packs.manifestPresent ? "present" : "missing"}, review ${report.packs.reviewPackPresent ? "present" : "missing"}`,
|
|
5003
5042
|
`- Graph: ${report.graph.current === null ? "not attached" : report.graph.current ? "current" : "stale"}, capsule ${report.graph.capsulePresent ? "present" : "missing"}, sources ${report.graph.sourceArtifactCount}`,
|
|
5043
|
+
`- Loop: **${report.loop.state}** (${report.loop.status})`,
|
|
5044
|
+
`- Evidence tier: **${report.evidenceTier.stage}** / ${report.evidenceTier.confidence.level}`,
|
|
5045
|
+
`- Authority: **${report.authority.activeLane}**`,
|
|
5004
5046
|
"",
|
|
5005
5047
|
"## Findings",
|
|
5006
5048
|
""
|
|
@@ -5084,6 +5126,12 @@ function formatDiagnosticCatalogText() {
|
|
|
5084
5126
|
async function createProjectEvidenceBundle(projectRoot, report, options = {}) {
|
|
5085
5127
|
const audit = await auditProject(projectRoot);
|
|
5086
5128
|
const assertions = createContractAssertions(projectRoot, audit);
|
|
5129
|
+
const visualManifestPath = join5(projectRoot, ".decantr", "evidence", "visual-manifest.json");
|
|
5130
|
+
const browserEvidence = await browserEvidenceFromOptions(
|
|
5131
|
+
projectRoot,
|
|
5132
|
+
options,
|
|
5133
|
+
report.routes.declared
|
|
5134
|
+
);
|
|
5087
5135
|
return createEvidenceBundle({
|
|
5088
5136
|
projectRoot,
|
|
5089
5137
|
report,
|
|
@@ -5091,7 +5139,36 @@ async function createProjectEvidenceBundle(projectRoot, report, options = {}) {
|
|
|
5091
5139
|
assertions,
|
|
5092
5140
|
workspaceConfigPath: existsSync5(join5(projectRoot, ".decantr", "workspace.json")) ? join5(projectRoot, ".decantr", "workspace.json") : null,
|
|
5093
5141
|
designTokensPath: resolveOptionalPath(projectRoot, options.designTokensPath) ?? null,
|
|
5094
|
-
|
|
5142
|
+
visualManifestPath: existsSync5(visualManifestPath) ? visualManifestPath : null,
|
|
5143
|
+
artifacts: [
|
|
5144
|
+
{
|
|
5145
|
+
id: "artifact:evidence-bundle",
|
|
5146
|
+
kind: "evidence-bundle",
|
|
5147
|
+
path: ".decantr/evidence/evidence-bundle.json",
|
|
5148
|
+
hash: null,
|
|
5149
|
+
localOnly: true,
|
|
5150
|
+
redacted: true
|
|
5151
|
+
},
|
|
5152
|
+
...existsSync5(visualManifestPath) ? [
|
|
5153
|
+
{
|
|
5154
|
+
id: "artifact:visual-manifest",
|
|
5155
|
+
kind: "visual-manifest",
|
|
5156
|
+
path: ".decantr/evidence/visual-manifest.json",
|
|
5157
|
+
hash: null,
|
|
5158
|
+
localOnly: true,
|
|
5159
|
+
redacted: true
|
|
5160
|
+
}
|
|
5161
|
+
] : [],
|
|
5162
|
+
...(browserEvidence?.screenshots ?? []).map((screenshot, index) => ({
|
|
5163
|
+
id: `artifact:screenshot:${index + 1}`,
|
|
5164
|
+
kind: "screenshot",
|
|
5165
|
+
path: screenshot,
|
|
5166
|
+
hash: null,
|
|
5167
|
+
localOnly: true,
|
|
5168
|
+
redacted: false
|
|
5169
|
+
}))
|
|
5170
|
+
],
|
|
5171
|
+
browser: browserEvidence,
|
|
5095
5172
|
designTokens: collectDesignTokenEvidence(projectRoot, options.designTokensPath)
|
|
5096
5173
|
});
|
|
5097
5174
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createProjectHealthReport,
|
|
3
3
|
listWorkspaceAppCandidates
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EUEQMXN5.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/workspace.ts
|
|
7
7
|
import { execFileSync } from "child_process";
|
|
8
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
9
|
import { dirname, join, relative, resolve } from "path";
|
|
10
|
+
import { WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL } from "@decantr/verifier";
|
|
10
11
|
var BOLD = "\x1B[1m";
|
|
11
12
|
var DIM = "\x1B[2m";
|
|
12
13
|
var GREEN = "\x1B[32m";
|
|
13
14
|
var RED = "\x1B[31m";
|
|
14
15
|
var YELLOW = "\x1B[33m";
|
|
15
16
|
var RESET = "\x1B[0m";
|
|
16
|
-
var WORKSPACE_HEALTH_SCHEMA_URL =
|
|
17
|
+
var WORKSPACE_HEALTH_SCHEMA_URL = WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL;
|
|
17
18
|
var DEFAULT_IGNORES = /* @__PURE__ */ new Set([
|
|
18
19
|
".git",
|
|
19
20
|
".next",
|
|
@@ -181,7 +182,9 @@ async function createWorkspaceHealthReport(root = process.cwd(), options = {}) {
|
|
|
181
182
|
durationMs: Date.now() - startedAt,
|
|
182
183
|
changed: options.changedOnly ? projectChanged(project, changed) : false,
|
|
183
184
|
source: project.source,
|
|
184
|
-
error: null
|
|
185
|
+
error: null,
|
|
186
|
+
loopState: report.loop.state,
|
|
187
|
+
loopNextAction: report.loop.nextActions[0] ?? null
|
|
185
188
|
};
|
|
186
189
|
} catch (error) {
|
|
187
190
|
return {
|
|
@@ -196,23 +199,44 @@ async function createWorkspaceHealthReport(root = process.cwd(), options = {}) {
|
|
|
196
199
|
durationMs: Date.now() - startedAt,
|
|
197
200
|
changed: options.changedOnly ? projectChanged(project, changed) : false,
|
|
198
201
|
source: project.source,
|
|
199
|
-
error: error.message
|
|
202
|
+
error: error.message,
|
|
203
|
+
loopState: "blocked_missing_context",
|
|
204
|
+
loopNextAction: "Fix the project health failure, then rerun workspace health."
|
|
200
205
|
};
|
|
201
206
|
}
|
|
202
207
|
});
|
|
208
|
+
const summary = {
|
|
209
|
+
projectCount: allProjects.length,
|
|
210
|
+
checkedCount: checked.length,
|
|
211
|
+
healthyCount: checked.filter((project) => project.status === "healthy").length,
|
|
212
|
+
warningCount: checked.filter((project) => project.status === "warning").length,
|
|
213
|
+
errorCount: checked.filter((project) => project.status === "error").length,
|
|
214
|
+
failedCount: checked.filter((project) => project.status === "failed").length
|
|
215
|
+
};
|
|
216
|
+
const blockedCount = checked.filter(
|
|
217
|
+
(project) => project.loopState?.startsWith("blocked") || project.loopState === "human_resolution_required"
|
|
218
|
+
).length;
|
|
219
|
+
const repairRequiredCount = checked.filter(
|
|
220
|
+
(project) => project.loopState === "repair_required"
|
|
221
|
+
).length;
|
|
222
|
+
const workspaceLoopState = checked.length === 0 ? "needs_context" : blockedCount > 0 ? "human_resolution_required" : repairRequiredCount > 0 || summary.errorCount > 0 || summary.warningCount > 0 ? "repair_required" : "verified";
|
|
223
|
+
const workspaceLoopStatus = workspaceLoopState === "human_resolution_required" || workspaceLoopState.startsWith("blocked") ? "blocked" : summary.errorCount > 0 || summary.failedCount > 0 ? "error" : summary.warningCount > 0 ? "warning" : "healthy";
|
|
203
224
|
return {
|
|
204
225
|
$schema: WORKSPACE_HEALTH_SCHEMA_URL,
|
|
205
226
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
206
227
|
workspaceRoot,
|
|
207
228
|
changedOnly: options.changedOnly ?? false,
|
|
208
229
|
since: options.changedOnly ? since : null,
|
|
209
|
-
summary
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
summary,
|
|
231
|
+
loop: {
|
|
232
|
+
state: workspaceLoopState,
|
|
233
|
+
status: workspaceLoopStatus,
|
|
234
|
+
projectCount: checked.length,
|
|
235
|
+
blockedCount,
|
|
236
|
+
repairRequiredCount,
|
|
237
|
+
nextActions: [
|
|
238
|
+
workspaceLoopState === "verified" ? "Workspace loop verified." : 'Open the highest-risk project, run `decantr task <route> "<intent>"`, repair, then rerun `decantr verify`.'
|
|
239
|
+
]
|
|
216
240
|
},
|
|
217
241
|
projects: checked
|
|
218
242
|
};
|
|
@@ -223,6 +247,7 @@ function formatWorkspaceHealthText(report) {
|
|
|
223
247
|
"",
|
|
224
248
|
`Projects: ${report.summary.checkedCount}/${report.summary.projectCount}`,
|
|
225
249
|
`Healthy: ${report.summary.healthyCount} | Warnings: ${report.summary.warningCount} | Errors: ${report.summary.errorCount} | Failed: ${report.summary.failedCount}`,
|
|
250
|
+
`Loop: ${report.loop.state} | blocked ${report.loop.blockedCount} | repair ${report.loop.repairRequiredCount}`,
|
|
226
251
|
""
|
|
227
252
|
];
|
|
228
253
|
for (const project of report.projects) {
|
|
@@ -562,6 +562,11 @@ function detectPathnameBranchRoutes(content) {
|
|
|
562
562
|
if (strongMatches > 0 || hasPathnameSignal) {
|
|
563
563
|
collectRouteLiterals(/\b(?:href|to)\s*=\s*["'`](\/[^"'`]+)["'`]/g, content, routes);
|
|
564
564
|
}
|
|
565
|
+
if (hasPathnameSignal && (/\?\s*["'`]\/["'`]\s*:/.test(content) || /\|\|\s*["'`]\/["'`]/.test(content) || /\b(?:defaultRoute|defaultPath|fallbackRoute|fallbackPath)\s*[:=]\s*["'`]\/["'`]/.test(
|
|
566
|
+
content
|
|
567
|
+
))) {
|
|
568
|
+
routes.add("/");
|
|
569
|
+
}
|
|
565
570
|
return [...routes];
|
|
566
571
|
}
|
|
567
572
|
function hasReactRouterDependency(projectRoot) {
|
|
@@ -1441,6 +1446,18 @@ function loadJsonEntries(dir) {
|
|
|
1441
1446
|
return [];
|
|
1442
1447
|
}
|
|
1443
1448
|
}
|
|
1449
|
+
function readJson(path) {
|
|
1450
|
+
if (!existsSync7(path)) return null;
|
|
1451
|
+
try {
|
|
1452
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
1453
|
+
} catch {
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function addPattern(registry, id, source, data = null) {
|
|
1458
|
+
if (typeof id !== "string" || !id.trim() || registry.has(id)) return;
|
|
1459
|
+
registry.set(id, data ?? { id, source });
|
|
1460
|
+
}
|
|
1444
1461
|
function buildGuardRegistryContext(projectRoot = process.cwd()) {
|
|
1445
1462
|
const themeRegistry = /* @__PURE__ */ new Map();
|
|
1446
1463
|
const patternRegistry = /* @__PURE__ */ new Map();
|
|
@@ -1461,21 +1478,22 @@ function buildGuardRegistryContext(projectRoot = process.cwd()) {
|
|
|
1461
1478
|
}
|
|
1462
1479
|
}
|
|
1463
1480
|
for (const data of loadJsonEntries(join7(cacheDir, "@official", "patterns"))) {
|
|
1464
|
-
|
|
1465
|
-
patternRegistry.set(data.id, data);
|
|
1466
|
-
}
|
|
1481
|
+
addPattern(patternRegistry, data.id, "cache", data);
|
|
1467
1482
|
}
|
|
1468
1483
|
for (const entry of loadBundledContentList("patterns")) {
|
|
1469
1484
|
const data = entry.data;
|
|
1470
1485
|
const id = typeof data.id === "string" ? data.id : entry.id;
|
|
1471
|
-
|
|
1472
|
-
patternRegistry.set(id, data);
|
|
1473
|
-
}
|
|
1486
|
+
addPattern(patternRegistry, id, "bundled", data);
|
|
1474
1487
|
}
|
|
1475
1488
|
for (const data of loadJsonEntries(join7(customDir, "patterns"))) {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1489
|
+
addPattern(patternRegistry, data.id, "custom", data);
|
|
1490
|
+
}
|
|
1491
|
+
const localPatterns = readJson(join7(projectRoot, ".decantr", "local-patterns.json"));
|
|
1492
|
+
const patterns = Array.isArray(localPatterns?.patterns) ? localPatterns.patterns : [];
|
|
1493
|
+
for (const pattern of patterns) {
|
|
1494
|
+
if (!pattern || typeof pattern !== "object") continue;
|
|
1495
|
+
const data = pattern;
|
|
1496
|
+
addPattern(patternRegistry, data.id, "local-law", data);
|
|
1479
1497
|
}
|
|
1480
1498
|
return { themeRegistry, patternRegistry };
|
|
1481
1499
|
}
|