@decantr/cli 2.9.3 → 2.9.4

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 CHANGED
@@ -176,11 +176,11 @@ decantr verify --workspace --changed --since origin/main
176
176
  decantr export --to figma-tokens
177
177
  ```
178
178
 
179
- Use `--json` for machines and schema validation, `--markdown` for summaries, `--evidence` for the privacy-redacted Evidence Bundle, and `--prompt <finding-id>` when you want a scoped remediation prompt for an AI assistant. The prompt command prints instructions only; it does not modify source files. In monorepos, prompt commands preserve `--project <path>` so the finding resolves from the same app that produced it. `--browser` uses a project-local Playwright install and a supplied base URL to capture local route screenshots under `.decantr/evidence/screenshots/` and write `.decantr/evidence/visual-manifest.json`; missing Playwright becomes a visible setup finding/message, not a crash or silent skip. `--save-baseline` writes `.decantr/health-baseline.json`; `--since-baseline` writes `.decantr/health-baseline-diff.json` with changed files, route impact, finding deltas, screenshot hash drift, and contract drift. `--design-tokens <path>` compares a Tokens Studio/Figma token JSON export against Decantr CSS token names. `decantr ci --fail-on error` fails only when blocking errors exist; `decantr ci --fail-on warn` also fails on warnings.
179
+ Use `--json` for machines and schema validation, `--markdown` for summaries, `--evidence` for the privacy-redacted Evidence Bundle, and `--prompt <finding-id>` when you want a scoped remediation prompt for an AI assistant. The prompt command prints instructions only; it does not modify source files. In monorepos, prompt commands preserve `--project <path>`, include app-prefixed read targets such as `apps/web/DECANTR.md`, and use root-safe runtime commands such as `pnpm --dir apps/web build` so the finding resolves from the same app that produced it. `--browser` uses a project-local Playwright install and a supplied base URL to capture local route screenshots under `.decantr/evidence/screenshots/` and write `.decantr/evidence/visual-manifest.json`; missing Playwright becomes a visible setup finding/message, not a crash or silent skip. `--save-baseline` writes `.decantr/health-baseline.json`; `--since-baseline` writes `.decantr/health-baseline-diff.json` with changed files, route impact, finding deltas, screenshot hash drift, and contract drift. `--design-tokens <path>` compares a Tokens Studio/Figma token JSON export against Decantr CSS token names. `decantr ci --fail-on error` fails only when blocking errors exist; `decantr ci --fail-on warn` also fails on warnings.
180
180
 
181
181
  `decantr ci init` installs `.github/workflows/decantr-ci.yml` for GitHub Actions. The generated workflow installs dependencies at the workspace root, writes JSON/markdown CI artifacts, gates with `decantr ci`, appends the markdown report to the GitHub step summary, and uploads both files as artifacts. Use `--force` to replace an existing workflow or `--fail-on warn` for stricter repositories. In monorepos, add `--project <path>` from the repository root; dependency install stays at the root while CI evaluates the app contract and uploads app-scoped artifacts. Use `--workspace` to generate an aggregate gate. Use `--provider generic` for Jenkins, Please, Buildkite, GitLab, Azure DevOps, or internal deployment tools. Generated CI uses the pinned local package-manager command and does not depend on `@latest`. Project Health remediation prompts are also monorepo-aware, so missing-pack fixes use `apps/web/decantr.essence.json` and CI recommendations include `--project apps/web`.
182
182
 
183
- `decantr workspace` is the monorepo reliability namespace. Before attach, `workspace list` shows app candidates. After attach, it also discovers Decantr projects from `.decantr/workspace.json` or by finding `decantr.essence.json` files. Workspace health runs projects with deterministic ordering, concurrency, per-project timeout, failure isolation, and aggregate JSON, and can limit a run to changed projects:
183
+ `decantr workspace` is the monorepo reliability namespace. Before attach, `workspace list` shows app candidates. After attach, it also discovers Decantr projects from `.decantr/workspace.json` or by finding `decantr.essence.json` files, and it distinguishes "attach another app" from the empty-workspace first attach. Workspace health runs projects with deterministic ordering, concurrency, per-project timeout, failure isolation, and aggregate JSON, and can limit a run to changed projects:
184
184
 
185
185
  ```bash
186
186
  decantr workspace list
package/dist/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-VMNUJOEH.js";
2
+ import "./chunk-GDGZFGMK.js";
3
3
  import "./chunk-RXF7ZYGK.js";
4
- import "./chunk-ARR3EPS2.js";
5
- import "./chunk-XZFKK6V7.js";
6
- import "./chunk-34TZXWIF.js";
4
+ import "./chunk-VCUFZB45.js";
5
+ import "./chunk-FACL3NXU.js";
6
+ import "./chunk-WONPNSSI.js";
@@ -3,7 +3,7 @@ import {
3
3
  sendProjectHealthCiFailedTelemetry,
4
4
  sendProjectHealthPromptTelemetry,
5
5
  sendProjectHealthReportTelemetry
6
- } from "./chunk-34TZXWIF.js";
6
+ } from "./chunk-WONPNSSI.js";
7
7
 
8
8
  // src/commands/health.ts
9
9
  import { execFileSync } from "child_process";
@@ -383,6 +383,47 @@ function commandsForFinding(source) {
383
383
  return ["decantr audit", "decantr health"];
384
384
  }
385
385
  }
386
+ function detectPackageManager(root) {
387
+ const pkg = readJsonFile(join2(root, "package.json"));
388
+ const declared = pkg?.packageManager?.split("@")[0];
389
+ if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
390
+ return declared;
391
+ }
392
+ if (existsSync2(join2(root, "pnpm-lock.yaml"))) return "pnpm";
393
+ if (existsSync2(join2(root, "package-lock.json"))) return "npm";
394
+ if (existsSync2(join2(root, "yarn.lock"))) return "yarn";
395
+ if (existsSync2(join2(root, "bun.lock")) || existsSync2(join2(root, "bun.lockb"))) return "bun";
396
+ return "unknown";
397
+ }
398
+ function buildCommandForProject(workspaceRoot, projectPath) {
399
+ const packageManager = detectPackageManager(workspaceRoot);
400
+ if (projectPath) {
401
+ switch (packageManager) {
402
+ case "pnpm":
403
+ return `pnpm --dir ${projectPath} build`;
404
+ case "yarn":
405
+ return `yarn --cwd ${projectPath} build`;
406
+ case "bun":
407
+ return `bun --cwd ${projectPath} run build`;
408
+ case "npm":
409
+ return `npm --prefix ${projectPath} run build`;
410
+ default:
411
+ return `cd ${projectPath} && npm run build`;
412
+ }
413
+ }
414
+ switch (packageManager) {
415
+ case "pnpm":
416
+ return "pnpm build";
417
+ case "yarn":
418
+ return "yarn build";
419
+ case "bun":
420
+ return "bun run build";
421
+ case "npm":
422
+ return "npm run build";
423
+ default:
424
+ return "npm run build";
425
+ }
426
+ }
386
427
  function commandContextForProject(projectRoot) {
387
428
  const workspaceInfo = resolveWorkspaceInfo(projectRoot);
388
429
  const relativeProjectPath = relative(workspaceInfo.workspaceRoot, projectRoot).replace(
@@ -394,6 +435,7 @@ function commandContextForProject(projectRoot) {
394
435
  const essencePath = projectPath ? `${projectPath}/decantr.essence.json` : "decantr.essence.json";
395
436
  return {
396
437
  projectPath,
438
+ buildCommand: buildCommandForProject(workspaceInfo.workspaceRoot, projectPath),
397
439
  compilePacksCommand: `decantr registry compile-packs ${essencePath} --write-context`,
398
440
  verifyCommand: `decantr verify${projectFlag}`,
399
441
  ciCommand: `decantr ci${projectFlag} --fail-on error`,
@@ -401,6 +443,7 @@ function commandContextForProject(projectRoot) {
401
443
  };
402
444
  }
403
445
  function rewriteHealthCommand(command, context) {
446
+ if (command === "npm run build") return context.buildCommand;
404
447
  let rewritten = command.replace(
405
448
  /decantr registry compile-packs decantr\.essence\.json --write-context/g,
406
449
  context.compilePacksCommand
@@ -461,17 +504,25 @@ function scopeHealthFindingsToProject(projectRoot, findings) {
461
504
  message: finding.message,
462
505
  evidence: finding.evidence,
463
506
  suggestedFix,
464
- commands
507
+ commands,
508
+ projectPath: context.projectPath
465
509
  })
466
510
  }
467
511
  };
468
512
  });
469
513
  }
470
514
  function buildRemediationPrompt(input) {
515
+ const prefix = input.projectPath ? `${input.projectPath}/` : "";
516
+ const readTargets = [
517
+ `${prefix}DECANTR.md`,
518
+ `${prefix}decantr.essence.json`,
519
+ `${prefix}.decantr/context/scaffold-pack.md`,
520
+ `${prefix}.decantr/context/scaffold.md`
521
+ ];
471
522
  return [
472
523
  "You are fixing one Decantr Project Health finding in this local workspace.",
473
524
  "",
474
- "Read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` if they exist. For route or page work, read the matching page/section packs before editing.",
525
+ `Read project-scoped Decantr files if they exist: ${readTargets.map((target) => `\`${target}\``).join(", ")}. For route or page work, read the matching page/section packs before editing.`,
475
526
  "",
476
527
  `Finding: ${input.id}`,
477
528
  `Source: ${input.source}`,
@@ -22,14 +22,14 @@ import {
22
22
  listWorkspaceCandidates,
23
23
  listWorkspaceProjects,
24
24
  shouldFailWorkspaceHealth
25
- } from "./chunk-ARR3EPS2.js";
25
+ } from "./chunk-VCUFZB45.js";
26
26
  import {
27
27
  createProjectHealthReport,
28
28
  formatProjectHealthMarkdown,
29
29
  formatProjectHealthText,
30
30
  resolveWorkspaceInfo,
31
31
  shouldFailHealth
32
- } from "./chunk-XZFKK6V7.js";
32
+ } from "./chunk-FACL3NXU.js";
33
33
  import {
34
34
  buildGuardRegistryContext,
35
35
  createDoctrineMap,
@@ -46,7 +46,7 @@ import {
46
46
  sendCliCommandTelemetry,
47
47
  sendNewProjectCompletedTelemetry,
48
48
  writeDoctrineMap
49
- } from "./chunk-34TZXWIF.js";
49
+ } from "./chunk-WONPNSSI.js";
50
50
 
51
51
  // src/index.ts
52
52
  import { existsSync as existsSync29, mkdirSync as mkdirSync16, readdirSync as readdirSync9, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
@@ -4682,6 +4682,16 @@ var GREEN5 = "\x1B[32m";
4682
4682
  var RED4 = "\x1B[31m";
4683
4683
  var DIM5 = "\x1B[2m";
4684
4684
  var RESET5 = "\x1B[0m";
4685
+ function readAdoptionMode(projectRoot) {
4686
+ const projectJsonPath = join19(projectRoot, ".decantr", "project.json");
4687
+ if (!existsSync17(projectJsonPath)) return null;
4688
+ try {
4689
+ const parsed = JSON.parse(readFileSync14(projectJsonPath, "utf-8"));
4690
+ return typeof parsed.initialized?.adoptionMode === "string" ? parsed.initialized.adoptionMode : null;
4691
+ } catch {
4692
+ return null;
4693
+ }
4694
+ }
4685
4695
  function resolveOutputPath(projectRoot, output, fallback) {
4686
4696
  if (!output) return fallback;
4687
4697
  return isAbsolute(output) ? output : join19(projectRoot, output);
@@ -4853,9 +4863,9 @@ async function cmdExport(target, projectRoot, options = {}) {
4853
4863
  return;
4854
4864
  }
4855
4865
  if (!existsSync17(tokensPath)) {
4856
- console.error(
4857
- `${RED4}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET5}`
4858
- );
4866
+ const adoptionMode = readAdoptionMode(projectRoot);
4867
+ const message = adoptionMode === "contract-only" ? `No src/styles/tokens.css found. This export reads Decantr CSS tokens, but this project is contract-only Brownfield and may intentionally use its own Tailwind/Sass token system. Export from the app's token source or adopt a style bridge before using \`decantr export --to ${target}\`.` : "No src/styles/tokens.css found. Run `decantr refresh` to generate tokens.";
4868
+ console.error(`${RED4}${message}${RESET5}`);
4859
4869
  process.exitCode = 1;
4860
4870
  return;
4861
4871
  }
@@ -10320,7 +10330,7 @@ async function cmdAdoptWorkflow(args) {
10320
10330
  console.log(dim3("Skipping hosted pack hydration in offline mode."));
10321
10331
  }
10322
10332
  if (runVerify) {
10323
- const { cmdHealth } = await import("./health-MB63O56B.js");
10333
+ const { cmdHealth } = await import("./health-R7AV5G4V.js");
10324
10334
  await cmdHealth(projectRoot, {
10325
10335
  browser: runBrowser,
10326
10336
  browserBaseUrl: baseUrl,
@@ -10391,7 +10401,7 @@ async function cmdVerifyWorkflow(args) {
10391
10401
  return;
10392
10402
  }
10393
10403
  if (workspaceMode) {
10394
- const { cmdWorkspace } = await import("./workspace-OGFYJA4N.js");
10404
+ const { cmdWorkspace } = await import("./workspace-DRHTQ2NG.js");
10395
10405
  await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
10396
10406
  return;
10397
10407
  }
@@ -10424,7 +10434,7 @@ async function cmdVerifyWorkflow(args) {
10424
10434
  }
10425
10435
  let guardExitCode;
10426
10436
  if (brownfield) {
10427
- const { cmdHeal, collectCheckIssues } = await import("./heal-2BDT7TR5.js");
10437
+ const { cmdHeal, collectCheckIssues } = await import("./heal-ZQHEHBUJ.js");
10428
10438
  if (quietOutput) {
10429
10439
  const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
10430
10440
  guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
@@ -10434,7 +10444,7 @@ async function cmdVerifyWorkflow(args) {
10434
10444
  process.exitCode = void 0;
10435
10445
  }
10436
10446
  }
10437
- const { cmdHealth, parseHealthArgs } = await import("./health-MB63O56B.js");
10447
+ const { cmdHealth, parseHealthArgs } = await import("./health-R7AV5G4V.js");
10438
10448
  await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
10439
10449
  if (localPatterns) {
10440
10450
  const validation = validateLocalLaw(workspaceInfo.appRoot);
@@ -11417,7 +11427,7 @@ async function main() {
11417
11427
  `${YELLOW10}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET16}`
11418
11428
  );
11419
11429
  }
11420
- const { cmdHeal } = await import("./heal-2BDT7TR5.js");
11430
+ const { cmdHeal } = await import("./heal-ZQHEHBUJ.js");
11421
11431
  const { flags } = parseLooseArgs(args);
11422
11432
  const workspaceInfo = resolveWorkflowProject(flags, "check");
11423
11433
  if (!workspaceInfo) break;
@@ -11442,7 +11452,7 @@ async function main() {
11442
11452
  const { flags } = parseLooseArgs(args);
11443
11453
  const workspaceInfo = resolveWorkflowProject(flags, "health");
11444
11454
  if (!workspaceInfo) break;
11445
- const { cmdHealth, parseHealthArgs } = await import("./health-MB63O56B.js");
11455
+ const { cmdHealth, parseHealthArgs } = await import("./health-R7AV5G4V.js");
11446
11456
  await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
11447
11457
  } catch (e) {
11448
11458
  console.error(error2(e.message));
@@ -11470,7 +11480,7 @@ async function main() {
11470
11480
  cmdStudioHelp();
11471
11481
  break;
11472
11482
  }
11473
- const { cmdStudio, parseStudioArgs } = await import("./studio-6QGXJBVH.js");
11483
+ const { cmdStudio, parseStudioArgs } = await import("./studio-2734P63C.js");
11474
11484
  await cmdStudio(process.cwd(), parseStudioArgs(args));
11475
11485
  } catch (e) {
11476
11486
  console.error(error2(e.message));
@@ -11484,7 +11494,7 @@ async function main() {
11484
11494
  cmdWorkspaceHelp();
11485
11495
  break;
11486
11496
  }
11487
- const { cmdWorkspace } = await import("./workspace-OGFYJA4N.js");
11497
+ const { cmdWorkspace } = await import("./workspace-DRHTQ2NG.js");
11488
11498
  await cmdWorkspace(process.cwd(), args);
11489
11499
  } catch (e) {
11490
11500
  console.error(error2(e.message));
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createProjectHealthReport,
3
3
  listWorkspaceAppCandidates
4
- } from "./chunk-XZFKK6V7.js";
4
+ } from "./chunk-FACL3NXU.js";
5
5
 
6
6
  // src/commands/workspace.ts
7
7
  import { execFileSync } from "child_process";
@@ -322,7 +322,7 @@ async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"])
322
322
  }
323
323
  if (unattachedCandidates.length > 0) {
324
324
  console.log("");
325
- console.log("Start by attaching one app:");
325
+ console.log(projects.length > 0 ? "Attach another app:" : "Start by attaching one app:");
326
326
  console.log(` ${unattachedCandidates[0].suggestedAdoptCommand}`);
327
327
  }
328
328
  return;
@@ -1323,6 +1323,22 @@ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
1323
1323
  ".cache"
1324
1324
  ]);
1325
1325
  var MAX_FILE_SIZE = 1024 * 1024;
1326
+ function isNonUiInteractionSource(relativePath) {
1327
+ const normalized2 = relativePath.replace(/\\/g, "/");
1328
+ if (/\.d\.ts$/i.test(normalized2)) return true;
1329
+ if (/(?:^|\/)(?:__tests__|__mocks__|tests?|specs?|fixtures?|mocks?|stories?)(?:\/|$)/i.test(
1330
+ normalized2
1331
+ )) {
1332
+ return true;
1333
+ }
1334
+ if (/\.(?:test|spec|stories?|story|fixture|mock)\.(?:[cm]?[jt]sx?|mdx|html)$/i.test(normalized2)) {
1335
+ return true;
1336
+ }
1337
+ if (/^(?:src\/)?app\/api\//i.test(normalized2)) return true;
1338
+ if (/^(?:src\/)?pages\/api\//i.test(normalized2)) return true;
1339
+ if (/(?:^|\/)route\.[cm]?[jt]s$/i.test(normalized2)) return true;
1340
+ return false;
1341
+ }
1326
1342
  function walkSourceTree(rootDir) {
1327
1343
  const sources = /* @__PURE__ */ new Map();
1328
1344
  function walk2(dir) {
@@ -1341,12 +1357,14 @@ function walkSourceTree(rootDir) {
1341
1357
  } catch {
1342
1358
  continue;
1343
1359
  }
1360
+ const relativePath = relative3(rootDir, fullPath) || entry;
1344
1361
  if (s.isDirectory()) {
1345
1362
  walk2(fullPath);
1346
1363
  } else if (s.isFile() && SCAN_EXTENSIONS.has(extname2(entry))) {
1347
1364
  if (s.size > MAX_FILE_SIZE) continue;
1365
+ if (isNonUiInteractionSource(relativePath)) continue;
1348
1366
  try {
1349
- sources.set(relative3(rootDir, fullPath) || entry, readFileSync8(fullPath, "utf8"));
1367
+ sources.set(relativePath, readFileSync8(fullPath, "utf8"));
1350
1368
  } catch {
1351
1369
  }
1352
1370
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cmdHeal,
3
3
  collectCheckIssues
4
- } from "./chunk-34TZXWIF.js";
4
+ } from "./chunk-WONPNSSI.js";
5
5
  export {
6
6
  cmdHeal,
7
7
  collectCheckIssues
@@ -10,8 +10,8 @@ import {
10
10
  renderProjectHealthCiWorkflow,
11
11
  shouldFailHealth,
12
12
  writeProjectHealthCiWorkflow
13
- } from "./chunk-XZFKK6V7.js";
14
- import "./chunk-34TZXWIF.js";
13
+ } from "./chunk-FACL3NXU.js";
14
+ import "./chunk-WONPNSSI.js";
15
15
  export {
16
16
  cmdHealth,
17
17
  collectDesignTokenEvidence,
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import "./chunk-VMNUJOEH.js";
1
+ import "./chunk-GDGZFGMK.js";
2
2
  import "./chunk-RXF7ZYGK.js";
3
- import "./chunk-ARR3EPS2.js";
4
- import "./chunk-XZFKK6V7.js";
5
- import "./chunk-34TZXWIF.js";
3
+ import "./chunk-VCUFZB45.js";
4
+ import "./chunk-FACL3NXU.js";
5
+ import "./chunk-WONPNSSI.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createWorkspaceHealthReport
3
- } from "./chunk-ARR3EPS2.js";
3
+ } from "./chunk-VCUFZB45.js";
4
4
  import {
5
5
  createProjectHealthReport
6
- } from "./chunk-XZFKK6V7.js";
6
+ } from "./chunk-FACL3NXU.js";
7
7
  import {
8
8
  sendStudioHealthRefreshedTelemetry,
9
9
  sendStudioStartedTelemetry
10
- } from "./chunk-34TZXWIF.js";
10
+ } from "./chunk-WONPNSSI.js";
11
11
 
12
12
  // src/commands/studio.ts
13
13
  import { readFileSync } from "fs";
@@ -7,9 +7,9 @@ import {
7
7
  listWorkspaceProjects,
8
8
  parseWorkspaceArgs,
9
9
  shouldFailWorkspaceHealth
10
- } from "./chunk-ARR3EPS2.js";
11
- import "./chunk-XZFKK6V7.js";
12
- import "./chunk-34TZXWIF.js";
10
+ } from "./chunk-VCUFZB45.js";
11
+ import "./chunk-FACL3NXU.js";
12
+ import "./chunk-WONPNSSI.js";
13
13
  export {
14
14
  cmdWorkspace,
15
15
  createWorkspaceHealthReport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/cli",
3
- "version": "2.9.3",
3
+ "version": "2.9.4",
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",
51
52
  "@decantr/essence-spec": "2.0.1",
52
53
  "@decantr/registry": "2.2.0",
53
- "@decantr/verifier": "2.3.3",
54
54
  "@decantr/telemetry": "2.2.1",
55
- "@decantr/core": "2.1.0"
55
+ "@decantr/verifier": "2.3.3"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",