@sdd-method/verify-cli 1.0.0 → 1.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/dist/lib/catalogue/cli-version.js +3 -3
- package/dist/lib/lock-file.js +25 -1
- package/dist/lib/pack/validate.js +2 -2
- package/dist/lib/package-version.js +47 -10
- package/dist/lib/validate/bundle-coherence.js +2 -2
- package/dist/lib/validate/plan.js +54 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/validate.js +26 -0
- package/dist/verify-program.js +11 -4
- package/dist/verify-version.json +2 -2
- package/package.json +2 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* sdd-cli version constant, used in error messages from the catalogue
|
|
3
3
|
* builder + aggregator + validate subcommands.
|
|
4
4
|
*
|
|
5
|
-
* Read from package.json via
|
|
5
|
+
* Read from package.json via getSddCliVersion() (the ENGINE version, also in a persona package) to guarantee the
|
|
6
6
|
* constant cannot drift from the published version.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
export const CLI_VERSION =
|
|
8
|
+
import { getSddCliVersion } from "../package-version.js";
|
|
9
|
+
export const CLI_VERSION = getSddCliVersion();
|
|
10
10
|
//# sourceMappingURL=cli-version.js.map
|
package/dist/lib/lock-file.js
CHANGED
|
@@ -62,6 +62,10 @@ export function serialiseLockFile(lock) {
|
|
|
62
62
|
const lines = [
|
|
63
63
|
`method_repo: ${lock.methodRepo}`,
|
|
64
64
|
`lock_version: ${lock.lockVersion}`,
|
|
65
|
+
...(lock.ciBinding !== undefined ? [`ci_binding: ${lock.ciBinding}`] : []),
|
|
66
|
+
...(lock.ciRunner !== undefined ? [`ci_runner: "${lock.ciRunner}"`] : []),
|
|
67
|
+
...(lock.ciWorkflowBlockSha256 !== undefined ? [`ci_workflow_block_sha256: "${lock.ciWorkflowBlockSha256}"`] : []),
|
|
68
|
+
...(lock.ciModuleSha256 !== undefined ? [`ci_module_sha256: "${lock.ciModuleSha256}"`] : []),
|
|
65
69
|
"bundles:",
|
|
66
70
|
];
|
|
67
71
|
for (const b of lock.bundles) {
|
|
@@ -109,6 +113,10 @@ function stripQuotes(value) {
|
|
|
109
113
|
function parseVersioned(raw) {
|
|
110
114
|
let methodRepo;
|
|
111
115
|
let lockVersion;
|
|
116
|
+
let ciWorkflowBlockSha256;
|
|
117
|
+
let ciBinding;
|
|
118
|
+
let ciRunner;
|
|
119
|
+
let ciModuleSha256;
|
|
112
120
|
const bundles = [];
|
|
113
121
|
let inBundles = false;
|
|
114
122
|
let inPaths = false;
|
|
@@ -170,6 +178,14 @@ function parseVersioned(raw) {
|
|
|
170
178
|
methodRepo = stripQuotes(value);
|
|
171
179
|
else if (key === "lock_version")
|
|
172
180
|
lockVersion = Number(stripQuotes(value));
|
|
181
|
+
else if (key === "ci_workflow_block_sha256")
|
|
182
|
+
ciWorkflowBlockSha256 = stripQuotes(value);
|
|
183
|
+
else if (key === "ci_binding")
|
|
184
|
+
ciBinding = stripQuotes(value);
|
|
185
|
+
else if (key === "ci_runner")
|
|
186
|
+
ciRunner = stripQuotes(value);
|
|
187
|
+
else if (key === "ci_module_sha256")
|
|
188
|
+
ciModuleSha256 = stripQuotes(value);
|
|
173
189
|
}
|
|
174
190
|
continue;
|
|
175
191
|
}
|
|
@@ -289,7 +305,15 @@ function parseVersioned(raw) {
|
|
|
289
305
|
? ` — the lock was written by a newer sdd-cli; upgrade rather than downgrading the lock.`
|
|
290
306
|
: `.`));
|
|
291
307
|
}
|
|
292
|
-
return {
|
|
308
|
+
return {
|
|
309
|
+
methodRepo,
|
|
310
|
+
lockVersion: LOCK_VERSION,
|
|
311
|
+
bundles,
|
|
312
|
+
...(ciBinding !== undefined ? { ciBinding } : {}),
|
|
313
|
+
...(ciRunner !== undefined ? { ciRunner } : {}),
|
|
314
|
+
...(ciWorkflowBlockSha256 !== undefined ? { ciWorkflowBlockSha256 } : {}),
|
|
315
|
+
...(ciModuleSha256 !== undefined ? { ciModuleSha256 } : {}),
|
|
316
|
+
};
|
|
293
317
|
}
|
|
294
318
|
function migrateV1(raw, legacyBundleName) {
|
|
295
319
|
const scalars = new Map();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (M-ADR 0212 §4.3b). `index.ts` re-exports it, so existing imports are unchanged.
|
|
5
5
|
import { validateSeed } from "./schema.js";
|
|
6
6
|
import { loadSeed } from "./seed.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getSddCliVersion } from "../package-version.js";
|
|
8
8
|
import { fail, info, pass, section } from "./log.js";
|
|
9
9
|
export async function runPackValidate(opts, stdout = (s) => process.stdout.write(s)) {
|
|
10
10
|
stdout(section("Pack validate"));
|
|
@@ -21,7 +21,7 @@ export async function runPackValidate(opts, stdout = (s) => process.stdout.write
|
|
|
21
21
|
// than itself, exactly as for `.sdd/sdd-manifest.yaml`. Silence here is what let an
|
|
22
22
|
// adopter follow the schema and get a clean validation with no effect.
|
|
23
23
|
if (seed.unknownKeys && seed.unknownKeys.length > 0) {
|
|
24
|
-
stdout(info(`${seed.unknownKeys.length} seed key(s) not understood by sdd-cli ${
|
|
24
|
+
stdout(info(`${seed.unknownKeys.length} seed key(s) not understood by sdd-cli ${getSddCliVersion()} ` +
|
|
25
25
|
`and therefore IGNORED: ${seed.unknownKeys.join(", ")}. If the seed follows a newer ` +
|
|
26
26
|
`context-pack-seed-schema than this build, upgrade sdd-cli; otherwise remove or correct the key.`));
|
|
27
27
|
}
|
|
@@ -13,6 +13,25 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
* Cached on first call.
|
|
14
14
|
*/
|
|
15
15
|
let cached;
|
|
16
|
+
let cachedEngine;
|
|
17
|
+
/**
|
|
18
|
+
* The version of the sdd-cli ENGINE this code is. In @sdd-method/sdd-cli it is the package
|
|
19
|
+
* version. In a persona package (verify-cli, maintain-cli) the package version is the
|
|
20
|
+
* persona's own (0.1.0, 1.0.0), while every check and stamp that means "which sdd-cli is
|
|
21
|
+
* this" (a bundle's minimum_sdd_cli_version, the CLI version stamped into catalogues, the
|
|
22
|
+
* MCP server identity) must see the engine it was built from. The persona build writes that
|
|
23
|
+
* as `sddCliVersion` in the package's package.json. Found by maintain-cli's acceptance run:
|
|
24
|
+
* its sync refused every bundle because 0.1.0 < 1.41.0.
|
|
25
|
+
*/
|
|
26
|
+
export function getSddCliVersion() {
|
|
27
|
+
if (cachedEngine !== undefined)
|
|
28
|
+
return cachedEngine;
|
|
29
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const pkg = JSON.parse(readFileSync(resolve(here, "..", "..", "package.json"), "utf8"));
|
|
31
|
+
cachedEngine = pkg.sddCliVersion ?? pkg.version;
|
|
32
|
+
return cachedEngine;
|
|
33
|
+
}
|
|
34
|
+
/** The package's own version: what `--version` prints. For the engine version, use getSddCliVersion. */
|
|
16
35
|
export function getPackageVersion() {
|
|
17
36
|
if (cached !== undefined)
|
|
18
37
|
return cached;
|
|
@@ -36,20 +55,38 @@ export function getPackageVersion() {
|
|
|
36
55
|
*
|
|
37
56
|
* Throws rather than defaults: a missing version must not render a floating install.
|
|
38
57
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
const cachedPersona = {};
|
|
59
|
+
/**
|
|
60
|
+
* One exact npm version: X.Y.Z, optionally with a prerelease tag (1.1.0-rc.0), which a release
|
|
61
|
+
* candidate published under the `next` dist-tag needs so hosted CI can run it before the release.
|
|
62
|
+
* Never a range, a tag name or a wildcard.
|
|
63
|
+
*/
|
|
64
|
+
export const EXACT_VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$/;
|
|
65
|
+
function personaVersion(persona) {
|
|
66
|
+
const hit = cachedPersona[persona];
|
|
67
|
+
if (hit !== undefined)
|
|
68
|
+
return hit;
|
|
43
69
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
44
70
|
const fromSource = here.endsWith(`${sep}src${sep}lib`);
|
|
45
71
|
const file = fromSource
|
|
46
|
-
? resolve(here, "..", "..", "scripts",
|
|
47
|
-
: resolve(here, "..",
|
|
72
|
+
? resolve(here, "..", "..", "scripts", `${persona}-version.json`)
|
|
73
|
+
: resolve(here, "..", `${persona}-version.json`);
|
|
48
74
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
49
|
-
if (typeof parsed.version !== "string" ||
|
|
50
|
-
throw new Error(`${file} has no semver \`version\`; refusing to render an unpinned
|
|
75
|
+
if (typeof parsed.version !== "string" || !EXACT_VERSION.test(parsed.version)) {
|
|
76
|
+
throw new Error(`${file} has no semver \`version\`; refusing to render an unpinned ${persona}-cli install`);
|
|
51
77
|
}
|
|
52
|
-
|
|
53
|
-
return
|
|
78
|
+
cachedPersona[persona] = parsed.version;
|
|
79
|
+
return parsed.version;
|
|
80
|
+
}
|
|
81
|
+
export function getVerifyCliVersion() {
|
|
82
|
+
return personaVersion("verify");
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The @sdd-method/maintain-cli version released WITH this sdd-cli. The managed CI workflow's
|
|
86
|
+
* catalogue freshness step installs exactly this version (M-ADR 0218 §5; ruled 2026-10-15, sdm-jts.6).
|
|
87
|
+
* Read the same way as the verify version, for the same reason (dist/ copy, never scripts/ live).
|
|
88
|
+
*/
|
|
89
|
+
export function getMaintainCliVersion() {
|
|
90
|
+
return personaVersion("maintain");
|
|
54
91
|
}
|
|
55
92
|
//# sourceMappingURL=package-version.js.map
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// read 0 (the scores-coverage precedent, M-ADR 0205 §4.5).
|
|
12
12
|
import { readLockFile, findForwardBase } from "../lock-file.js";
|
|
13
13
|
import { loadSddManifest, } from "../catalogue/manifest-loader.js";
|
|
14
|
-
import {
|
|
14
|
+
import { getSddCliVersion } from "../package-version.js";
|
|
15
15
|
/**
|
|
16
16
|
* Manifest profile → the forward base that profile expects. Profiles with no
|
|
17
17
|
* entry (support-repo, organisation-profile) carry no expectation — silence,
|
|
@@ -37,7 +37,7 @@ export async function checkBundleCoherence(targetRepo) {
|
|
|
37
37
|
let profile;
|
|
38
38
|
try {
|
|
39
39
|
const manifest = await loadSddManifest(targetRepo, {
|
|
40
|
-
cliVersion:
|
|
40
|
+
cliVersion: getSddCliVersion(),
|
|
41
41
|
});
|
|
42
42
|
profile = manifest.profile;
|
|
43
43
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The method-managed validation job, as DATA (bead sdm-dd6.2; Dagger ruling sdm-s48, 2026-10-15).
|
|
3
|
+
*
|
|
4
|
+
* One ordered plan per profile: every default validator, the platform's script-only checks, then the
|
|
5
|
+
* advisory catalogue freshness check. Every executor renders or runs THIS: the GitHub workflow
|
|
6
|
+
* generator (ci-workflow.ts) and the Dagger module (via `sdd-verify validate --list`). The job list
|
|
7
|
+
* drifted once when CI kept a hand copy of the suite (2026-10-01: three validators never reached CI),
|
|
8
|
+
* so no executor may keep its own list.
|
|
9
|
+
*/
|
|
10
|
+
import { validatorsForProfile } from "./dispatch.js";
|
|
11
|
+
/** Titles kept from the hand-kept list, so step names stay readable. */
|
|
12
|
+
const TITLES = {
|
|
13
|
+
"adr-structure": "Validate ADR metadata",
|
|
14
|
+
"spec-lifecycle": "Validate spec lifecycle",
|
|
15
|
+
"no-bare-adr": "Reject bare ADR references",
|
|
16
|
+
traceability: "Validate traceability",
|
|
17
|
+
"runway-classification": "Validate runway classification",
|
|
18
|
+
"corpus-consistency": "Validate corpus consistency",
|
|
19
|
+
config: "Validate configuration",
|
|
20
|
+
"app-docs": "Validate app documentation",
|
|
21
|
+
"integration-docs": "Validate integration documentation",
|
|
22
|
+
"yaml-parse": "Validate that tracked YAML parses",
|
|
23
|
+
"gate-readiness": "Validate gate readiness",
|
|
24
|
+
"bundle-coherence": "Report bundle coherence",
|
|
25
|
+
// CI has no platform checkout, so this step runs the app-only checks; the met gate needs the platform beside it.
|
|
26
|
+
"app-dependencies": "Validate app dependencies (app-only checks; no platform checkout in CI)",
|
|
27
|
+
};
|
|
28
|
+
export function methodValidationPlan(profile) {
|
|
29
|
+
const steps = validatorsForProfile(profile)
|
|
30
|
+
.filter((v) => v.optional !== true)
|
|
31
|
+
.map((v) => ({
|
|
32
|
+
id: v.name,
|
|
33
|
+
title: TITLES[v.name] ?? `Validate ${v.name}`,
|
|
34
|
+
tool: "sdd-verify",
|
|
35
|
+
args: ["validate", "--target-repo", ".", "--validator", v.name],
|
|
36
|
+
advisory: false,
|
|
37
|
+
}));
|
|
38
|
+
if (profile === "platform") {
|
|
39
|
+
steps.push({ id: "cascade-completeness", title: "Validate cascade completeness", tool: "bash", args: ["orchestration/scripts/validate-cascade-completeness.sh", "all"], advisory: false }, { id: "architecture-alignment", title: "Validate architecture alignment", tool: "bash", args: ["orchestration/scripts/validate-architecture-alignment.sh"], advisory: false });
|
|
40
|
+
}
|
|
41
|
+
steps.push({
|
|
42
|
+
id: "catalogue-freshness",
|
|
43
|
+
title: "Check catalogues reproduce from the specs (advisory)",
|
|
44
|
+
tool: "sdd-maintain",
|
|
45
|
+
args: ["catalogue", "build", "--check", "--sdd-path", "."],
|
|
46
|
+
advisory: true,
|
|
47
|
+
});
|
|
48
|
+
return steps;
|
|
49
|
+
}
|
|
50
|
+
/** The shell form of a step, as a CI host's `run:` line writes it. */
|
|
51
|
+
export function stepCommand(step) {
|
|
52
|
+
return [step.tool, ...step.args].join(" ");
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=plan.js.map
|
package/dist/verbs/validate.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { runValidate } from "../lib/validate/index.js";
|
|
2
|
+
import { methodValidationPlan } from "../lib/validate/plan.js";
|
|
3
|
+
import { profileFromRepoKind, readRepoKind } from "../lib/repo-kind.js";
|
|
4
|
+
const LISTABLE = ["platform", "application", "integration"];
|
|
5
|
+
const isListable = (p) => LISTABLE.includes(p);
|
|
2
6
|
import { resolvePathOption } from "./path-option.js";
|
|
3
7
|
export function registerValidate(program) {
|
|
4
8
|
program
|
|
@@ -8,7 +12,29 @@ export function registerValidate(program) {
|
|
|
8
12
|
.option("--platform-root <dir>", "path to the sdd-method (or platform) repo, required for app-dependencies", resolvePathOption)
|
|
9
13
|
.option("--validator <name>", "run only the named validator")
|
|
10
14
|
.option("--strict", "treat warnings as failures")
|
|
15
|
+
.option("--list", "print the method-managed validation job for this repo's profile as JSON (the plan every CI executor runs, the Dagger module included) and run nothing")
|
|
16
|
+
.option("--profile <profile>", "with --list: the profile to list, instead of reading .sdd-repo-kind (platform | application | integration)")
|
|
11
17
|
.action(async (opts) => {
|
|
18
|
+
if (opts.list === true) {
|
|
19
|
+
let profile;
|
|
20
|
+
if (opts.profile !== undefined) {
|
|
21
|
+
if (!isListable(opts.profile)) {
|
|
22
|
+
process.stderr.write(`--profile must be platform, application or integration (got: ${opts.profile})\n`);
|
|
23
|
+
process.exit(2);
|
|
24
|
+
}
|
|
25
|
+
profile = opts.profile;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const kind = await readRepoKind(opts.targetRepo);
|
|
29
|
+
if (kind === null) {
|
|
30
|
+
process.stderr.write(`Not an SDD repo (no .sdd-repo-kind in ${opts.targetRepo}); pass --profile\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
profile = profileFromRepoKind(kind);
|
|
34
|
+
}
|
|
35
|
+
process.stdout.write(JSON.stringify({ profile, steps: methodValidationPlan(profile) }, null, 2) + "\n");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
12
38
|
const { exitCode } = await runValidate({
|
|
13
39
|
targetRepo: opts.targetRepo,
|
|
14
40
|
...(opts.platformRoot ? { platformRoot: opts.platformRoot } : {}),
|
package/dist/verify-program.js
CHANGED
|
@@ -49,7 +49,7 @@ const OUTSIDE = "is not part of sdd-verify, which cannot write to the repository
|
|
|
49
49
|
* this binary exists to prevent. So an excluded verb fails, loudly, whatever
|
|
50
50
|
* flags accompany it.
|
|
51
51
|
*/
|
|
52
|
-
export function rejectOutsideSurface(program, argv) {
|
|
52
|
+
export function rejectOutsideSurface(program, argv, outside = OUTSIDE) {
|
|
53
53
|
const words = argv.filter((a) => !a.startsWith("-"));
|
|
54
54
|
if (words.length === 0)
|
|
55
55
|
return;
|
|
@@ -58,10 +58,10 @@ export function rejectOutsideSurface(program, argv) {
|
|
|
58
58
|
return;
|
|
59
59
|
const cmd = program.commands.find((c) => c.name() === verb);
|
|
60
60
|
if (!cmd)
|
|
61
|
-
throw new Error(`'${verb}' ${
|
|
61
|
+
throw new Error(`'${verb}' ${outside}`);
|
|
62
62
|
const subs = cmd.commands.map((c) => c.name()).filter((n) => n !== "help");
|
|
63
63
|
if (subs.length > 0 && sub !== undefined && sub !== "help" && !subs.includes(sub)) {
|
|
64
|
-
throw new Error(`'${verb} ${sub}' ${
|
|
64
|
+
throw new Error(`'${verb} ${sub}' ${outside}`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
export async function runVerify(argv) {
|
|
@@ -97,6 +97,14 @@ export function buildVerifyProgram() {
|
|
|
97
97
|
writeOut: (str) => process.stdout.write(str),
|
|
98
98
|
writeErr: (str) => process.stderr.write(str),
|
|
99
99
|
});
|
|
100
|
+
registerVerifySurface(program);
|
|
101
|
+
return program;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The verify verbs, registered onto any program. sdd-verify calls it; sdd-maintain calls it too,
|
|
105
|
+
* which is what makes the personas nest structurally: whatever verify ships, maintain ships.
|
|
106
|
+
*/
|
|
107
|
+
export function registerVerifySurface(program) {
|
|
100
108
|
registerValidate(program);
|
|
101
109
|
registerList(program);
|
|
102
110
|
const pack = program
|
|
@@ -110,6 +118,5 @@ export function buildVerifyProgram() {
|
|
|
110
118
|
registerCatalogueImpact(catalogue);
|
|
111
119
|
registerCatalogueBaselineCheck(catalogue);
|
|
112
120
|
registerCatalogueVersion(catalogue);
|
|
113
|
-
return program;
|
|
114
121
|
}
|
|
115
122
|
//# sourceMappingURL=verify-program.js.map
|
package/dist/verify-version.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdd-method/verify-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"sddCliVersion": "1.42.0",
|
|
4
5
|
"description": "Verify an SDD repository without changing it — validate, list, and read-only catalogue and pack checks. Ships only the code those verbs reach; it cannot write to the repository it is pointed at. Authoring and maintenance are in @sdd-method/sdd-cli.",
|
|
5
6
|
"license": "Apache-2.0",
|
|
6
7
|
"type": "module",
|