@decantr/cli 2.9.2 → 2.9.3
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 +7 -2
- package/dist/bin.js +3 -3
- package/dist/{chunk-R57DMFLF.js → chunk-ARR3EPS2.js} +1 -1
- package/dist/{chunk-AXMGQ5IB.js → chunk-VMNUJOEH.js} +712 -313
- package/dist/{chunk-DX2UDORT.js → chunk-XZFKK6V7.js} +107 -14
- package/dist/{health-LTDSTNOV.js → health-MB63O56B.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{studio-7E2LJS3A.js → studio-6QGXJBVH.js} +2 -2
- package/dist/{workspace-53EIHUXB.js → workspace-OGFYJA4N.js} +2 -2
- package/package.json +3 -3
|
@@ -10,7 +10,7 @@ import { execFileSync } from "child_process";
|
|
|
10
10
|
import { createHash } from "crypto";
|
|
11
11
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
12
12
|
import { createRequire } from "module";
|
|
13
|
-
import { dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2 } from "path";
|
|
13
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative, resolve as resolve2 } from "path";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
15
15
|
import {
|
|
16
16
|
auditProject,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
|
|
21
21
|
// src/workspace.ts
|
|
22
22
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
23
|
-
import { dirname, join, resolve } from "path";
|
|
23
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
24
24
|
function readPackageJson(dir) {
|
|
25
25
|
const path = join(dir, "package.json");
|
|
26
26
|
if (!existsSync(path)) return null;
|
|
@@ -92,7 +92,7 @@ function listWorkspaceAppCandidates(workspaceRoot) {
|
|
|
92
92
|
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
93
93
|
const absoluteCwd = resolve(cwd);
|
|
94
94
|
const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
95
|
-
const appRoot = projectArg ? resolve(
|
|
95
|
+
const appRoot = projectArg ? resolve(isAbsolute(projectArg) ? "/" : workspaceRoot, projectArg) : absoluteCwd;
|
|
96
96
|
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
97
97
|
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
98
98
|
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 0;
|
|
@@ -333,6 +333,9 @@ function contractAssertionApplies(assertion, metadata) {
|
|
|
333
333
|
if (assertion.rule === "tokens-file-present" && metadata.adoptionMode === "contract-only") {
|
|
334
334
|
return false;
|
|
335
335
|
}
|
|
336
|
+
if (metadata.adoptionMode === "contract-only" && (assertion.rule === "pack-manifest-present" || assertion.rule === "review-pack-present")) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
336
339
|
return true;
|
|
337
340
|
}
|
|
338
341
|
function slugify(value) {
|
|
@@ -382,15 +385,19 @@ function commandsForFinding(source) {
|
|
|
382
385
|
}
|
|
383
386
|
function commandContextForProject(projectRoot) {
|
|
384
387
|
const workspaceInfo = resolveWorkspaceInfo(projectRoot);
|
|
385
|
-
const relativeProjectPath = relative(workspaceInfo.workspaceRoot, projectRoot).replace(
|
|
386
|
-
|
|
388
|
+
const relativeProjectPath = relative(workspaceInfo.workspaceRoot, projectRoot).replace(
|
|
389
|
+
/\\/g,
|
|
390
|
+
"/"
|
|
391
|
+
);
|
|
392
|
+
const projectPath = relativeProjectPath && !relativeProjectPath.startsWith("..") && !isAbsolute2(relativeProjectPath) ? relativeProjectPath : null;
|
|
387
393
|
const projectFlag = projectPath ? ` --project ${projectPath}` : "";
|
|
388
394
|
const essencePath = projectPath ? `${projectPath}/decantr.essence.json` : "decantr.essence.json";
|
|
389
395
|
return {
|
|
390
396
|
projectPath,
|
|
391
397
|
compilePacksCommand: `decantr registry compile-packs ${essencePath} --write-context`,
|
|
392
398
|
verifyCommand: `decantr verify${projectFlag}`,
|
|
393
|
-
ciCommand: `decantr ci${projectFlag} --fail-on error
|
|
399
|
+
ciCommand: `decantr ci${projectFlag} --fail-on error`,
|
|
400
|
+
promptCommand: (id) => `decantr health${projectFlag} --prompt ${id}`
|
|
394
401
|
};
|
|
395
402
|
}
|
|
396
403
|
function rewriteHealthCommand(command, context) {
|
|
@@ -403,8 +410,14 @@ function rewriteHealthCommand(command, context) {
|
|
|
403
410
|
/^decantr init --existing\b/,
|
|
404
411
|
`decantr init --project ${context.projectPath} --existing`
|
|
405
412
|
);
|
|
406
|
-
rewritten = rewritten.replace(
|
|
407
|
-
|
|
413
|
+
rewritten = rewritten.replace(
|
|
414
|
+
/^decantr analyze\b/,
|
|
415
|
+
`decantr analyze --project ${context.projectPath}`
|
|
416
|
+
);
|
|
417
|
+
rewritten = rewritten.replace(
|
|
418
|
+
/^decantr check\b/,
|
|
419
|
+
`decantr check --project ${context.projectPath}`
|
|
420
|
+
);
|
|
408
421
|
rewritten = rewritten.replace(/^decantr audit\b/, context.verifyCommand);
|
|
409
422
|
rewritten = rewritten.replace(/^decantr health\b/, context.verifyCommand);
|
|
410
423
|
return rewritten;
|
|
@@ -509,6 +522,69 @@ function createHealthFinding(input) {
|
|
|
509
522
|
remediation
|
|
510
523
|
};
|
|
511
524
|
}
|
|
525
|
+
function collectContractPackConsistencyFindings(projectRoot, essence, manifest) {
|
|
526
|
+
if (!essence || typeof essence !== "object") return [];
|
|
527
|
+
const record = essence;
|
|
528
|
+
const blueprint = record.blueprint;
|
|
529
|
+
if (!blueprint || typeof blueprint !== "object") return [];
|
|
530
|
+
const bp = blueprint;
|
|
531
|
+
const routes = bp.routes && typeof bp.routes === "object" && !Array.isArray(bp.routes) ? bp.routes : {};
|
|
532
|
+
const routeTargets = new Set(
|
|
533
|
+
Object.values(routes).filter(
|
|
534
|
+
(entry) => Boolean(entry) && typeof entry === "object"
|
|
535
|
+
).map((entry) => `${String(entry.section ?? "")}/${String(entry.page ?? "")}`)
|
|
536
|
+
);
|
|
537
|
+
const pages = [];
|
|
538
|
+
for (const section of Array.isArray(bp.sections) ? bp.sections : []) {
|
|
539
|
+
if (!section || typeof section !== "object") continue;
|
|
540
|
+
const sectionRecord = section;
|
|
541
|
+
const sectionId = typeof sectionRecord.id === "string" ? sectionRecord.id : "unknown";
|
|
542
|
+
for (const page of Array.isArray(sectionRecord.pages) ? sectionRecord.pages : []) {
|
|
543
|
+
if (!page || typeof page !== "object") continue;
|
|
544
|
+
const pageRecord = page;
|
|
545
|
+
const pageId = typeof pageRecord.id === "string" ? pageRecord.id : "unknown";
|
|
546
|
+
const route = typeof pageRecord.route === "string" ? pageRecord.route : null;
|
|
547
|
+
pages.push({ section: sectionId, page: pageId, route });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const findings = [];
|
|
551
|
+
const routeLess = pages.filter(
|
|
552
|
+
(page) => !page.route && !routeTargets.has(`${page.section}/${page.page}`)
|
|
553
|
+
);
|
|
554
|
+
if (routeLess.length > 0) {
|
|
555
|
+
findings.push(
|
|
556
|
+
createHealthFinding({
|
|
557
|
+
source: "assertion",
|
|
558
|
+
category: "Contract Route Topology",
|
|
559
|
+
severity: "error",
|
|
560
|
+
message: "One or more blueprint pages have no route and cannot be addressed by task-time context.",
|
|
561
|
+
evidence: routeLess.slice(0, 8).map(
|
|
562
|
+
(page) => `${page.section}/${page.page} has no page.route or blueprint.routes entry`
|
|
563
|
+
),
|
|
564
|
+
rule: "page-route-required",
|
|
565
|
+
suggestedFix: "Add a route for each page or rerun the add-page flow with a route-aware Decantr CLI.",
|
|
566
|
+
baseId: "page-route-required"
|
|
567
|
+
})
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
const pagePackCount = manifest && "pages" in manifest && Array.isArray(manifest.pages) ? manifest.pages.length : 0;
|
|
571
|
+
if (manifest && pages.length !== pagePackCount) {
|
|
572
|
+
const context = commandContextForProject(projectRoot);
|
|
573
|
+
findings.push(
|
|
574
|
+
createHealthFinding({
|
|
575
|
+
source: "pack",
|
|
576
|
+
category: "Generated Artifacts",
|
|
577
|
+
severity: "warn",
|
|
578
|
+
message: `Compiled page pack count (${pagePackCount}) does not match the contract page count (${pages.length}).`,
|
|
579
|
+
evidence: ["Page packs should be regenerated after adding, removing, or re-routing pages."],
|
|
580
|
+
rule: "page-pack-count-mismatch",
|
|
581
|
+
suggestedFix: context.compilePacksCommand,
|
|
582
|
+
baseId: "page-pack-count-mismatch"
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
return findings;
|
|
587
|
+
}
|
|
512
588
|
function countFindings(findings) {
|
|
513
589
|
return {
|
|
514
590
|
errorCount: findings.filter((finding) => finding.severity === "error").length,
|
|
@@ -541,7 +617,7 @@ function isDuplicateFinding(existing, finding) {
|
|
|
541
617
|
}
|
|
542
618
|
function resolveOptionalPath(projectRoot, path) {
|
|
543
619
|
if (!path) return void 0;
|
|
544
|
-
return
|
|
620
|
+
return isAbsolute2(path) ? path : resolve2(projectRoot, path);
|
|
545
621
|
}
|
|
546
622
|
function hasProjectPlaywright(projectRoot) {
|
|
547
623
|
try {
|
|
@@ -758,7 +834,7 @@ function parseDecantrCssTokenNames(projectRoot) {
|
|
|
758
834
|
function collectDesignTokenEvidence(projectRoot, designTokensPath) {
|
|
759
835
|
const resolved = resolveOptionalPath(projectRoot, designTokensPath);
|
|
760
836
|
if (!resolved) return void 0;
|
|
761
|
-
const sourceLabel =
|
|
837
|
+
const sourceLabel = isAbsolute2(designTokensPath ?? "") ? "<design-tokens>" : designTokensPath ?? "<design-tokens>";
|
|
762
838
|
if (!existsSync2(resolved)) {
|
|
763
839
|
return {
|
|
764
840
|
source: sourceLabel,
|
|
@@ -1033,6 +1109,13 @@ async function createProjectHealthReport(projectRoot = process.cwd(), options =
|
|
|
1033
1109
|
}
|
|
1034
1110
|
const declaredRoutes = collectDeclaredRoutes(audit.essence);
|
|
1035
1111
|
const manifest = audit.packManifest;
|
|
1112
|
+
for (const consistencyFinding of collectContractPackConsistencyFindings(
|
|
1113
|
+
projectRoot,
|
|
1114
|
+
audit.essence,
|
|
1115
|
+
manifest
|
|
1116
|
+
)) {
|
|
1117
|
+
if (!isDuplicateFinding(seen, consistencyFinding)) findings.push(consistencyFinding);
|
|
1118
|
+
}
|
|
1036
1119
|
const browserVerification = await collectBrowserVerification(
|
|
1037
1120
|
projectRoot,
|
|
1038
1121
|
options,
|
|
@@ -1092,6 +1175,7 @@ function colorForStatus(status) {
|
|
|
1092
1175
|
}
|
|
1093
1176
|
function formatProjectHealthText(report) {
|
|
1094
1177
|
const color = colorForStatus(report.status);
|
|
1178
|
+
const commandContext = commandContextForProject(report.projectRoot);
|
|
1095
1179
|
const lines = [
|
|
1096
1180
|
`${BOLD}Decantr Project Health${RESET}`,
|
|
1097
1181
|
"",
|
|
@@ -1121,7 +1205,7 @@ function formatProjectHealthText(report) {
|
|
|
1121
1205
|
if (finding.suggestedFix) {
|
|
1122
1206
|
lines.push(` ${DIM}Fix: ${finding.suggestedFix}${RESET}`);
|
|
1123
1207
|
}
|
|
1124
|
-
lines.push(` ${DIM}Prompt:
|
|
1208
|
+
lines.push(` ${DIM}Prompt: ${commandContext.promptCommand(finding.id)}${RESET}`);
|
|
1125
1209
|
}
|
|
1126
1210
|
}
|
|
1127
1211
|
lines.push("");
|
|
@@ -1130,6 +1214,7 @@ function formatProjectHealthText(report) {
|
|
|
1130
1214
|
`;
|
|
1131
1215
|
}
|
|
1132
1216
|
function formatProjectHealthMarkdown(report) {
|
|
1217
|
+
const commandContext = commandContextForProject(report.projectRoot);
|
|
1133
1218
|
const lines = [
|
|
1134
1219
|
"# Decantr Project Health",
|
|
1135
1220
|
"",
|
|
@@ -1158,7 +1243,7 @@ function formatProjectHealthMarkdown(report) {
|
|
|
1158
1243
|
lines.push("- Evidence:");
|
|
1159
1244
|
for (const evidence of finding.evidence) lines.push(` - ${evidence}`);
|
|
1160
1245
|
}
|
|
1161
|
-
lines.push(`- Prompt:
|
|
1246
|
+
lines.push(`- Prompt: \`${commandContext.promptCommand(finding.id)}\``);
|
|
1162
1247
|
lines.push("");
|
|
1163
1248
|
}
|
|
1164
1249
|
}
|
|
@@ -1252,17 +1337,25 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
|
|
|
1252
1337
|
}
|
|
1253
1338
|
const format = resolveFormat(options);
|
|
1254
1339
|
const failOn = options.failOn ?? "error";
|
|
1255
|
-
const
|
|
1340
|
+
const evidenceBundle = options.evidence ? await createProjectEvidenceBundle(projectRoot, report, reportOptions) : null;
|
|
1341
|
+
const basePayload = options.evidence ? `${JSON.stringify(evidenceBundle, null, 2)}
|
|
1256
1342
|
` : format === "json" ? formatProjectHealthJson(report) : format === "markdown" ? formatProjectHealthMarkdown(report) : formatProjectHealthText(report);
|
|
1257
1343
|
const payload = baselineComparison && !options.evidence && format === "text" ? `${basePayload}${formatBaselineComparisonText(baselineComparison)}` : basePayload;
|
|
1258
1344
|
if (options.output) {
|
|
1259
|
-
const outputPath =
|
|
1345
|
+
const outputPath = isAbsolute2(options.output) ? options.output : join2(projectRoot, options.output);
|
|
1260
1346
|
mkdirSync(dirname2(outputPath), { recursive: true });
|
|
1261
1347
|
writeFileSync(outputPath, payload, "utf-8");
|
|
1262
1348
|
if (!options.ci) {
|
|
1263
1349
|
console.log(
|
|
1264
1350
|
`${GREEN}Wrote Decantr ${options.evidence ? "evidence bundle" : "health report"}:${RESET} ${options.output}`
|
|
1265
1351
|
);
|
|
1352
|
+
if (options.browser && evidenceBundle?.browser?.status === "unavailable") {
|
|
1353
|
+
const reason = evidenceBundle.browser.findings[0] ?? "Playwright is not available to Decantr in this project.";
|
|
1354
|
+
console.log(`${YELLOW}Browser evidence unavailable:${RESET} ${reason}`);
|
|
1355
|
+
console.log(
|
|
1356
|
+
`${DIM}Static evidence was still written. Install Playwright or rerun without --browser if screenshots are not needed.${RESET}`
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1266
1359
|
}
|
|
1267
1360
|
} else {
|
|
1268
1361
|
process.stdout.write(payload);
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkspaceHealthReport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ARR3EPS2.js";
|
|
4
4
|
import {
|
|
5
5
|
createProjectHealthReport
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XZFKK6V7.js";
|
|
7
7
|
import {
|
|
8
8
|
sendStudioHealthRefreshedTelemetry,
|
|
9
9
|
sendStudioStartedTelemetry
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
listWorkspaceProjects,
|
|
8
8
|
parseWorkspaceArgs,
|
|
9
9
|
shouldFailWorkspaceHealth
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-ARR3EPS2.js";
|
|
11
|
+
import "./chunk-XZFKK6V7.js";
|
|
12
12
|
import "./chunk-34TZXWIF.js";
|
|
13
13
|
export {
|
|
14
14
|
cmdWorkspace,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.3",
|
|
4
4
|
"description": "Decantr CLI - scaffold, audit, inspect Project Health, and maintain Decantr projects from the terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"decantr",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"ajv": "^8.20.0",
|
|
51
|
-
"@decantr/core": "2.1.0",
|
|
52
51
|
"@decantr/essence-spec": "2.0.1",
|
|
53
52
|
"@decantr/registry": "2.2.0",
|
|
53
|
+
"@decantr/verifier": "2.3.3",
|
|
54
54
|
"@decantr/telemetry": "2.2.1",
|
|
55
|
-
"@decantr/
|
|
55
|
+
"@decantr/core": "2.1.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsup",
|