@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 +22 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-Y45MCRGI.js → chunk-FLZVSNB5.js} +19 -3
- package/dist/{chunk-DONMNPS7.js → chunk-K5KCZSEI.js} +141 -2
- package/dist/content-health-QQHBR6XG.js +1057 -0
- package/dist/{health-VSL4MROO.js → health-SIKAOE2Z.js} +7 -3
- package/dist/index.js +1 -1
- package/dist/{studio-BCTWKXFH.js → studio-EQSSNA6D.js} +1 -1
- package/package.json +5 -4
- package/src/templates/DECANTR.md.template +2 -1
- package/src/templates/decantr-health.workflow.yml.template +62 -0
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
|
@@ -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-
|
|
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-
|
|
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,
|