@decantr/cli 1.8.0 → 1.10.0

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
@@ -61,6 +61,7 @@ Brownfield analysis also writes `.decantr/doctrine-map.json`, a ranked source-pr
61
61
  - generates execution-pack context files for AI coding assistants
62
62
  - audits projects against Decantr contracts
63
63
  - produces local Project Health reports and a localhost Studio dashboard for end-user drift triage
64
+ - audits local registry content repositories with Content Health reports for schema, reference, and quality coverage
64
65
  - searches the registry and showcase benchmark corpus
65
66
  - validates, refreshes, and maintains `decantr.essence.json`
66
67
 
@@ -80,6 +81,7 @@ decantr audit
80
81
  decantr check
81
82
  decantr health --ci --fail-on error
82
83
  decantr studio --port 4319 --host 127.0.0.1
84
+ decantr content-health --ci --fail-on error
83
85
  decantr registry summary --namespace @official --json
84
86
  decantr showcase verification --json
85
87
  ```
@@ -95,10 +97,14 @@ decantr health --markdown --output health.md
95
97
  decantr health --ci --fail-on error
96
98
  decantr health --ci --fail-on warn
97
99
  decantr health --prompt <finding-id>
100
+ decantr health init-ci
101
+ decantr health init-ci --fail-on warn --cli-version latest --force
98
102
  ```
99
103
 
100
104
  Use `--json` for machines and schema validation, `--markdown` for CI summaries, and `--prompt <finding-id>` when you want a scoped remediation prompt for an AI assistant. `--ci --fail-on error` fails only when blocking errors exist; `--ci --fail-on warn` also fails on warnings.
101
105
 
106
+ `decantr health init-ci` installs `.github/workflows/decantr-health.yml` for GitHub Actions. The generated workflow installs project dependencies, writes `decantr-health.json`, gates with `decantr health --ci --fail-on error --markdown --output decantr-health.md`, appends the markdown report to the GitHub step summary, and uploads both files as artifacts. Use `--force` to replace an existing workflow, `--fail-on warn` for stricter repositories, or `--cli-version <version|latest>` to pin the package used by CI.
107
+
102
108
  `decantr studio` starts a local-only dashboard powered by the same report. It uses Node built-ins only and serves `GET /`, `GET /api/health`, and `POST /api/refresh`.
103
109
 
104
110
  ```bash
@@ -108,6 +114,21 @@ decantr studio --port 4319 --host 127.0.0.1
108
114
 
109
115
  Studio is for local triage, not Decantr admin telemetry. The tabs cover Overview, Routes, Drift, Findings, Remediation, CI, and Packs without uploading source code, prompts, file paths, or project data.
110
116
 
117
+ ## Content Health
118
+
119
+ `decantr content-health` is the local supply-chain observability command for registry content repositories such as `decantr-content`. It is separate from Project Health: Project Health checks an end-user app against its Decantr contract, while Content Health checks published content inputs before they flow into the hosted registry.
120
+
121
+ ```bash
122
+ decantr content-health
123
+ decantr content-health --json
124
+ decantr content-health --markdown --output content-health.md
125
+ decantr content-health --ci --fail-on error
126
+ decantr content-health --ci --fail-on warn
127
+ decantr content-health --prompt <finding-id>
128
+ ```
129
+
130
+ The report validates local `patterns/`, `themes/`, `blueprints/`, `archetypes/`, and `shells/` against the published registry schemas, checks hard references such as blueprint themes and composed archetypes, summarizes softer generation-coverage gaps such as missing pattern coverage, and emits AI-ready remediation prompts. It does not call the hosted registry by default; use the existing registry drift audits when you need live publish parity.
131
+
111
132
  ## Greenfield Certification
112
133
 
113
134
  Use the built-in certification harness before releases when you want to prove that representative blueprints still scaffold into runnable starter projects:
@@ -182,7 +203,7 @@ Recommended read order for AI-assisted scaffolding:
182
203
 
183
204
  Treat the compiled execution packs as the source of truth. Use the narrative docs as secondary explanation, start with the shell and route structure first, and run `decantr check` plus `decantr audit` after implementation.
184
205
 
185
- For a broader health pass, run `decantr health` after `refresh`, before opening a pull request, or inside CI. Findings include remediation commands and can be turned into focused AI prompts with `decantr health --prompt <finding-id>`.
206
+ For a broader health pass, run `decantr health` after `refresh`, before opening a pull request, or inside CI. Install the default GitHub Actions gate with `decantr health init-ci`. Findings include remediation commands and can be turned into focused AI prompts with `decantr health --prompt <finding-id>`.
186
207
 
187
208
  For cold-start harness or certification runs, use only the scaffolded workspace files as the contract. If local scaffold files disagree, stop and report the mismatch rather than relying on repo-global Decantr assumptions.
188
209
 
package/dist/bin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-Y45MCRGI.js";
2
+ import "./chunk-FLZVSNB5.js";
3
3
  import "./chunk-USOO77A5.js";
4
4
  import "./chunk-DI2PLOJ6.js";
@@ -6481,6 +6481,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
6481
6481
  console.log(" Commands:");
6482
6482
  console.log(` ${cyan3("decantr status")} Project health`);
6483
6483
  console.log(` ${cyan3("decantr health")} Contract health report`);
6484
+ console.log(` ${cyan3("decantr content-health")} Registry content health report`);
6484
6485
  console.log(` ${cyan3("decantr studio")} Local health dashboard`);
6485
6486
  console.log(` ${cyan3("decantr search")} Search registry`);
6486
6487
  console.log(` ${cyan3("decantr get")} Fetch content details`);
@@ -6926,6 +6927,8 @@ ${BOLD6}Usage:${RESET13}
6926
6927
  decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
6927
6928
  decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
6928
6929
  decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
6930
+ decantr health init-ci [--force] [--fail-on <error|warn|none>] [--cli-version <version|latest>]
6931
+ decantr content-health [--json] [--markdown] [--ci]
6929
6932
  decantr rules preview [--project=<path>]
6930
6933
  decantr rules apply [--project=<path>]
6931
6934
  decantr validate [path]
@@ -6963,7 +6966,8 @@ ${BOLD6}Commands:${RESET13}
6963
6966
  ${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
6964
6967
  ${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
6965
6968
  ${cyan3("status")} Show project status, DNA axioms, and blueprint info
6966
- ${cyan3("health")} Generate a local Project Health report [--json] [--markdown] [--ci]
6969
+ ${cyan3("health")} Generate a local Project Health report [--json] [--markdown] [--ci]; use health init-ci to install a GitHub Actions gate
6970
+ ${cyan3("content-health")} Generate a local registry content health report [--json] [--markdown] [--ci]
6967
6971
  ${cyan3("studio")} Open a local Project Health dashboard backed by the same report
6968
6972
  ${cyan3("sync")} Sync registry content from API
6969
6973
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
@@ -7002,7 +7006,9 @@ ${BOLD6}Examples:${RESET13}
7002
7006
  decantr rules apply
7003
7007
  decantr status
7004
7008
  decantr health
7009
+ decantr health init-ci
7005
7010
  decantr health --ci --fail-on error
7011
+ decantr content-health --ci --fail-on error
7006
7012
  decantr studio
7007
7013
  decantr audit
7008
7014
  decantr audit src/pages/HomePage.tsx
@@ -7184,7 +7190,7 @@ async function main() {
7184
7190
  }
7185
7191
  case "health": {
7186
7192
  try {
7187
- const { cmdHealth, parseHealthArgs } = await import("./health-VSL4MROO.js");
7193
+ const { cmdHealth, parseHealthArgs } = await import("./health-SIKAOE2Z.js");
7188
7194
  await cmdHealth(process.cwd(), parseHealthArgs(args));
7189
7195
  } catch (e) {
7190
7196
  console.error(error3(e.message));
@@ -7192,9 +7198,19 @@ async function main() {
7192
7198
  }
7193
7199
  break;
7194
7200
  }
7201
+ case "content-health": {
7202
+ try {
7203
+ const { cmdContentHealth, parseContentHealthArgs } = await import("./content-health-QQHBR6XG.js");
7204
+ await cmdContentHealth(process.cwd(), parseContentHealthArgs(args));
7205
+ } catch (e) {
7206
+ console.error(error3(e.message));
7207
+ process.exitCode = 1;
7208
+ }
7209
+ break;
7210
+ }
7195
7211
  case "studio": {
7196
7212
  try {
7197
- const { cmdStudio, parseStudioArgs } = await import("./studio-BCTWKXFH.js");
7213
+ const { cmdStudio, parseStudioArgs } = await import("./studio-EQSSNA6D.js");
7198
7214
  await cmdStudio(process.cwd(), parseStudioArgs(args));
7199
7215
  } catch (e) {
7200
7216
  console.error(error3(e.message));
@@ -3,8 +3,9 @@ import {
3
3
  } from "./chunk-RSDCWAHD.js";
4
4
 
5
5
  // src/commands/health.ts
6
- import { existsSync, readFileSync, writeFileSync } from "fs";
7
- import { join } from "path";
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
+ import { dirname, join } from "path";
8
+ import { fileURLToPath } from "url";
8
9
  import {
9
10
  auditProject
10
11
  } from "@decantr/verifier";
@@ -16,6 +17,11 @@ var GREEN = "\x1B[32m";
16
17
  var CYAN = "\x1B[36m";
17
18
  var YELLOW = "\x1B[33m";
18
19
  var PROJECT_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v1.json";
20
+ var DEFAULT_HEALTH_CI_WORKFLOW_PATH = ".github/workflows/decantr-health.yml";
21
+ var DEFAULT_HEALTH_CI_REPORT_PATH = "decantr-health.md";
22
+ var DEFAULT_HEALTH_CI_JSON_PATH = "decantr-health.json";
23
+ var DEFAULT_HEALTH_CI_CLI_VERSION = "latest";
24
+ var __dirname = dirname(fileURLToPath(import.meta.url));
19
25
  function readProjectMetadata(projectRoot) {
20
26
  const projectJsonPath = join(projectRoot, ".decantr", "project.json");
21
27
  if (!existsSync(projectJsonPath)) {
@@ -34,6 +40,91 @@ function readProjectMetadata(projectRoot) {
34
40
  return { workflowMode: null, adoptionMode: null, autoBrownfield: false };
35
41
  }
36
42
  }
43
+ function loadHealthTemplate(name) {
44
+ const fromDist = join(__dirname, "..", "src", "templates", name);
45
+ if (existsSync(fromDist)) return readFileSync(fromDist, "utf-8");
46
+ const fromSrc = join(__dirname, "..", "templates", name);
47
+ if (existsSync(fromSrc)) return readFileSync(fromSrc, "utf-8");
48
+ const fromCommandSrc = join(__dirname, "..", "..", "templates", name);
49
+ if (existsSync(fromCommandSrc)) return readFileSync(fromCommandSrc, "utf-8");
50
+ throw new Error(`Template not found: ${name}`);
51
+ }
52
+ function renderTemplate(template, vars) {
53
+ let result = template;
54
+ for (const [key, value] of Object.entries(vars)) {
55
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
56
+ }
57
+ return result;
58
+ }
59
+ function normalizeCliPackageSpecifier(version) {
60
+ const value = (version || DEFAULT_HEALTH_CI_CLI_VERSION).trim();
61
+ if (!value) return `@decantr/cli@${DEFAULT_HEALTH_CI_CLI_VERSION}`;
62
+ const versionToken = value.startsWith("@decantr/cli@") ? value.slice("@decantr/cli@".length) : value;
63
+ if (!/^[A-Za-z0-9._~^*-]+$/.test(versionToken)) {
64
+ throw new Error(
65
+ "Invalid --cli-version value. Use a package version or dist-tag such as latest, 1.10.0, or next."
66
+ );
67
+ }
68
+ return `@decantr/cli@${versionToken}`;
69
+ }
70
+ function normalizeHealthFailOn(value) {
71
+ const failOn = value ?? "error";
72
+ if (!["error", "warn", "none"].includes(failOn)) {
73
+ throw new Error("Invalid --fail-on value. Use error, warn, or none.");
74
+ }
75
+ return failOn;
76
+ }
77
+ function validateWorkflowPath(value) {
78
+ const normalized = value.trim();
79
+ if (!normalized || normalized.startsWith("/") || normalized.startsWith("-") || normalized.includes("..") || normalized.includes("\\") || /\s/.test(normalized)) {
80
+ throw new Error(
81
+ "Invalid --workflow-path value. Use a relative path without spaces or parent-directory segments."
82
+ );
83
+ }
84
+ return normalized;
85
+ }
86
+ function validateArtifactPath(value, flag) {
87
+ const normalized = value.trim();
88
+ if (!normalized || normalized.startsWith("/") || normalized.startsWith("-") || normalized.includes("..") || normalized.includes("\\") || /\s/.test(normalized)) {
89
+ throw new Error(
90
+ `Invalid ${flag} value. Use a relative artifact path without spaces or parent-directory segments.`
91
+ );
92
+ }
93
+ return normalized;
94
+ }
95
+ function renderProjectHealthCiWorkflow(options = {}) {
96
+ const failOn = normalizeHealthFailOn(options.failOn);
97
+ const template = loadHealthTemplate("decantr-health.workflow.yml.template");
98
+ return renderTemplate(template, {
99
+ CLI_PACKAGE: normalizeCliPackageSpecifier(options.cliVersion),
100
+ FAIL_ON: failOn,
101
+ REPORT_PATH: validateArtifactPath(
102
+ options.reportPath || DEFAULT_HEALTH_CI_REPORT_PATH,
103
+ "--report-path"
104
+ ),
105
+ JSON_PATH: validateArtifactPath(options.jsonPath || DEFAULT_HEALTH_CI_JSON_PATH, "--json-path")
106
+ });
107
+ }
108
+ function writeProjectHealthCiWorkflow(projectRoot, options = {}) {
109
+ const workflowRelativePath = validateWorkflowPath(
110
+ options.workflowPath || DEFAULT_HEALTH_CI_WORKFLOW_PATH
111
+ );
112
+ const workflowPath = join(projectRoot, workflowRelativePath);
113
+ const alreadyExists = existsSync(workflowPath);
114
+ if (alreadyExists && !options.force) {
115
+ throw new Error(
116
+ `${workflowRelativePath} already exists. Re-run with --force to replace it, or use --workflow-path <file>.`
117
+ );
118
+ }
119
+ mkdirSync(dirname(workflowPath), { recursive: true });
120
+ writeFileSync(workflowPath, renderProjectHealthCiWorkflow(options), "utf-8");
121
+ return {
122
+ path: workflowRelativePath,
123
+ created: !alreadyExists,
124
+ cliPackage: normalizeCliPackageSpecifier(options.cliVersion),
125
+ failOn: normalizeHealthFailOn(options.failOn)
126
+ };
127
+ }
37
128
  function collectDeclaredRoutes(essence) {
38
129
  if (!essence || typeof essence !== "object") return [];
39
130
  const record = essence;
@@ -393,6 +484,19 @@ function shouldFailHealth(report, failOn) {
393
484
  return report.summary.errorCount > 0;
394
485
  }
395
486
  async function cmdHealth(projectRoot = process.cwd(), options = {}) {
487
+ if (options.initCi) {
488
+ try {
489
+ const result = writeProjectHealthCiWorkflow(projectRoot, options.initCi);
490
+ const action = result.created ? "Created" : "Updated";
491
+ console.log(`${GREEN}${action} Decantr Project Health workflow:${RESET} ${result.path}`);
492
+ console.log(`${DIM}CLI package: ${result.cliPackage}${RESET}`);
493
+ console.log(`${DIM}CI gate: decantr health --ci --fail-on ${result.failOn}${RESET}`);
494
+ } catch (e) {
495
+ console.error(`${RED}${e.message}${RESET}`);
496
+ process.exitCode = 1;
497
+ }
498
+ return;
499
+ }
396
500
  const report = await createProjectHealthReport(projectRoot);
397
501
  if (options.promptId) {
398
502
  const finding = report.findings.find((entry) => entry.id === options.promptId);
@@ -420,6 +524,39 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
420
524
  }
421
525
  function parseHealthArgs(args) {
422
526
  const options = {};
527
+ if (args[1] === "init-ci") {
528
+ options.initCi = {};
529
+ for (let index = 2; index < args.length; index += 1) {
530
+ const arg = args[index];
531
+ if (arg === "--force") {
532
+ options.initCi.force = true;
533
+ } else if (arg === "--fail-on" && args[index + 1]) {
534
+ options.initCi.failOn = args[++index];
535
+ } else if (arg.startsWith("--fail-on=")) {
536
+ options.initCi.failOn = arg.split("=")[1];
537
+ } else if ((arg === "--cli-version" || arg === "--cli") && args[index + 1]) {
538
+ options.initCi.cliVersion = args[++index];
539
+ } else if (arg.startsWith("--cli-version=")) {
540
+ options.initCi.cliVersion = arg.split("=")[1];
541
+ } else if (arg.startsWith("--cli=")) {
542
+ options.initCi.cliVersion = arg.split("=")[1];
543
+ } else if (arg === "--workflow-path" && args[index + 1]) {
544
+ options.initCi.workflowPath = args[++index];
545
+ } else if (arg.startsWith("--workflow-path=")) {
546
+ options.initCi.workflowPath = arg.split("=")[1];
547
+ } else if (arg === "--report-path" && args[index + 1]) {
548
+ options.initCi.reportPath = args[++index];
549
+ } else if (arg.startsWith("--report-path=")) {
550
+ options.initCi.reportPath = arg.split("=")[1];
551
+ } else if (arg === "--json-path" && args[index + 1]) {
552
+ options.initCi.jsonPath = args[++index];
553
+ } else if (arg.startsWith("--json-path=")) {
554
+ options.initCi.jsonPath = arg.split("=")[1];
555
+ }
556
+ }
557
+ normalizeHealthFailOn(options.initCi.failOn);
558
+ return options;
559
+ }
423
560
  for (let index = 1; index < args.length; index += 1) {
424
561
  const arg = args[index];
425
562
  if (arg === "--json") {
@@ -456,6 +593,8 @@ function parseHealthArgs(args) {
456
593
  }
457
594
 
458
595
  export {
596
+ renderProjectHealthCiWorkflow,
597
+ writeProjectHealthCiWorkflow,
459
598
  createProjectHealthReport,
460
599
  formatProjectHealthText,
461
600
  formatProjectHealthMarkdown,