@decantr/cli 1.7.29 → 1.9.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
@@ -60,6 +60,8 @@ Brownfield analysis also writes `.decantr/doctrine-map.json`, a ranked source-pr
60
60
  - supports explicit workflow lanes: greenfield blueprint, greenfield contract-only, brownfield adoption, and hybrid composition
61
61
  - generates execution-pack context files for AI coding assistants
62
62
  - audits projects against Decantr contracts
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
63
65
  - searches the registry and showcase benchmark corpus
64
66
  - validates, refreshes, and maintains `decantr.essence.json`
65
67
 
@@ -77,10 +79,52 @@ decantr rules apply
77
79
  decantr magic "AI-native analytics workspace"
78
80
  decantr audit
79
81
  decantr check
82
+ decantr health --ci --fail-on error
83
+ decantr studio --port 4319 --host 127.0.0.1
84
+ decantr content-health --ci --fail-on error
80
85
  decantr registry summary --namespace @official --json
81
86
  decantr showcase verification --json
82
87
  ```
83
88
 
89
+ ## Project Health And Studio
90
+
91
+ `decantr health` is the local project observability command. It composes the existing verifier audit, guard checks, brownfield route drift checks, runtime evidence, and execution-pack files into a `ProjectHealthReport` with a status, score, route summary, pack summary, findings, and AI-ready remediation prompts.
92
+
93
+ ```bash
94
+ decantr health
95
+ decantr health --format json
96
+ decantr health --markdown --output health.md
97
+ decantr health --ci --fail-on error
98
+ decantr health --ci --fail-on warn
99
+ decantr health --prompt <finding-id>
100
+ ```
101
+
102
+ 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.
103
+
104
+ `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`.
105
+
106
+ ```bash
107
+ decantr studio
108
+ decantr studio --port 4319 --host 127.0.0.1
109
+ ```
110
+
111
+ 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.
112
+
113
+ ## Content Health
114
+
115
+ `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.
116
+
117
+ ```bash
118
+ decantr content-health
119
+ decantr content-health --json
120
+ decantr content-health --markdown --output content-health.md
121
+ decantr content-health --ci --fail-on error
122
+ decantr content-health --ci --fail-on warn
123
+ decantr content-health --prompt <finding-id>
124
+ ```
125
+
126
+ 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.
127
+
84
128
  ## Greenfield Certification
85
129
 
86
130
  Use the built-in certification harness before releases when you want to prove that representative blueprints still scaffold into runnable starter projects:
@@ -155,6 +199,8 @@ Recommended read order for AI-assisted scaffolding:
155
199
 
156
200
  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.
157
201
 
202
+ 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>`.
203
+
158
204
  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.
159
205
 
160
206
  ## Related Packages
package/dist/bin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-US6RK5QT.js";
3
- import "./chunk-HULA6E2D.js";
2
+ import "./chunk-PKJSI6IH.js";
3
+ import "./chunk-USOO77A5.js";
4
4
  import "./chunk-DI2PLOJ6.js";
@@ -0,0 +1,466 @@
1
+ import {
2
+ collectCheckIssues
3
+ } from "./chunk-RSDCWAHD.js";
4
+
5
+ // src/commands/health.ts
6
+ import { existsSync, readFileSync, writeFileSync } from "fs";
7
+ import { join } from "path";
8
+ import {
9
+ auditProject
10
+ } from "@decantr/verifier";
11
+ var BOLD = "\x1B[1m";
12
+ var DIM = "\x1B[2m";
13
+ var RESET = "\x1B[0m";
14
+ var RED = "\x1B[31m";
15
+ var GREEN = "\x1B[32m";
16
+ var CYAN = "\x1B[36m";
17
+ var YELLOW = "\x1B[33m";
18
+ var PROJECT_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v1.json";
19
+ function readProjectMetadata(projectRoot) {
20
+ const projectJsonPath = join(projectRoot, ".decantr", "project.json");
21
+ if (!existsSync(projectJsonPath)) {
22
+ return { workflowMode: null, adoptionMode: null, autoBrownfield: false };
23
+ }
24
+ try {
25
+ const data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
26
+ const workflowMode = typeof data.initialized?.workflowMode === "string" ? data.initialized.workflowMode : null;
27
+ const adoptionMode = typeof data.initialized?.adoptionMode === "string" ? data.initialized.adoptionMode : null;
28
+ return {
29
+ workflowMode,
30
+ adoptionMode,
31
+ autoBrownfield: workflowMode === "brownfield-attach"
32
+ };
33
+ } catch {
34
+ return { workflowMode: null, adoptionMode: null, autoBrownfield: false };
35
+ }
36
+ }
37
+ function collectDeclaredRoutes(essence) {
38
+ if (!essence || typeof essence !== "object") return [];
39
+ const record = essence;
40
+ const blueprint = record.blueprint;
41
+ if (!blueprint || typeof blueprint !== "object") return [];
42
+ const bp = blueprint;
43
+ const routes = /* @__PURE__ */ new Set();
44
+ if (bp.routes && typeof bp.routes === "object" && !Array.isArray(bp.routes)) {
45
+ for (const route of Object.keys(bp.routes)) {
46
+ routes.add(route);
47
+ }
48
+ }
49
+ const flatPages = Array.isArray(bp.pages) ? bp.pages : [];
50
+ for (const page of flatPages) {
51
+ if (page && typeof page === "object") {
52
+ const route = page.route;
53
+ if (typeof route === "string") routes.add(route);
54
+ }
55
+ }
56
+ const sections = Array.isArray(bp.sections) ? bp.sections : [];
57
+ for (const section of sections) {
58
+ if (!section || typeof section !== "object") continue;
59
+ const pages = section.pages;
60
+ if (!Array.isArray(pages)) continue;
61
+ for (const page of pages) {
62
+ if (page && typeof page === "object") {
63
+ const route = page.route;
64
+ if (typeof route === "string") routes.add(route);
65
+ }
66
+ }
67
+ }
68
+ return [...routes].sort();
69
+ }
70
+ function severityFromCheckIssue(issue) {
71
+ return issue.type === "error" ? "error" : "warn";
72
+ }
73
+ function sourceFromAuditFinding(finding) {
74
+ const category = finding.category.toLowerCase();
75
+ const id = finding.id.toLowerCase();
76
+ const rule = finding.rule?.toLowerCase() ?? "";
77
+ if (category.includes("runtime") || category.includes("document") || category.includes("performance")) {
78
+ return "runtime";
79
+ }
80
+ if (category.includes("pack") || category.includes("review contract")) {
81
+ return "pack";
82
+ }
83
+ if (category.includes("interaction") || id.includes("interaction") || rule.includes("interaction")) {
84
+ return "interaction";
85
+ }
86
+ return "audit";
87
+ }
88
+ function sourceFromCheckIssue(issue) {
89
+ if (issue.rule.startsWith("brownfield-")) return "brownfield";
90
+ if (issue.rule.includes("interaction")) return "interaction";
91
+ return "check";
92
+ }
93
+ function slugify(value) {
94
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
95
+ }
96
+ function commandsForFinding(source) {
97
+ switch (source) {
98
+ case "brownfield":
99
+ return ["decantr analyze", "decantr init --existing --merge-proposal", "decantr health"];
100
+ case "pack":
101
+ return ["decantr refresh", "decantr registry get-pack review --write-context", "decantr health"];
102
+ case "runtime":
103
+ return ["npm run build", "decantr health"];
104
+ case "interaction":
105
+ return ["decantr check --strict", "decantr health"];
106
+ case "check":
107
+ return ["decantr check", "decantr health"];
108
+ default:
109
+ return ["decantr audit", "decantr health"];
110
+ }
111
+ }
112
+ function buildRemediationPrompt(input) {
113
+ return [
114
+ "You are fixing one Decantr Project Health finding in this local workspace.",
115
+ "",
116
+ "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.",
117
+ "",
118
+ `Finding: ${input.id}`,
119
+ `Source: ${input.source}`,
120
+ `Severity: ${input.severity}`,
121
+ `Category: ${input.category}`,
122
+ `Message: ${input.message}`,
123
+ input.evidence.length > 0 ? `Evidence:
124
+ ${input.evidence.map((entry) => `- ${entry}`).join("\n")}` : null,
125
+ input.suggestedFix ? `Suggested fix: ${input.suggestedFix}` : null,
126
+ "",
127
+ "Make the smallest coherent code or contract change that resolves this finding. Preserve the existing framework, routing, styling system, and Decantr workflow mode unless the finding explicitly requires a contract update.",
128
+ "",
129
+ `After the fix, run:
130
+ ${input.commands.map((command) => `- ${command}`).join("\n")}`
131
+ ].filter((line) => Boolean(line)).join("\n");
132
+ }
133
+ function createHealthFinding(input) {
134
+ const idBase = input.baseId || input.rule || `${input.category}-${input.message}`;
135
+ const id = `${input.source}-${slugify(idBase)}`;
136
+ const commands = commandsForFinding(input.source);
137
+ const remediation = {
138
+ summary: input.suggestedFix || `Resolve ${input.category.toLowerCase()} finding.`,
139
+ commands,
140
+ prompt: buildRemediationPrompt({
141
+ id,
142
+ source: input.source,
143
+ category: input.category,
144
+ severity: input.severity,
145
+ message: input.message,
146
+ evidence: input.evidence ?? [],
147
+ suggestedFix: input.suggestedFix,
148
+ commands
149
+ })
150
+ };
151
+ return {
152
+ id,
153
+ source: input.source,
154
+ category: input.category,
155
+ severity: input.severity,
156
+ message: input.message,
157
+ evidence: input.evidence ?? [],
158
+ target: input.target,
159
+ file: input.file,
160
+ rule: input.rule,
161
+ suggestedFix: input.suggestedFix,
162
+ remediation
163
+ };
164
+ }
165
+ function countFindings(findings) {
166
+ return {
167
+ errorCount: findings.filter((finding) => finding.severity === "error").length,
168
+ warnCount: findings.filter((finding) => finding.severity === "warn").length,
169
+ infoCount: findings.filter((finding) => finding.severity === "info").length
170
+ };
171
+ }
172
+ function statusFromCounts(counts) {
173
+ if (counts.errorCount > 0) return "error";
174
+ if (counts.warnCount > 0) return "warning";
175
+ return "healthy";
176
+ }
177
+ function scoreFromCounts(counts) {
178
+ return Math.max(0, Math.min(100, 100 - counts.errorCount * 15 - counts.warnCount * 5 - counts.infoCount));
179
+ }
180
+ function routeIssuesFromFindings(findings) {
181
+ const issues = findings.filter(
182
+ (finding) => finding.category.toLowerCase().includes("route") || finding.rule?.toLowerCase().includes("route") || finding.id.toLowerCase().includes("route")
183
+ ).map((finding) => finding.message);
184
+ return [...new Set(issues)];
185
+ }
186
+ function isDuplicateFinding(existing, finding) {
187
+ const key = `${finding.rule ?? finding.id}|${finding.message}`;
188
+ if (existing.has(key)) return true;
189
+ existing.add(key);
190
+ return false;
191
+ }
192
+ async function createProjectHealthReport(projectRoot = process.cwd()) {
193
+ const metadata = readProjectMetadata(projectRoot);
194
+ const audit = await auditProject(projectRoot);
195
+ const findings = [];
196
+ const seen = /* @__PURE__ */ new Set();
197
+ for (const finding of audit.findings) {
198
+ const healthFinding = createHealthFinding({
199
+ source: sourceFromAuditFinding(finding),
200
+ category: finding.category,
201
+ severity: finding.severity,
202
+ message: finding.message,
203
+ evidence: finding.evidence,
204
+ target: finding.target,
205
+ file: finding.file,
206
+ rule: finding.rule,
207
+ suggestedFix: finding.suggestedFix,
208
+ baseId: finding.id
209
+ });
210
+ if (!isDuplicateFinding(seen, healthFinding)) findings.push(healthFinding);
211
+ }
212
+ try {
213
+ const check = collectCheckIssues(projectRoot, { brownfield: metadata.autoBrownfield });
214
+ for (const issue of check.issues) {
215
+ const source = sourceFromCheckIssue(issue);
216
+ const healthFinding = createHealthFinding({
217
+ source,
218
+ category: source === "brownfield" ? "Brownfield Drift" : "Contract Check",
219
+ severity: severityFromCheckIssue(issue),
220
+ message: issue.message,
221
+ evidence: [`Rule: ${issue.rule}`],
222
+ rule: issue.rule,
223
+ suggestedFix: issue.suggestion,
224
+ baseId: issue.rule
225
+ });
226
+ if (!isDuplicateFinding(seen, healthFinding)) findings.push(healthFinding);
227
+ }
228
+ } catch (e) {
229
+ const healthFinding = createHealthFinding({
230
+ source: "check",
231
+ category: "Contract Check",
232
+ severity: "error",
233
+ message: `Decantr check could not complete: ${e.message}`,
234
+ evidence: ["The health command could not run the local check pass."],
235
+ rule: "check-failed",
236
+ suggestedFix: "Repair the local Decantr contract and rerun `decantr health`.",
237
+ baseId: "check-failed"
238
+ });
239
+ if (!isDuplicateFinding(seen, healthFinding)) findings.push(healthFinding);
240
+ }
241
+ if (!audit.valid && findings.every((finding) => finding.severity !== "error")) {
242
+ findings.push(
243
+ createHealthFinding({
244
+ source: "audit",
245
+ category: "Project Contract",
246
+ severity: "error",
247
+ message: "Project audit is not valid.",
248
+ evidence: ["The verifier returned valid=false."],
249
+ rule: "project-audit-invalid",
250
+ suggestedFix: "Resolve blocking audit findings and rerun `decantr health`."
251
+ })
252
+ );
253
+ }
254
+ const counts = countFindings(findings);
255
+ const declaredRoutes = collectDeclaredRoutes(audit.essence);
256
+ const manifest = audit.packManifest;
257
+ return {
258
+ $schema: PROJECT_HEALTH_SCHEMA_URL,
259
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
260
+ projectRoot,
261
+ status: statusFromCounts(counts),
262
+ score: scoreFromCounts(counts),
263
+ summary: {
264
+ ...counts,
265
+ findingCount: findings.length,
266
+ workflowMode: metadata.workflowMode,
267
+ adoptionMode: metadata.adoptionMode,
268
+ essenceVersion: audit.summary.essenceVersion,
269
+ pageCount: audit.summary.pageCount,
270
+ runtimeAuditChecked: audit.summary.runtimeAuditChecked,
271
+ runtimePassed: audit.summary.runtimePassed,
272
+ packManifestPresent: audit.summary.packManifestPresent,
273
+ reviewPackPresent: audit.summary.reviewPackPresent
274
+ },
275
+ routes: {
276
+ declared: declaredRoutes,
277
+ runtimeChecked: audit.runtimeAudit.routeHintsChecked,
278
+ runtimeMatched: audit.runtimeAudit.routeHintsMatched,
279
+ runtimeCoverageOk: audit.summary.runtimeAuditChecked ? audit.runtimeAudit.routeHintsCoverageOk : null,
280
+ issues: routeIssuesFromFindings(findings)
281
+ },
282
+ packs: {
283
+ manifestPresent: Boolean(manifest),
284
+ reviewPackPresent: Boolean(manifest?.review ?? audit.reviewPack),
285
+ scaffoldPackPresent: Boolean(manifest?.scaffold),
286
+ sectionPackCount: manifest?.sections.length ?? 0,
287
+ pagePackCount: manifest?.pages.length ?? 0,
288
+ mutationPackCount: manifest?.mutations?.length ?? 0,
289
+ generatedAt: typeof manifest?.generatedAt === "string" ? manifest.generatedAt : null
290
+ },
291
+ ci: {
292
+ recommendedCommand: "decantr health --ci --fail-on error",
293
+ failOn: "error"
294
+ },
295
+ findings
296
+ };
297
+ }
298
+ function colorForStatus(status) {
299
+ if (status === "healthy") return GREEN;
300
+ if (status === "warning") return YELLOW;
301
+ return RED;
302
+ }
303
+ function formatProjectHealthText(report) {
304
+ const color = colorForStatus(report.status);
305
+ const lines = [
306
+ `${BOLD}Decantr Project Health${RESET}`,
307
+ "",
308
+ `${color}${report.status.toUpperCase()}${RESET} score ${report.score}/100`,
309
+ `${DIM}${report.projectRoot}${RESET}`,
310
+ "",
311
+ `${BOLD}Summary:${RESET}`,
312
+ ` Findings: ${report.summary.errorCount} error(s), ${report.summary.warnCount} warn(s), ${report.summary.infoCount} info`,
313
+ ` Essence: ${report.summary.essenceVersion ?? "missing"} | pages ${report.summary.pageCount}`,
314
+ ` Workflow: ${report.summary.workflowMode ?? "unknown"} | adoption ${report.summary.adoptionMode ?? "unknown"}`,
315
+ ` Runtime audit: ${report.summary.runtimeAuditChecked ? report.summary.runtimePassed ? "passed" : "failed" : "not checked"}`,
316
+ ` Packs: manifest ${report.packs.manifestPresent ? "present" : "missing"} | review ${report.packs.reviewPackPresent ? "present" : "missing"} | pages ${report.packs.pagePackCount}`,
317
+ "",
318
+ `${BOLD}Findings:${RESET}`
319
+ ];
320
+ if (report.findings.length === 0) {
321
+ lines.push(` ${GREEN}No findings. Project is healthy.${RESET}`);
322
+ } else {
323
+ for (const finding of report.findings) {
324
+ const findingColor = finding.severity === "error" ? RED : finding.severity === "warn" ? YELLOW : CYAN;
325
+ lines.push(
326
+ ` ${findingColor}[${finding.severity.toUpperCase()}]${RESET} ${finding.id}: ${finding.message}`
327
+ );
328
+ if (finding.evidence.length > 0) {
329
+ lines.push(` ${DIM}${finding.evidence[0]}${RESET}`);
330
+ }
331
+ if (finding.suggestedFix) {
332
+ lines.push(` ${DIM}Fix: ${finding.suggestedFix}${RESET}`);
333
+ }
334
+ lines.push(` ${DIM}Prompt: decantr health --prompt ${finding.id}${RESET}`);
335
+ }
336
+ }
337
+ lines.push("");
338
+ lines.push(`${BOLD}CI:${RESET} ${report.ci.recommendedCommand}`);
339
+ return `${lines.join("\n")}
340
+ `;
341
+ }
342
+ function formatProjectHealthMarkdown(report) {
343
+ const lines = [
344
+ "# Decantr Project Health",
345
+ "",
346
+ `- Status: **${report.status}**`,
347
+ `- Score: **${report.score}/100**`,
348
+ `- Project: \`${report.projectRoot}\``,
349
+ `- Findings: ${report.summary.errorCount} error(s), ${report.summary.warnCount} warn(s), ${report.summary.infoCount} info`,
350
+ `- Runtime audit: ${report.summary.runtimeAuditChecked ? report.summary.runtimePassed ? "passed" : "failed" : "not checked"}`,
351
+ `- Packs: manifest ${report.packs.manifestPresent ? "present" : "missing"}, review ${report.packs.reviewPackPresent ? "present" : "missing"}`,
352
+ "",
353
+ "## Findings",
354
+ ""
355
+ ];
356
+ if (report.findings.length === 0) {
357
+ lines.push("No findings. Project is healthy.");
358
+ } else {
359
+ for (const finding of report.findings) {
360
+ lines.push(`### ${finding.id}`);
361
+ lines.push("");
362
+ lines.push(`- Severity: ${finding.severity}`);
363
+ lines.push(`- Source: ${finding.source}`);
364
+ lines.push(`- Category: ${finding.category}`);
365
+ lines.push(`- Message: ${finding.message}`);
366
+ if (finding.suggestedFix) lines.push(`- Fix: ${finding.suggestedFix}`);
367
+ if (finding.evidence.length > 0) {
368
+ lines.push("- Evidence:");
369
+ for (const evidence of finding.evidence) lines.push(` - ${evidence}`);
370
+ }
371
+ lines.push(`- Prompt: \`decantr health --prompt ${finding.id}\``);
372
+ lines.push("");
373
+ }
374
+ }
375
+ lines.push("## CI");
376
+ lines.push("");
377
+ lines.push(`\`${report.ci.recommendedCommand}\``);
378
+ return `${lines.join("\n")}
379
+ `;
380
+ }
381
+ function formatProjectHealthJson(report) {
382
+ return `${JSON.stringify(report, null, 2)}
383
+ `;
384
+ }
385
+ function resolveFormat(options) {
386
+ if (options.json) return "json";
387
+ if (options.markdown) return "markdown";
388
+ return options.format ?? "text";
389
+ }
390
+ function shouldFailHealth(report, failOn) {
391
+ if (failOn === "none") return false;
392
+ if (failOn === "warn") return report.summary.errorCount > 0 || report.summary.warnCount > 0;
393
+ return report.summary.errorCount > 0;
394
+ }
395
+ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
396
+ const report = await createProjectHealthReport(projectRoot);
397
+ if (options.promptId) {
398
+ const finding = report.findings.find((entry) => entry.id === options.promptId);
399
+ if (!finding) {
400
+ console.error(`${RED}No health finding found for id: ${options.promptId}${RESET}`);
401
+ process.exitCode = 1;
402
+ return;
403
+ }
404
+ console.log(finding.remediation.prompt);
405
+ return;
406
+ }
407
+ const format = resolveFormat(options);
408
+ const payload = format === "json" ? formatProjectHealthJson(report) : format === "markdown" ? formatProjectHealthMarkdown(report) : formatProjectHealthText(report);
409
+ if (options.output) {
410
+ writeFileSync(options.output, payload, "utf-8");
411
+ if (!options.ci) {
412
+ console.log(`${GREEN}Wrote Decantr health report:${RESET} ${options.output}`);
413
+ }
414
+ } else {
415
+ process.stdout.write(payload);
416
+ }
417
+ if (options.ci && shouldFailHealth(report, options.failOn ?? "error")) {
418
+ process.exitCode = 1;
419
+ }
420
+ }
421
+ function parseHealthArgs(args) {
422
+ const options = {};
423
+ for (let index = 1; index < args.length; index += 1) {
424
+ const arg = args[index];
425
+ if (arg === "--json") {
426
+ options.json = true;
427
+ } else if (arg === "--markdown") {
428
+ options.markdown = true;
429
+ } else if (arg === "--ci") {
430
+ options.ci = true;
431
+ } else if (arg === "--format" && args[index + 1]) {
432
+ options.format = args[++index];
433
+ } else if (arg.startsWith("--format=")) {
434
+ options.format = arg.split("=")[1];
435
+ } else if (arg === "--output" && args[index + 1]) {
436
+ options.output = args[++index];
437
+ } else if (arg.startsWith("--output=")) {
438
+ options.output = arg.split("=")[1];
439
+ } else if (arg === "--fail-on" && args[index + 1]) {
440
+ options.failOn = args[++index];
441
+ } else if (arg.startsWith("--fail-on=")) {
442
+ options.failOn = arg.split("=")[1];
443
+ } else if (arg === "--prompt" && args[index + 1]) {
444
+ options.promptId = args[++index];
445
+ } else if (arg.startsWith("--prompt=")) {
446
+ options.promptId = arg.split("=")[1];
447
+ }
448
+ }
449
+ if (options.format && !["text", "json", "markdown"].includes(options.format)) {
450
+ throw new Error("Invalid --format value. Use text, json, or markdown.");
451
+ }
452
+ if (options.failOn && !["error", "warn", "none"].includes(options.failOn)) {
453
+ throw new Error("Invalid --fail-on value. Use error, warn, or none.");
454
+ }
455
+ return options;
456
+ }
457
+
458
+ export {
459
+ createProjectHealthReport,
460
+ formatProjectHealthText,
461
+ formatProjectHealthMarkdown,
462
+ formatProjectHealthJson,
463
+ shouldFailHealth,
464
+ cmdHealth,
465
+ parseHealthArgs
466
+ };
@@ -14,7 +14,7 @@ import {
14
14
  scaffoldProject,
15
15
  syncRegistry,
16
16
  writeExecutionPackBundleArtifacts
17
- } from "./chunk-HULA6E2D.js";
17
+ } from "./chunk-USOO77A5.js";
18
18
  import {
19
19
  buildGuardRegistryContext,
20
20
  createDoctrineMap,
@@ -6480,6 +6480,9 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
6480
6480
  console.log("");
6481
6481
  console.log(" Commands:");
6482
6482
  console.log(` ${cyan3("decantr status")} Project health`);
6483
+ console.log(` ${cyan3("decantr health")} Contract health report`);
6484
+ console.log(` ${cyan3("decantr content-health")} Registry content health report`);
6485
+ console.log(` ${cyan3("decantr studio")} Local health dashboard`);
6483
6486
  console.log(` ${cyan3("decantr search")} Search registry`);
6484
6487
  console.log(` ${cyan3("decantr get")} Fetch content details`);
6485
6488
  console.log(` ${cyan3("decantr validate")} Check essence file`);
@@ -6924,6 +6927,7 @@ ${BOLD6}Usage:${RESET13}
6924
6927
  decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
6925
6928
  decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
6926
6929
  decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
6930
+ decantr content-health [--json] [--markdown] [--ci]
6927
6931
  decantr rules preview [--project=<path>]
6928
6932
  decantr rules apply [--project=<path>]
6929
6933
  decantr validate [path]
@@ -6961,6 +6965,9 @@ ${BOLD6}Commands:${RESET13}
6961
6965
  ${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
6962
6966
  ${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
6963
6967
  ${cyan3("status")} Show project status, DNA axioms, and blueprint info
6968
+ ${cyan3("health")} Generate a local Project Health report [--json] [--markdown] [--ci]
6969
+ ${cyan3("content-health")} Generate a local registry content health report [--json] [--markdown] [--ci]
6970
+ ${cyan3("studio")} Open a local Project Health dashboard backed by the same report
6964
6971
  ${cyan3("sync")} Sync registry content from API
6965
6972
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
6966
6973
  ${cyan3("migrate")} Migrate v2 essence to v3 format (with .v2.backup.json backup)
@@ -6997,6 +7004,10 @@ ${BOLD6}Examples:${RESET13}
6997
7004
  decantr rules preview
6998
7005
  decantr rules apply
6999
7006
  decantr status
7007
+ decantr health
7008
+ decantr health --ci --fail-on error
7009
+ decantr content-health --ci --fail-on error
7010
+ decantr studio
7000
7011
  decantr audit
7001
7012
  decantr audit src/pages/HomePage.tsx
7002
7013
  decantr migrate
@@ -7157,7 +7168,7 @@ async function main() {
7157
7168
  break;
7158
7169
  }
7159
7170
  case "upgrade": {
7160
- const { cmdUpgrade } = await import("./upgrade-EV23CKA3.js");
7171
+ const { cmdUpgrade } = await import("./upgrade-4NRDVD5N.js");
7161
7172
  const applyFlag = args.includes("--apply");
7162
7173
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
7163
7174
  break;
@@ -7169,12 +7180,42 @@ async function main() {
7169
7180
  `${YELLOW9}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET13}`
7170
7181
  );
7171
7182
  }
7172
- const { cmdHeal } = await import("./heal-YHLXO5QL.js");
7183
+ const { cmdHeal } = await import("./heal-5JHGCLDX.js");
7173
7184
  const telemetryFlag = args.includes("--telemetry");
7174
7185
  const brownfieldFlag = args.includes("--brownfield");
7175
7186
  await cmdHeal(process.cwd(), { telemetry: telemetryFlag, brownfield: brownfieldFlag });
7176
7187
  break;
7177
7188
  }
7189
+ case "health": {
7190
+ try {
7191
+ const { cmdHealth, parseHealthArgs } = await import("./health-VSL4MROO.js");
7192
+ await cmdHealth(process.cwd(), parseHealthArgs(args));
7193
+ } catch (e) {
7194
+ console.error(error3(e.message));
7195
+ process.exitCode = 1;
7196
+ }
7197
+ break;
7198
+ }
7199
+ case "content-health": {
7200
+ try {
7201
+ const { cmdContentHealth, parseContentHealthArgs } = await import("./content-health-QQHBR6XG.js");
7202
+ await cmdContentHealth(process.cwd(), parseContentHealthArgs(args));
7203
+ } catch (e) {
7204
+ console.error(error3(e.message));
7205
+ process.exitCode = 1;
7206
+ }
7207
+ break;
7208
+ }
7209
+ case "studio": {
7210
+ try {
7211
+ const { cmdStudio, parseStudioArgs } = await import("./studio-BCTWKXFH.js");
7212
+ await cmdStudio(process.cwd(), parseStudioArgs(args));
7213
+ } catch (e) {
7214
+ console.error(error3(e.message));
7215
+ process.exitCode = 1;
7216
+ }
7217
+ break;
7218
+ }
7178
7219
  case "migrate": {
7179
7220
  await cmdMigrate(process.cwd());
7180
7221
  break;