@sdd-method/verify-cli 0.1.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/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +7 -0
- package/dist/lib/catalogue/baseline-check.js +136 -0
- package/dist/lib/catalogue/canonical-schema.yaml +3557 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +522 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/engine-validator.js +222 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +329 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +422 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +764 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +535 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/validate.js +176 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +413 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +25 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/validate/aggregate.js +35 -0
- package/dist/lib/validate/bundle-coherence.js +69 -0
- package/dist/lib/validate/dispatch.js +218 -0
- package/dist/lib/validate/index.js +88 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/validate.js +22 -0
- package/dist/verify-bin.js +8 -0
- package/dist/verify-program.js +115 -0
- package/package.json +32 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { checkBundleCoherence } from "./bundle-coherence.js";
|
|
2
|
+
// Validators that accept --app-root / --repo-root flags work fine from the
|
|
3
|
+
// bundled location because they override their internal ROOT_DIR via the
|
|
4
|
+
// flag. Validators without that flag (adr-structure, runway-classification,
|
|
5
|
+
// spec-lifecycle) compute ROOT_DIR as SCRIPT_DIR/../.. — so we must run the
|
|
6
|
+
// copy that sync placed inside the target repo, not the bundled sdd-cli copy.
|
|
7
|
+
const APP_VALIDATORS = [
|
|
8
|
+
{
|
|
9
|
+
name: "app-docs",
|
|
10
|
+
source: { kind: "bundled", script: "validate-app-docs.sh" },
|
|
11
|
+
build: (ctx) => ({ status: "run", args: ["--app-root", ctx.targetRepo] }),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "app-dependencies",
|
|
15
|
+
source: { kind: "bundled", script: "validate-app-dependencies.sh" },
|
|
16
|
+
// NO LONGER SKIPS WITHOUT --platform-root. It used to, and the consequence was
|
|
17
|
+
// that the validator had never executed: measured across 22/22 forward-estate
|
|
18
|
+
// app repos it skipped every time, because nothing passes --platform-root, and
|
|
19
|
+
// the suite printed "All validators passed" over the skip.
|
|
20
|
+
//
|
|
21
|
+
// Only the resolution halves of checks 2 and 3 need a platform surface. The
|
|
22
|
+
// stability vocabulary, "is a service_id declared", and "does a locked
|
|
23
|
+
// dependency name a contract_path" are all answerable in the app repo alone,
|
|
24
|
+
// and they are the more basic defects. The script now runs those and reports
|
|
25
|
+
// the resolutions it could not perform, with their denominator — a partial run
|
|
26
|
+
// that never claims the gate is met (M-ADR 0200 §4.3).
|
|
27
|
+
build: (ctx) => ({
|
|
28
|
+
status: "run",
|
|
29
|
+
args: [
|
|
30
|
+
"--app-root",
|
|
31
|
+
ctx.targetRepo,
|
|
32
|
+
...(ctx.platformRoot ? ["--platform-root", ctx.platformRoot] : []),
|
|
33
|
+
],
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "adr-structure",
|
|
38
|
+
source: {
|
|
39
|
+
kind: "target-repo",
|
|
40
|
+
relPath: "orchestration/scripts/validate-adr-structure.sh",
|
|
41
|
+
},
|
|
42
|
+
build: () => ({ status: "run", args: [] }),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "spec-lifecycle",
|
|
46
|
+
source: {
|
|
47
|
+
kind: "target-repo",
|
|
48
|
+
relPath: "orchestration/scripts/validate-spec-lifecycle.sh",
|
|
49
|
+
},
|
|
50
|
+
build: () => ({ status: "run", args: [] }),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "no-bare-adr",
|
|
54
|
+
source: {
|
|
55
|
+
kind: "target-repo",
|
|
56
|
+
relPath: "orchestration/scripts/validate-no-bare-adr.sh",
|
|
57
|
+
},
|
|
58
|
+
build: () => ({ status: "run", args: [] }),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "bundle-coherence",
|
|
62
|
+
source: {
|
|
63
|
+
kind: "native",
|
|
64
|
+
run: async (ctx) => checkBundleCoherence(ctx.targetRepo),
|
|
65
|
+
},
|
|
66
|
+
build: () => ({ status: "run", args: [] }),
|
|
67
|
+
},
|
|
68
|
+
// Note: validate-runway-classification is intentionally NOT in the app set.
|
|
69
|
+
// It requires platform-level files (docs/platform/feature-tree.md,
|
|
70
|
+
// architecture-runway-index.md) that app repos don't own. Per ADR 0089,
|
|
71
|
+
// the reduced app suite is app-specific validators + shared foundations,
|
|
72
|
+
// not platform validators.
|
|
73
|
+
];
|
|
74
|
+
const INTEGRATION_VALIDATORS = [
|
|
75
|
+
{
|
|
76
|
+
name: "integration-docs",
|
|
77
|
+
source: { kind: "bundled", script: "validate-integration-docs.sh" },
|
|
78
|
+
build: (ctx) => ({ status: "run", args: ["--repo-root", ctx.targetRepo] }),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "adr-structure",
|
|
82
|
+
source: {
|
|
83
|
+
kind: "target-repo",
|
|
84
|
+
relPath: "orchestration/scripts/validate-adr-structure.sh",
|
|
85
|
+
},
|
|
86
|
+
build: () => ({ status: "run", args: [] }),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "spec-lifecycle",
|
|
90
|
+
source: {
|
|
91
|
+
kind: "target-repo",
|
|
92
|
+
relPath: "orchestration/scripts/validate-spec-lifecycle.sh",
|
|
93
|
+
},
|
|
94
|
+
build: () => ({ status: "run", args: [] }),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "no-bare-adr",
|
|
98
|
+
source: {
|
|
99
|
+
kind: "target-repo",
|
|
100
|
+
relPath: "orchestration/scripts/validate-no-bare-adr.sh",
|
|
101
|
+
},
|
|
102
|
+
build: () => ({ status: "run", args: [] }),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "bundle-coherence",
|
|
106
|
+
source: {
|
|
107
|
+
kind: "native",
|
|
108
|
+
run: async (ctx) => checkBundleCoherence(ctx.targetRepo),
|
|
109
|
+
},
|
|
110
|
+
build: () => ({ status: "run", args: [] }),
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
const PLATFORM_VALIDATORS = [
|
|
114
|
+
{
|
|
115
|
+
name: "traceability",
|
|
116
|
+
source: {
|
|
117
|
+
kind: "target-repo",
|
|
118
|
+
relPath: "orchestration/scripts/validate-traceability.sh",
|
|
119
|
+
},
|
|
120
|
+
build: () => ({ status: "run", args: ["all"] }),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "runway-classification",
|
|
124
|
+
source: {
|
|
125
|
+
kind: "target-repo",
|
|
126
|
+
relPath: "orchestration/scripts/validate-runway-classification.sh",
|
|
127
|
+
},
|
|
128
|
+
build: () => ({ status: "run", args: [] }),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "adr-structure",
|
|
132
|
+
source: {
|
|
133
|
+
kind: "target-repo",
|
|
134
|
+
relPath: "orchestration/scripts/validate-adr-structure.sh",
|
|
135
|
+
},
|
|
136
|
+
build: () => ({ status: "run", args: [] }),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "spec-lifecycle",
|
|
140
|
+
source: {
|
|
141
|
+
kind: "target-repo",
|
|
142
|
+
relPath: "orchestration/scripts/validate-spec-lifecycle.sh",
|
|
143
|
+
},
|
|
144
|
+
build: () => ({ status: "run", args: [] }),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "no-bare-adr",
|
|
148
|
+
source: {
|
|
149
|
+
kind: "target-repo",
|
|
150
|
+
relPath: "orchestration/scripts/validate-no-bare-adr.sh",
|
|
151
|
+
},
|
|
152
|
+
build: () => ({ status: "run", args: [] }),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "corpus-consistency",
|
|
156
|
+
source: {
|
|
157
|
+
kind: "target-repo",
|
|
158
|
+
relPath: "orchestration/scripts/validate-corpus-consistency.sh",
|
|
159
|
+
},
|
|
160
|
+
build: () => ({ status: "run", args: [] }),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "config",
|
|
164
|
+
source: {
|
|
165
|
+
kind: "target-repo",
|
|
166
|
+
relPath: "orchestration/scripts/validate-config.sh",
|
|
167
|
+
},
|
|
168
|
+
build: () => ({ status: "run", args: [] }),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "gate-readiness",
|
|
172
|
+
source: {
|
|
173
|
+
kind: "target-repo",
|
|
174
|
+
relPath: "orchestration/scripts/validate-gate-readiness.sh",
|
|
175
|
+
},
|
|
176
|
+
build: () => ({ status: "run", args: [] }),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "bundle-coherence",
|
|
180
|
+
source: {
|
|
181
|
+
kind: "native",
|
|
182
|
+
run: async (ctx) => checkBundleCoherence(ctx.targetRepo),
|
|
183
|
+
},
|
|
184
|
+
build: () => ({ status: "run", args: [] }),
|
|
185
|
+
},
|
|
186
|
+
// Optional: the upstream validate-docs.sh dispatcher covers the 6
|
|
187
|
+
// conditional validators (backward-compatibility, deprecation-runway,
|
|
188
|
+
// migration-completeness, ddd-artifacts, glossary-alignment,
|
|
189
|
+
// intent-quality-readiness) and self-skips when their inputs (BASELINE.md,
|
|
190
|
+
// gate-registry files) aren't present. Invoked via --validator docs-all.
|
|
191
|
+
{
|
|
192
|
+
name: "docs-all",
|
|
193
|
+
source: {
|
|
194
|
+
kind: "target-repo",
|
|
195
|
+
relPath: "orchestration/scripts/validate-docs.sh",
|
|
196
|
+
},
|
|
197
|
+
build: () => ({ status: "run", args: ["all"] }),
|
|
198
|
+
optional: true,
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
export function validatorsForProfile(profile) {
|
|
202
|
+
if (profile === "platform")
|
|
203
|
+
return PLATFORM_VALIDATORS;
|
|
204
|
+
return profile === "application" ? APP_VALIDATORS : INTEGRATION_VALIDATORS;
|
|
205
|
+
}
|
|
206
|
+
export function selectValidators(profile, filter) {
|
|
207
|
+
const all = validatorsForProfile(profile);
|
|
208
|
+
if (!filter)
|
|
209
|
+
return all.filter((v) => v.optional !== true);
|
|
210
|
+
const matched = all.find((v) => v.name === filter);
|
|
211
|
+
if (!matched) {
|
|
212
|
+
throw new Error(`Unknown validator "${filter}" for ${profile} profile. Available: ${all
|
|
213
|
+
.map((v) => v.name)
|
|
214
|
+
.join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
return [matched];
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { profileFromRepoKind, readRepoKind } from "../repo-kind.js";
|
|
2
|
+
import { aggregateResults } from "./aggregate.js";
|
|
3
|
+
import { selectValidators } from "./dispatch.js";
|
|
4
|
+
import { runValidator } from "./runner.js";
|
|
5
|
+
const SECTION_RULE = "═".repeat(59);
|
|
6
|
+
export async function runValidate(opts, stdout = (line) => process.stdout.write(line)) {
|
|
7
|
+
const repoKind = await readRepoKind(opts.targetRepo);
|
|
8
|
+
if (repoKind === null) {
|
|
9
|
+
stdout(fail(`Not an SDD repo: no .sdd-repo-kind marker in ${opts.targetRepo}`));
|
|
10
|
+
return { exitCode: 1, summary: emptySummary() };
|
|
11
|
+
}
|
|
12
|
+
const profile = profileFromRepoKind(repoKind);
|
|
13
|
+
stdout(section(`Validate (${profile})`));
|
|
14
|
+
let validators;
|
|
15
|
+
try {
|
|
16
|
+
validators = selectValidators(profile, opts.validator);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
stdout(fail(err.message));
|
|
20
|
+
return { exitCode: 1, summary: emptySummary() };
|
|
21
|
+
}
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const cfg of validators) {
|
|
24
|
+
stdout(info(`Running ${cfg.name}`));
|
|
25
|
+
const result = await runValidator(cfg, {
|
|
26
|
+
targetRepo: opts.targetRepo,
|
|
27
|
+
...(opts.platformRoot ? { platformRoot: opts.platformRoot } : {}),
|
|
28
|
+
});
|
|
29
|
+
results.push(result);
|
|
30
|
+
if (result.status === "skip") {
|
|
31
|
+
stdout(skip(`${cfg.name}: skipped — ${result.reason}`));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (result.stdout)
|
|
35
|
+
stdout(result.stdout);
|
|
36
|
+
if (result.stderr)
|
|
37
|
+
stdout(result.stderr);
|
|
38
|
+
if (result.exitCode === 0)
|
|
39
|
+
stdout(pass(`${cfg.name}: ok`));
|
|
40
|
+
else
|
|
41
|
+
stdout(fail(`${cfg.name}: exit ${result.exitCode}`));
|
|
42
|
+
}
|
|
43
|
+
const summary = aggregateResults(results, { strict: opts.strict });
|
|
44
|
+
stdout(info(`Summary: passed=${summary.passed} failed=${summary.failed} skipped=${summary.skipped} warned=${summary.warned}${opts.strict ? " (strict)" : ""}`));
|
|
45
|
+
stdout(verdict(summary, validators.length));
|
|
46
|
+
return { exitCode: summary.exitCode, summary };
|
|
47
|
+
}
|
|
48
|
+
// Every verdict states its denominator. A skipped validator did not run, so it
|
|
49
|
+
// cannot be covered by a green verdict: "all passed" with one skipped and "all
|
|
50
|
+
// passed" with none were previously the same line (M-ADR 0200 §4.3). The
|
|
51
|
+
// exit code is deliberately unchanged — the defect is the claim, not the
|
|
52
|
+
// gating; whether an unexecutable validator should FAIL is the open design
|
|
53
|
+
// question (post-3.0.0-followons.md A2), not this one.
|
|
54
|
+
function verdict(summary, selected) {
|
|
55
|
+
if (summary.exitCode !== 0)
|
|
56
|
+
return fail("One or more validators failed");
|
|
57
|
+
const ran = summary.passed + summary.failed;
|
|
58
|
+
if (summary.skipped === 0)
|
|
59
|
+
return pass("All validators passed");
|
|
60
|
+
if (ran === 0) {
|
|
61
|
+
return warn(`0 of ${selected} validators ran — all ${summary.skipped} skipped. ` +
|
|
62
|
+
`This is an absence of evidence, not a pass.`);
|
|
63
|
+
}
|
|
64
|
+
return warn(`${ran} of ${selected} validators passed — ${summary.skipped} skipped, ` +
|
|
65
|
+
`and a skipped validator is not covered by this verdict.`);
|
|
66
|
+
}
|
|
67
|
+
function section(title) {
|
|
68
|
+
return `\n${SECTION_RULE}\n ${title}\n${SECTION_RULE}\n\n`;
|
|
69
|
+
}
|
|
70
|
+
function info(msg) {
|
|
71
|
+
return `[INFO] ${msg}\n`;
|
|
72
|
+
}
|
|
73
|
+
function pass(msg) {
|
|
74
|
+
return `[PASS] ${msg}\n`;
|
|
75
|
+
}
|
|
76
|
+
function fail(msg) {
|
|
77
|
+
return `[FAIL] ${msg}\n`;
|
|
78
|
+
}
|
|
79
|
+
function skip(msg) {
|
|
80
|
+
return `[SKIP] ${msg}\n`;
|
|
81
|
+
}
|
|
82
|
+
function warn(msg) {
|
|
83
|
+
return `[WARN] ${msg}\n`;
|
|
84
|
+
}
|
|
85
|
+
function emptySummary() {
|
|
86
|
+
return { exitCode: 1, passed: 0, failed: 0, skipped: 0, warned: 0 };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { resolveBundledScript } from "../scripts.js";
|
|
6
|
+
const execFile = promisify(execFileCb);
|
|
7
|
+
export async function runValidator(cfg, ctx) {
|
|
8
|
+
const built = cfg.build(ctx);
|
|
9
|
+
if (built.status === "skip") {
|
|
10
|
+
return {
|
|
11
|
+
name: cfg.name,
|
|
12
|
+
scriptPath: null,
|
|
13
|
+
status: "skip",
|
|
14
|
+
reason: built.reason,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (cfg.source.kind === "native") {
|
|
18
|
+
const r = await cfg.source.run(ctx);
|
|
19
|
+
if (r.skipped !== undefined) {
|
|
20
|
+
return {
|
|
21
|
+
name: cfg.name,
|
|
22
|
+
scriptPath: "(native)",
|
|
23
|
+
status: "skip",
|
|
24
|
+
reason: r.skipped,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
name: cfg.name,
|
|
29
|
+
scriptPath: "(native)",
|
|
30
|
+
status: "run",
|
|
31
|
+
exitCode: r.exitCode,
|
|
32
|
+
stdout: r.stdout,
|
|
33
|
+
stderr: "",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const resolved = await resolveScriptPath(cfg.source, ctx);
|
|
37
|
+
if (!resolved.exists) {
|
|
38
|
+
return {
|
|
39
|
+
name: cfg.name,
|
|
40
|
+
scriptPath: resolved.path,
|
|
41
|
+
status: "skip",
|
|
42
|
+
reason: `script not found in target repo (${resolved.path}); expected sync to have placed it there`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const { stdout, stderr } = await execFile("bash", [resolved.path, ...built.args], {
|
|
47
|
+
cwd: ctx.targetRepo,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
name: cfg.name,
|
|
53
|
+
scriptPath: resolved.path,
|
|
54
|
+
status: "run",
|
|
55
|
+
exitCode: 0,
|
|
56
|
+
stdout,
|
|
57
|
+
stderr,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const e = err;
|
|
62
|
+
return {
|
|
63
|
+
name: cfg.name,
|
|
64
|
+
scriptPath: resolved.path,
|
|
65
|
+
status: "run",
|
|
66
|
+
exitCode: typeof e.code === "number" ? e.code : 1,
|
|
67
|
+
stdout: e.stdout ?? "",
|
|
68
|
+
stderr: e.stderr ?? "",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function resolveScriptPath(source, ctx) {
|
|
73
|
+
if (source.kind === "bundled") {
|
|
74
|
+
return { exists: true, path: resolveBundledScript(source.script) };
|
|
75
|
+
}
|
|
76
|
+
const path = join(ctx.targetRepo, source.relPath);
|
|
77
|
+
try {
|
|
78
|
+
await access(path);
|
|
79
|
+
return { exists: true, path };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { exists: false, path };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The read-only `catalogue` subcommands — validate (incl. --coverage), impact,
|
|
2
|
+
// baseline-check, version — split from `catalogue.ts` so a program can register
|
|
3
|
+
// them without importing the catalogue BUILD path. The verify-only CLI ships
|
|
4
|
+
// these and must not ship code that writes the derived model (M-ADR 0212 §4.3b).
|
|
5
|
+
// `registerCatalogue` calls each at its original position, so sdd-cli's
|
|
6
|
+
// `catalogue` surface and help order are unchanged.
|
|
7
|
+
//
|
|
8
|
+
// ⚠ Import each run* from its own module, NEVER from `../lib/catalogue/index.js`:
|
|
9
|
+
// that barrel re-exports the build and aggregate paths, and one convenience
|
|
10
|
+
// import from it would silently put authoring code back into the verify package.
|
|
11
|
+
import { runBaselineCheck } from "../lib/catalogue/baseline-check.js";
|
|
12
|
+
import { runCatalogueCoverage } from "../lib/catalogue/coverage-run.js";
|
|
13
|
+
import { runCatalogueImpact } from "../lib/catalogue/impact-run.js";
|
|
14
|
+
import { runCatalogueValidate } from "../lib/catalogue/validate.js";
|
|
15
|
+
import { runCatalogueVersion } from "../lib/catalogue/version.js";
|
|
16
|
+
export function registerCatalogueValidate(catalogue) {
|
|
17
|
+
catalogue
|
|
18
|
+
.command("validate")
|
|
19
|
+
.description("Re-run column-shape + FK validation against existing catalogues/ (no rebuild)")
|
|
20
|
+
.option("--sdd-path <path>", "Path to the SDD repo root (defaults to cwd)")
|
|
21
|
+
.option("--strict", "Treat findings as failures")
|
|
22
|
+
.option("--coverage", "Coverage mode: run the engine coverage predicates instead (same lines the gate scripts printed; exit 0 — gating stays the baseline's job)")
|
|
23
|
+
.option("--tier <tier>", "Coverage tier, explicit never inferred (member | aggregate; default member)")
|
|
24
|
+
.option("--catalogues-dir <dir>", "Coverage mode: catalogues directory relative to the SDD root (default catalogues)")
|
|
25
|
+
.action(async (opts) => {
|
|
26
|
+
if (opts.coverage === true) {
|
|
27
|
+
if (opts.tier !== undefined &&
|
|
28
|
+
opts.tier !== "member" &&
|
|
29
|
+
opts.tier !== "aggregate") {
|
|
30
|
+
process.stderr.write(`--tier must be 'member' or 'aggregate' (got: ${opts.tier})\n`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
const result = await runCatalogueCoverage({
|
|
34
|
+
...(opts.sddPath !== undefined ? { sddPath: opts.sddPath } : {}),
|
|
35
|
+
...(opts.cataloguesDir !== undefined
|
|
36
|
+
? { cataloguesDir: opts.cataloguesDir }
|
|
37
|
+
: {}),
|
|
38
|
+
...(opts.tier !== undefined
|
|
39
|
+
? { tier: opts.tier }
|
|
40
|
+
: {}),
|
|
41
|
+
}, (s) => process.stdout.write(s));
|
|
42
|
+
if (result.exitCode !== 0)
|
|
43
|
+
process.exit(result.exitCode);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const result = await runCatalogueValidate({
|
|
47
|
+
...(opts.sddPath !== undefined ? { sddPath: opts.sddPath } : {}),
|
|
48
|
+
...(opts.strict !== undefined ? { strict: opts.strict } : {}),
|
|
49
|
+
}, (s) => process.stdout.write(s));
|
|
50
|
+
if (result.exitCode !== 0)
|
|
51
|
+
process.exit(result.exitCode);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export function registerCatalogueImpact(catalogue) {
|
|
55
|
+
catalogue
|
|
56
|
+
.command("impact")
|
|
57
|
+
.description("Change-impact closure of a node (M-ADR 0206): each relationship is traversed per its declared impact-flow from the rubric-coverage-map — dependency edges reach their consumers, membership edges propagate part-to-whole only, xref/lineage records are inert — computed by recursive SQL on the embedded engine, with the co-directionality invariant asserted on every run")
|
|
58
|
+
.requiredOption("--of <node-id>", 'Node to compute impact of, in the projection\'s "<catalogue>/<id>" form (e.g. contracts/orders-api)')
|
|
59
|
+
.option("--sdd-path <path>", "Path to the SDD repo root (defaults to cwd)")
|
|
60
|
+
.option("--catalogues-dir <dir>", "Catalogues directory relative to the SDD root (default catalogues)")
|
|
61
|
+
.option("--tier <tier>", "Tier, explicit never inferred (member | aggregate; default member)")
|
|
62
|
+
.action(async (opts) => {
|
|
63
|
+
if (opts.tier !== undefined &&
|
|
64
|
+
opts.tier !== "member" &&
|
|
65
|
+
opts.tier !== "aggregate") {
|
|
66
|
+
process.stderr.write(`--tier must be 'member' or 'aggregate' (got: ${opts.tier})\n`);
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
const result = await runCatalogueImpact({
|
|
70
|
+
of: opts.of,
|
|
71
|
+
...(opts.sddPath !== undefined ? { sddPath: opts.sddPath } : {}),
|
|
72
|
+
...(opts.cataloguesDir !== undefined
|
|
73
|
+
? { cataloguesDir: opts.cataloguesDir }
|
|
74
|
+
: {}),
|
|
75
|
+
...(opts.tier !== undefined
|
|
76
|
+
? { tier: opts.tier }
|
|
77
|
+
: {}),
|
|
78
|
+
}, (s) => process.stdout.write(s));
|
|
79
|
+
if (result.exitCode !== 0)
|
|
80
|
+
process.exit(result.exitCode);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function registerCatalogueBaselineCheck(catalogue) {
|
|
84
|
+
catalogue
|
|
85
|
+
.command("baseline-check")
|
|
86
|
+
.description("Compare emitted findings against the repo's findings baseline BY IDENTITY (M-ADR 0132 §9) — a reworded finding reports as reworded, not as one closed and one new")
|
|
87
|
+
.option("--sdd-path <path>", "Path to the SDD repo root (defaults to cwd)")
|
|
88
|
+
.option("--findings <path>", "File of findings to compare (defaults to stdin, so it composes with `catalogue validate --coverage`)")
|
|
89
|
+
.option("--baseline <path>", "Baseline file (defaults to <sdd-path>/orchestration/catalogue-findings-baseline.txt)")
|
|
90
|
+
.action(async (opts) => {
|
|
91
|
+
const result = await runBaselineCheck({
|
|
92
|
+
...(opts.sddPath !== undefined ? { sddPath: opts.sddPath } : {}),
|
|
93
|
+
...(opts.findings !== undefined ? { findings: opts.findings } : {}),
|
|
94
|
+
...(opts.baseline !== undefined ? { baseline: opts.baseline } : {}),
|
|
95
|
+
stdin: async () => {
|
|
96
|
+
const chunks = [];
|
|
97
|
+
for await (const c of process.stdin)
|
|
98
|
+
chunks.push(Buffer.from(c));
|
|
99
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
100
|
+
},
|
|
101
|
+
}, (s) => process.stdout.write(s));
|
|
102
|
+
if (result.exitCode !== 0)
|
|
103
|
+
process.exit(result.exitCode);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
export function registerCatalogueVersion(catalogue) {
|
|
107
|
+
catalogue
|
|
108
|
+
.command("version")
|
|
109
|
+
.description("Print the catalogue schema version + sdd-cli version pin")
|
|
110
|
+
.action(async () => {
|
|
111
|
+
await runCatalogueVersion((s) => process.stdout.write(s));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=catalogue-verify.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { runList } from "../lib/list/index.js";
|
|
2
|
+
const VALID_PROFILES = [
|
|
3
|
+
"platform",
|
|
4
|
+
"application",
|
|
5
|
+
"integration",
|
|
6
|
+
];
|
|
7
|
+
export function registerList(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("list")
|
|
10
|
+
.description("Enumerate SDD repositories discoverable in the current workspace, including adopter pack provenance (ADR 0151)")
|
|
11
|
+
.option("--profile <profile>", "filter by profile: platform | application | integration")
|
|
12
|
+
.option("--workspace-root <path>", "workspace directory to walk (repeatable; defaults to cwd)", collectRoots, [])
|
|
13
|
+
.option("--format <format>", "output format: text | json", "text")
|
|
14
|
+
.option("--verbose", "show managed_path counts per bundle (text format)")
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const profileFilter = parseProfile(opts.profile);
|
|
17
|
+
const format = parseFormat(opts.format);
|
|
18
|
+
const { exitCode } = await runList({
|
|
19
|
+
workspaceRoots: opts.workspaceRoot ?? [],
|
|
20
|
+
format,
|
|
21
|
+
verbose: opts.verbose === true,
|
|
22
|
+
...(profileFilter ? { profileFilter } : {}),
|
|
23
|
+
}, (s) => process.stdout.write(s));
|
|
24
|
+
if (exitCode !== 0)
|
|
25
|
+
process.exit(exitCode);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function collectRoots(value, prior) {
|
|
29
|
+
prior.push(value);
|
|
30
|
+
return prior;
|
|
31
|
+
}
|
|
32
|
+
function parseProfile(value) {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (!VALID_PROFILES.includes(value)) {
|
|
36
|
+
throw new Error(`Unknown --profile "${value}". Expected one of: ${VALID_PROFILES.join(", ")}`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function parseFormat(value) {
|
|
41
|
+
if (value === "text" || value === "json")
|
|
42
|
+
return value;
|
|
43
|
+
throw new Error(`Unknown --format "${value}". Expected "text" or "json".`);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// `pack validate` registration, split from `pack.ts` so a program can register
|
|
2
|
+
// it without importing `runPackBuild` — the verify-only CLI must not ship the
|
|
3
|
+
// code that writes a pack (M-ADR 0212 §4.3b). `registerPack` calls this, so
|
|
4
|
+
// sdd-cli's `pack` surface and help order are unchanged.
|
|
5
|
+
import { runPackValidate } from "../lib/pack/validate.js";
|
|
6
|
+
export function registerPackValidate(pack) {
|
|
7
|
+
pack
|
|
8
|
+
.command("validate")
|
|
9
|
+
.description("Validate a context-pack seed file against the canonical schema. Fast feedback for authoring.")
|
|
10
|
+
.requiredOption("--seed <path>", "path to the seed file (orchestration/distribution/{name}-seed.yaml)")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const { exitCode } = await runPackValidate({ seedPath: opts.seed });
|
|
13
|
+
if (exitCode !== 0)
|
|
14
|
+
process.exit(exitCode);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=pack-validate.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Commander parse-arg coercion for an option naming a DIRECTORY ON DISK.
|
|
4
|
+
*
|
|
5
|
+
* Resolves the operand to an absolute path ONCE, at the option boundary, before
|
|
6
|
+
* any verb sees it. That placement is the fix, not a detail: B6 was a relative
|
|
7
|
+
* `--target-repo` that resolved far enough to succeed at the first read and
|
|
8
|
+
* then failed at every later one.
|
|
9
|
+
*
|
|
10
|
+
* `validate` read `.sdd-repo-kind` relative to the process cwd — which WORKS,
|
|
11
|
+
* so the run announced the correct profile and dispatched the full validator
|
|
12
|
+
* set — then handed the same unresolved string to bash validators running from
|
|
13
|
+
* a different working directory, which reported `App root not found` and exit
|
|
14
|
+
* 127. A healthy repo came back `passed=0 failed=5`, so the operator saw a
|
|
15
|
+
* broken REPO rather than a bad ARGUMENT. Repairing those messages would have
|
|
16
|
+
* been the wrong fix; the path is what is wrong, and it is wrong exactly once.
|
|
17
|
+
*
|
|
18
|
+
* Resolving at the boundary also means a verb cannot forget: there is no
|
|
19
|
+
* per-consumer `resolve()` to omit, and a new consumer of the option inherits
|
|
20
|
+
* an absolute path by construction.
|
|
21
|
+
*/
|
|
22
|
+
export function resolvePathOption(value) {
|
|
23
|
+
return resolve(value);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=path-option.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { runValidate } from "../lib/validate/index.js";
|
|
2
|
+
import { resolvePathOption } from "./path-option.js";
|
|
3
|
+
export function registerValidate(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("validate")
|
|
6
|
+
.description("Run the method-level validation suite for this SDD repository")
|
|
7
|
+
.option("--target-repo <dir>", "target SDD repo", resolvePathOption, process.cwd())
|
|
8
|
+
.option("--platform-root <dir>", "path to the sdd-method (or platform) repo, required for app-dependencies", resolvePathOption)
|
|
9
|
+
.option("--validator <name>", "run only the named validator")
|
|
10
|
+
.option("--strict", "treat warnings as failures")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const { exitCode } = await runValidate({
|
|
13
|
+
targetRepo: opts.targetRepo,
|
|
14
|
+
...(opts.platformRoot ? { platformRoot: opts.platformRoot } : {}),
|
|
15
|
+
...(opts.validator ? { validator: opts.validator } : {}),
|
|
16
|
+
strict: opts.strict === true,
|
|
17
|
+
});
|
|
18
|
+
if (exitCode !== 0)
|
|
19
|
+
process.exit(exitCode);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runVerify } from "./verify-program.js";
|
|
3
|
+
runVerify(process.argv.slice(2)).catch((err) => {
|
|
4
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5
|
+
process.stderr.write(`sdd-verify: ${message}\n`);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=verify-bin.js.map
|