@decantr/cli 1.9.0 → 1.11.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 +6 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-PKJSI6IH.js → chunk-5RODH77L.js} +6 -3
- package/dist/{chunk-DONMNPS7.js → chunk-6YCFRZZI.js} +180 -2
- package/dist/{health-VSL4MROO.js → health-3TJYYTX6.js} +7 -3
- package/dist/index.js +1 -1
- package/dist/{studio-BCTWKXFH.js → studio-7TE7YXFG.js} +1 -1
- package/package.json +5 -5
- package/src/templates/DECANTR.md.template +2 -1
- package/src/templates/decantr-health.workflow.yml.template +62 -0
package/README.md
CHANGED
|
@@ -97,10 +97,15 @@ decantr health --markdown --output health.md
|
|
|
97
97
|
decantr health --ci --fail-on error
|
|
98
98
|
decantr health --ci --fail-on warn
|
|
99
99
|
decantr health --prompt <finding-id>
|
|
100
|
+
decantr health init-ci
|
|
101
|
+
decantr health init-ci --fail-on warn --cli-version latest --force
|
|
102
|
+
decantr health init-ci --project apps/registry
|
|
100
103
|
```
|
|
101
104
|
|
|
102
105
|
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
106
|
|
|
107
|
+
`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. In monorepos, add `--project <path>` from the repository root; dependency install stays at the root while health runs inside the app contract and uploads artifacts from that project path.
|
|
108
|
+
|
|
104
109
|
`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
110
|
|
|
106
111
|
```bash
|
|
@@ -199,7 +204,7 @@ Recommended read order for AI-assisted scaffolding:
|
|
|
199
204
|
|
|
200
205
|
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.
|
|
201
206
|
|
|
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>`.
|
|
207
|
+
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>`.
|
|
203
208
|
|
|
204
209
|
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.
|
|
205
210
|
|
package/dist/bin.js
CHANGED
|
@@ -6927,6 +6927,7 @@ ${BOLD6}Usage:${RESET13}
|
|
|
6927
6927
|
decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
|
|
6928
6928
|
decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
|
|
6929
6929
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
6930
|
+
decantr health init-ci [--force] [--project <path>] [--fail-on <error|warn|none>] [--cli-version <version|latest>]
|
|
6930
6931
|
decantr content-health [--json] [--markdown] [--ci]
|
|
6931
6932
|
decantr rules preview [--project=<path>]
|
|
6932
6933
|
decantr rules apply [--project=<path>]
|
|
@@ -6965,7 +6966,7 @@ ${BOLD6}Commands:${RESET13}
|
|
|
6965
6966
|
${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
|
|
6966
6967
|
${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
|
|
6967
6968
|
${cyan3("status")} Show project status, DNA axioms, and blueprint info
|
|
6968
|
-
${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
|
|
6969
6970
|
${cyan3("content-health")} Generate a local registry content health report [--json] [--markdown] [--ci]
|
|
6970
6971
|
${cyan3("studio")} Open a local Project Health dashboard backed by the same report
|
|
6971
6972
|
${cyan3("sync")} Sync registry content from API
|
|
@@ -7005,6 +7006,8 @@ ${BOLD6}Examples:${RESET13}
|
|
|
7005
7006
|
decantr rules apply
|
|
7006
7007
|
decantr status
|
|
7007
7008
|
decantr health
|
|
7009
|
+
decantr health init-ci
|
|
7010
|
+
decantr health init-ci --project apps/web
|
|
7008
7011
|
decantr health --ci --fail-on error
|
|
7009
7012
|
decantr content-health --ci --fail-on error
|
|
7010
7013
|
decantr studio
|
|
@@ -7188,7 +7191,7 @@ async function main() {
|
|
|
7188
7191
|
}
|
|
7189
7192
|
case "health": {
|
|
7190
7193
|
try {
|
|
7191
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
7194
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-3TJYYTX6.js");
|
|
7192
7195
|
await cmdHealth(process.cwd(), parseHealthArgs(args));
|
|
7193
7196
|
} catch (e) {
|
|
7194
7197
|
console.error(error3(e.message));
|
|
@@ -7208,7 +7211,7 @@ async function main() {
|
|
|
7208
7211
|
}
|
|
7209
7212
|
case "studio": {
|
|
7210
7213
|
try {
|
|
7211
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
7214
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-7TE7YXFG.js");
|
|
7212
7215
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
7213
7216
|
} catch (e) {
|
|
7214
7217
|
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,122 @@ 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 validateProjectPath(value) {
|
|
96
|
+
if (value === void 0) return void 0;
|
|
97
|
+
const raw = value.trim();
|
|
98
|
+
if (!raw || raw === ".") return void 0;
|
|
99
|
+
const normalized = raw.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
100
|
+
if (!normalized || normalized.startsWith("/") || normalized.startsWith("-") || normalized.includes("..") || normalized.includes("\\") || /\s/.test(normalized) || !/^[A-Za-z0-9._@/-]+$/.test(normalized)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"Invalid --project value. Use a relative project path without spaces or parent-directory segments."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const segments = normalized.split("/");
|
|
106
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"Invalid --project value. Use a relative project path without empty or parent-directory segments."
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return normalized;
|
|
112
|
+
}
|
|
113
|
+
function prefixArtifactPath(projectPath, artifactPath) {
|
|
114
|
+
return projectPath ? `${projectPath}/${artifactPath}` : artifactPath;
|
|
115
|
+
}
|
|
116
|
+
function renderProjectHealthCiWorkflow(options = {}) {
|
|
117
|
+
const failOn = normalizeHealthFailOn(options.failOn);
|
|
118
|
+
const projectPath = validateProjectPath(options.projectPath);
|
|
119
|
+
const reportPath = validateArtifactPath(
|
|
120
|
+
options.reportPath || DEFAULT_HEALTH_CI_REPORT_PATH,
|
|
121
|
+
"--report-path"
|
|
122
|
+
);
|
|
123
|
+
const jsonPath = validateArtifactPath(options.jsonPath || DEFAULT_HEALTH_CI_JSON_PATH, "--json-path");
|
|
124
|
+
const template = loadHealthTemplate("decantr-health.workflow.yml.template");
|
|
125
|
+
return renderTemplate(template, {
|
|
126
|
+
CLI_PACKAGE: normalizeCliPackageSpecifier(options.cliVersion),
|
|
127
|
+
FAIL_ON: failOn,
|
|
128
|
+
PROJECT_WORKING_DIRECTORY: projectPath ? ` working-directory: ${projectPath}
|
|
129
|
+
` : "",
|
|
130
|
+
REPORT_PATH: reportPath,
|
|
131
|
+
JSON_PATH: jsonPath,
|
|
132
|
+
REPORT_ARTIFACT_PATH: prefixArtifactPath(projectPath, reportPath),
|
|
133
|
+
JSON_ARTIFACT_PATH: prefixArtifactPath(projectPath, jsonPath)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function writeProjectHealthCiWorkflow(projectRoot, options = {}) {
|
|
137
|
+
const workflowRelativePath = validateWorkflowPath(
|
|
138
|
+
options.workflowPath || DEFAULT_HEALTH_CI_WORKFLOW_PATH
|
|
139
|
+
);
|
|
140
|
+
const workflowPath = join(projectRoot, workflowRelativePath);
|
|
141
|
+
const alreadyExists = existsSync(workflowPath);
|
|
142
|
+
if (alreadyExists && !options.force) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`${workflowRelativePath} already exists. Re-run with --force to replace it, or use --workflow-path <file>.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
mkdirSync(dirname(workflowPath), { recursive: true });
|
|
148
|
+
writeFileSync(workflowPath, renderProjectHealthCiWorkflow(options), "utf-8");
|
|
149
|
+
const projectPath = validateProjectPath(options.projectPath);
|
|
150
|
+
const result = {
|
|
151
|
+
path: workflowRelativePath,
|
|
152
|
+
created: !alreadyExists,
|
|
153
|
+
cliPackage: normalizeCliPackageSpecifier(options.cliVersion),
|
|
154
|
+
failOn: normalizeHealthFailOn(options.failOn)
|
|
155
|
+
};
|
|
156
|
+
if (projectPath) result.projectPath = projectPath;
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
37
159
|
function collectDeclaredRoutes(essence) {
|
|
38
160
|
if (!essence || typeof essence !== "object") return [];
|
|
39
161
|
const record = essence;
|
|
@@ -393,6 +515,22 @@ function shouldFailHealth(report, failOn) {
|
|
|
393
515
|
return report.summary.errorCount > 0;
|
|
394
516
|
}
|
|
395
517
|
async function cmdHealth(projectRoot = process.cwd(), options = {}) {
|
|
518
|
+
if (options.initCi) {
|
|
519
|
+
try {
|
|
520
|
+
const result = writeProjectHealthCiWorkflow(projectRoot, options.initCi);
|
|
521
|
+
const action = result.created ? "Created" : "Updated";
|
|
522
|
+
console.log(`${GREEN}${action} Decantr Project Health workflow:${RESET} ${result.path}`);
|
|
523
|
+
console.log(`${DIM}CLI package: ${result.cliPackage}${RESET}`);
|
|
524
|
+
if (result.projectPath) {
|
|
525
|
+
console.log(`${DIM}Project: ${result.projectPath}${RESET}`);
|
|
526
|
+
}
|
|
527
|
+
console.log(`${DIM}CI gate: decantr health --ci --fail-on ${result.failOn}${RESET}`);
|
|
528
|
+
} catch (e) {
|
|
529
|
+
console.error(`${RED}${e.message}${RESET}`);
|
|
530
|
+
process.exitCode = 1;
|
|
531
|
+
}
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
396
534
|
const report = await createProjectHealthReport(projectRoot);
|
|
397
535
|
if (options.promptId) {
|
|
398
536
|
const finding = report.findings.find((entry) => entry.id === options.promptId);
|
|
@@ -420,6 +558,44 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
|
|
|
420
558
|
}
|
|
421
559
|
function parseHealthArgs(args) {
|
|
422
560
|
const options = {};
|
|
561
|
+
if (args[1] === "init-ci") {
|
|
562
|
+
options.initCi = {};
|
|
563
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
564
|
+
const arg = args[index];
|
|
565
|
+
if (arg === "--force") {
|
|
566
|
+
options.initCi.force = true;
|
|
567
|
+
} else if (arg === "--fail-on" && args[index + 1]) {
|
|
568
|
+
options.initCi.failOn = args[++index];
|
|
569
|
+
} else if (arg.startsWith("--fail-on=")) {
|
|
570
|
+
options.initCi.failOn = arg.split("=")[1];
|
|
571
|
+
} else if ((arg === "--cli-version" || arg === "--cli") && args[index + 1]) {
|
|
572
|
+
options.initCi.cliVersion = args[++index];
|
|
573
|
+
} else if (arg.startsWith("--cli-version=")) {
|
|
574
|
+
options.initCi.cliVersion = arg.split("=")[1];
|
|
575
|
+
} else if (arg.startsWith("--cli=")) {
|
|
576
|
+
options.initCi.cliVersion = arg.split("=")[1];
|
|
577
|
+
} else if (arg === "--workflow-path" && args[index + 1]) {
|
|
578
|
+
options.initCi.workflowPath = args[++index];
|
|
579
|
+
} else if (arg.startsWith("--workflow-path=")) {
|
|
580
|
+
options.initCi.workflowPath = arg.split("=")[1];
|
|
581
|
+
} else if (arg === "--report-path" && args[index + 1]) {
|
|
582
|
+
options.initCi.reportPath = args[++index];
|
|
583
|
+
} else if (arg.startsWith("--report-path=")) {
|
|
584
|
+
options.initCi.reportPath = arg.split("=")[1];
|
|
585
|
+
} else if (arg === "--json-path" && args[index + 1]) {
|
|
586
|
+
options.initCi.jsonPath = args[++index];
|
|
587
|
+
} else if (arg.startsWith("--json-path=")) {
|
|
588
|
+
options.initCi.jsonPath = arg.split("=")[1];
|
|
589
|
+
} else if (arg === "--project" && args[index + 1]) {
|
|
590
|
+
options.initCi.projectPath = args[++index];
|
|
591
|
+
} else if (arg.startsWith("--project=")) {
|
|
592
|
+
options.initCi.projectPath = arg.split("=")[1];
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
normalizeHealthFailOn(options.initCi.failOn);
|
|
596
|
+
validateProjectPath(options.initCi.projectPath);
|
|
597
|
+
return options;
|
|
598
|
+
}
|
|
423
599
|
for (let index = 1; index < args.length; index += 1) {
|
|
424
600
|
const arg = args[index];
|
|
425
601
|
if (arg === "--json") {
|
|
@@ -456,6 +632,8 @@ function parseHealthArgs(args) {
|
|
|
456
632
|
}
|
|
457
633
|
|
|
458
634
|
export {
|
|
635
|
+
renderProjectHealthCiWorkflow,
|
|
636
|
+
writeProjectHealthCiWorkflow,
|
|
459
637
|
createProjectHealthReport,
|
|
460
638
|
formatProjectHealthText,
|
|
461
639
|
formatProjectHealthMarkdown,
|
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
formatProjectHealthMarkdown,
|
|
6
6
|
formatProjectHealthText,
|
|
7
7
|
parseHealthArgs,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
renderProjectHealthCiWorkflow,
|
|
9
|
+
shouldFailHealth,
|
|
10
|
+
writeProjectHealthCiWorkflow
|
|
11
|
+
} from "./chunk-6YCFRZZI.js";
|
|
10
12
|
import "./chunk-RSDCWAHD.js";
|
|
11
13
|
import "./chunk-DI2PLOJ6.js";
|
|
12
14
|
export {
|
|
@@ -16,5 +18,7 @@ export {
|
|
|
16
18
|
formatProjectHealthMarkdown,
|
|
17
19
|
formatProjectHealthText,
|
|
18
20
|
parseHealthArgs,
|
|
19
|
-
|
|
21
|
+
renderProjectHealthCiWorkflow,
|
|
22
|
+
shouldFailHealth,
|
|
23
|
+
writeProjectHealthCiWorkflow
|
|
20
24
|
};
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Decantr CLI - scaffold, audit, inspect Project Health, and maintain Decantr projects from the terminal",
|
|
5
5
|
"author": "Decantr AI",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"ajv": "^8.
|
|
33
|
+
"ajv": "^8.20.0",
|
|
34
34
|
"@decantr/core": "1.0.6",
|
|
35
|
-
"@decantr/essence-spec": "1.0.7",
|
|
36
35
|
"@decantr/registry": "1.1.0",
|
|
37
|
-
"@decantr/
|
|
38
|
-
"@decantr/
|
|
36
|
+
"@decantr/telemetry": "0.1.2",
|
|
37
|
+
"@decantr/verifier": "1.1.1",
|
|
38
|
+
"@decantr/essence-spec": "1.0.8"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup",
|
|
@@ -115,7 +115,7 @@ Read `.decantr/context/page-{name}-pack.md` for the most local compiled route co
|
|
|
115
115
|
### Validation
|
|
116
116
|
|
|
117
117
|
Run `decantr check` to detect drift violations while editing and `decantr audit` to audit the whole project contract after implementation.
|
|
118
|
-
Run `decantr health` for the broader Project Health view before handoff, pull requests, or CI. Use `decantr health --prompt <finding-id>` to generate a scoped remediation prompt for a specific issue, and `decantr studio` to inspect local drift, routes, findings, remediation, CI, and pack state in a localhost dashboard.
|
|
118
|
+
Run `decantr health` for the broader Project Health view before handoff, pull requests, or CI. Use `decantr health init-ci` to install the default GitHub Actions health gate, `decantr health --prompt <finding-id>` to generate a scoped remediation prompt for a specific issue, and `decantr studio` to inspect local drift, routes, findings, remediation, CI, and pack state in a localhost dashboard.
|
|
119
119
|
Declared command palettes and hotkeys must be implemented, not merely acknowledged.
|
|
120
120
|
|
|
121
121
|
### Quick Commands
|
|
@@ -123,6 +123,7 @@ Declared command palettes and hotkeys must be implemented, not merely acknowledg
|
|
|
123
123
|
```bash
|
|
124
124
|
decantr status # Project status overview
|
|
125
125
|
decantr health # Local contract health report
|
|
126
|
+
decantr health init-ci # Install GitHub Actions health gate
|
|
126
127
|
decantr studio # Local health dashboard
|
|
127
128
|
decantr check # Detect drift violations
|
|
128
129
|
decantr get pattern X # Fetch a pattern spec from registry
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Decantr Project Health
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
health:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-node@v6
|
|
20
|
+
with:
|
|
21
|
+
node-version: '22'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
shell: bash
|
|
25
|
+
run: |
|
|
26
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
27
|
+
corepack enable
|
|
28
|
+
pnpm install --frozen-lockfile
|
|
29
|
+
elif [ -f package-lock.json ]; then
|
|
30
|
+
npm ci
|
|
31
|
+
elif [ -f yarn.lock ]; then
|
|
32
|
+
corepack enable
|
|
33
|
+
yarn install --frozen-lockfile
|
|
34
|
+
elif [ -f package.json ]; then
|
|
35
|
+
npm install
|
|
36
|
+
else
|
|
37
|
+
echo "No package manifest found; skipping dependency install."
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
- name: Generate Decantr health JSON
|
|
41
|
+
{{PROJECT_WORKING_DIRECTORY}} run: npx --yes {{CLI_PACKAGE}} health --json --output {{JSON_PATH}}
|
|
42
|
+
|
|
43
|
+
- name: Audit Decantr health
|
|
44
|
+
{{PROJECT_WORKING_DIRECTORY}} run: npx --yes {{CLI_PACKAGE}} health --ci --fail-on {{FAIL_ON}} --markdown --output {{REPORT_PATH}}
|
|
45
|
+
|
|
46
|
+
- name: Publish health summary
|
|
47
|
+
if: always()
|
|
48
|
+
{{PROJECT_WORKING_DIRECTORY}} shell: bash
|
|
49
|
+
run: |
|
|
50
|
+
if [ -f {{REPORT_PATH}} ]; then
|
|
51
|
+
cat {{REPORT_PATH}} >> "$GITHUB_STEP_SUMMARY"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
- name: Upload health artifacts
|
|
55
|
+
if: always()
|
|
56
|
+
uses: actions/upload-artifact@v6
|
|
57
|
+
with:
|
|
58
|
+
name: decantr-project-health
|
|
59
|
+
path: |
|
|
60
|
+
{{JSON_ARTIFACT_PATH}}
|
|
61
|
+
{{REPORT_ARTIFACT_PATH}}
|
|
62
|
+
if-no-files-found: ignore
|