@sdd-method/sdd-cli 1.29.0 → 1.29.2
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/csv-writer.d.ts +14 -7
- package/dist/lib/catalogue/csv-writer.d.ts.map +1 -1
- package/dist/lib/catalogue/csv-writer.js +14 -7
- package/dist/lib/catalogue/csv-writer.js.map +1 -1
- package/dist/lib/catalogue/graph-run.d.ts +1 -28
- package/dist/lib/catalogue/graph-run.d.ts.map +1 -1
- package/dist/lib/catalogue/graph-run.js +3 -57
- package/dist/lib/catalogue/graph-run.js.map +1 -1
- package/dist/lib/catalogue/impact-run.d.ts +1 -1
- package/dist/lib/catalogue/impact-run.d.ts.map +1 -1
- package/dist/lib/catalogue/impact-run.js +1 -1
- package/dist/lib/catalogue/impact-run.js.map +1 -1
- package/dist/lib/catalogue/relationship-semantics.d.ts +29 -0
- package/dist/lib/catalogue/relationship-semantics.d.ts.map +1 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/relationship-semantics.js.map +1 -0
- package/dist/lib/conformance/finding.d.ts +1 -1
- package/dist/lib/conformance/finding.d.ts.map +1 -1
- package/dist/lib/conformance/finding.js +10 -0
- package/dist/lib/conformance/finding.js.map +1 -1
- package/dist/lib/pack/index.d.ts +1 -9
- package/dist/lib/pack/index.d.ts.map +1 -1
- package/dist/lib/pack/index.js +2 -53
- package/dist/lib/pack/index.js.map +1 -1
- package/dist/lib/pack/log.d.ts +6 -0
- package/dist/lib/pack/log.d.ts.map +1 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/log.js.map +1 -0
- package/dist/lib/pack/validate.d.ts +10 -0
- package/dist/lib/pack/validate.d.ts.map +1 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/pack/validate.js.map +1 -0
- package/dist/verbs/catalogue-verify.d.ts +6 -0
- package/dist/verbs/catalogue-verify.d.ts.map +1 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/catalogue-verify.js.map +1 -0
- package/dist/verbs/catalogue.d.ts.map +1 -1
- package/dist/verbs/catalogue.js +6 -94
- package/dist/verbs/catalogue.js.map +1 -1
- package/dist/verbs/pack-validate.d.ts +3 -0
- package/dist/verbs/pack-validate.d.ts.map +1 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/pack-validate.js.map +1 -0
- package/dist/verbs/pack.d.ts.map +1 -1
- package/dist/verbs/pack.js +3 -10
- package/dist/verbs/pack.js.map +1 -1
- package/dist/verify-bin.d.ts +3 -0
- package/dist/verify-bin.d.ts.map +1 -0
- package/dist/verify-bin.js +8 -0
- package/dist/verify-bin.js.map +1 -0
- package/dist/verify-program.d.ts +14 -0
- package/dist/verify-program.d.ts.map +1 -0
- package/dist/verify-program.js +115 -0
- package/dist/verify-program.js.map +1 -0
- package/package.json +3 -3
- package/scripts/build-verify-package.mjs +178 -0
- package/scripts/verify-acceptance.sh +141 -0
- package/dist/conformance-bin.d.ts +0 -3
- package/dist/conformance-bin.d.ts.map +0 -1
- package/dist/conformance-bin.js +0 -8
- package/dist/conformance-bin.js.map +0 -1
- package/dist/conformance-program.d.ts +0 -6
- package/dist/conformance-program.d.ts.map +0 -1
- package/dist/conformance-program.js +0 -59
- package/dist/conformance-program.js.map +0 -1
- package/scripts/build-conformance-package.mjs +0 -57
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Assemble the publishable @sdd-method/verify-cli package — M-ADR 0212 §4.3b.
|
|
2
|
+
//
|
|
3
|
+
// ONE SOURCE, and this time a genuinely smaller ARTEFACT. The conformance
|
|
4
|
+
// package copied the whole of dist/ under a second manifest, so its "excluded"
|
|
5
|
+
// verbs were one `node dist/cli.js …` away. This script ships only the import
|
|
6
|
+
// CLOSURE of dist/verify-bin.js: an excluded verb's code is not in the package
|
|
7
|
+
// to be reached by any path.
|
|
8
|
+
//
|
|
9
|
+
// Fails closed on four conditions, each of which would otherwise let the
|
|
10
|
+
// boundary pass while not holding:
|
|
11
|
+
// 1. a relative import in the closure that does not resolve to a file;
|
|
12
|
+
// 2. a bare import that is neither a Node built-in nor a declared dependency;
|
|
13
|
+
// 3. a CANARY — a known authoring module — present in the closure;
|
|
14
|
+
// 4. a canary that no longer EXISTS in dist. A canary that was renamed or
|
|
15
|
+
// deleted cannot be absent from anything, so the check above would pass
|
|
16
|
+
// vacuously; this makes it fail instead, and asks for the list to follow.
|
|
17
|
+
//
|
|
18
|
+
// The generated directory is gitignored and safe to delete.
|
|
19
|
+
|
|
20
|
+
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
21
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
22
|
+
import { builtinModules } from "node:module";
|
|
23
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
27
|
+
const DIST = join(ROOT, "dist");
|
|
28
|
+
const OUT = join(ROOT, "build", "verify-cli");
|
|
29
|
+
const ENTRY = join(DIST, "verify-bin.js");
|
|
30
|
+
|
|
31
|
+
// Authoring and maintenance code that must never reach this package. Entry
|
|
32
|
+
// modules of excluded verbs plus the full CLI's own entrypoints.
|
|
33
|
+
const CANARIES = [
|
|
34
|
+
"cli.js",
|
|
35
|
+
"index.js",
|
|
36
|
+
"verbs/sync.js",
|
|
37
|
+
"verbs/catalogue.js",
|
|
38
|
+
"verbs/pack.js",
|
|
39
|
+
"verbs/mcp.js",
|
|
40
|
+
"verbs/init.js",
|
|
41
|
+
"verbs/render.js",
|
|
42
|
+
"verbs/metamodel.js",
|
|
43
|
+
"verbs/conformance.js",
|
|
44
|
+
"lib/sync/index.js",
|
|
45
|
+
"lib/catalogue/build.js",
|
|
46
|
+
"lib/catalogue/aggregate.js",
|
|
47
|
+
"lib/catalogue/graph-run.js",
|
|
48
|
+
"lib/catalogue/migration-map.js",
|
|
49
|
+
"lib/pack/builder.js",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const die = (msg) => {
|
|
53
|
+
process.stderr.write(`build-verify-package: FAIL — ${msg}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (!existsSync(ENTRY)) die(`${relative(ROOT, ENTRY)} missing — run \`npm run build\` first`);
|
|
58
|
+
|
|
59
|
+
// ---- 1. closure over imports ----------------------------------------------
|
|
60
|
+
// Line-based over tsc output, skipping comment lines. An earlier regex over the
|
|
61
|
+
// whole text read `// from "changed" produces…` in a comment as an import of a
|
|
62
|
+
// package called `changed` — prose parsed as code, the same failure a grep over
|
|
63
|
+
// workflow comments made the same day. Fail-closed caught it; this stops it.
|
|
64
|
+
const STATIC = /^\s*(?:import|export|\})[^\n]*?\bfrom\s*["']([^"']+)["']/;
|
|
65
|
+
const SIDE = /^\s*import\s*["']([^"']+)["']/;
|
|
66
|
+
const DYNAMIC = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
67
|
+
function specifiers(src) {
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const line of src.split("\n")) {
|
|
70
|
+
const t = line.trimStart();
|
|
71
|
+
if (t.startsWith("//") || t.startsWith("*") || t.startsWith("/*")) continue;
|
|
72
|
+
const m = line.match(STATIC) ?? line.match(SIDE);
|
|
73
|
+
if (m) out.push(m[1]);
|
|
74
|
+
for (const d of line.matchAll(DYNAMIC)) out.push(d[1]);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
const closure = new Set();
|
|
79
|
+
const bare = new Set();
|
|
80
|
+
const stack = [ENTRY];
|
|
81
|
+
while (stack.length) {
|
|
82
|
+
const f = stack.pop();
|
|
83
|
+
if (closure.has(f)) continue;
|
|
84
|
+
closure.add(f);
|
|
85
|
+
for (const spec of specifiers(readFileSync(f, "utf8"))) {
|
|
86
|
+
if (spec.startsWith(".")) {
|
|
87
|
+
const target = resolve(dirname(f), spec);
|
|
88
|
+
if (!existsSync(target)) die(`${relative(DIST, f)} imports '${spec}', which does not resolve`);
|
|
89
|
+
stack.push(target);
|
|
90
|
+
} else {
|
|
91
|
+
bare.add(spec);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---- 2. dependencies = only what the closure imports -----------------------
|
|
97
|
+
const main = JSON.parse(await readFile(join(ROOT, "package.json"), "utf8"));
|
|
98
|
+
const pkgName = (spec) =>
|
|
99
|
+
spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
|
|
100
|
+
const builtins = new Set(builtinModules);
|
|
101
|
+
const deps = {};
|
|
102
|
+
for (const spec of bare) {
|
|
103
|
+
if (spec.startsWith("node:") || builtins.has(spec) || builtins.has(spec.split("/")[0])) continue;
|
|
104
|
+
const name = pkgName(spec);
|
|
105
|
+
const range = main.dependencies?.[name];
|
|
106
|
+
if (!range) die(`closure imports '${spec}', which is not a declared dependency of sdd-cli`);
|
|
107
|
+
deps[name] = range;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---- 3 + 4. canaries -------------------------------------------------------
|
|
111
|
+
for (const c of CANARIES) {
|
|
112
|
+
const abs = join(DIST, c);
|
|
113
|
+
if (!existsSync(abs)) die(`canary '${c}' no longer exists in dist — the check would pass vacuously; update CANARIES`);
|
|
114
|
+
if (closure.has(abs)) die(`authoring module '${c}' is reachable from verify-bin.js — the boundary does not hold`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---- assets: yaml/json data beside a closure module ------------------------
|
|
118
|
+
const assets = new Set();
|
|
119
|
+
for (const f of closure) {
|
|
120
|
+
const dir = dirname(f);
|
|
121
|
+
for (const name of readdirSync(dir)) {
|
|
122
|
+
const p = join(dir, name);
|
|
123
|
+
if (/\.(ya?ml|json)$/.test(name) && statSync(p).isFile()) assets.add(p);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---- assemble --------------------------------------------------------------
|
|
128
|
+
const version = process.env.VERIFY_VERSION ?? "0.1.0";
|
|
129
|
+
// Empty the directory, never remove it: a shell sitting in build/verify-cli
|
|
130
|
+
// (to run `npm publish`) keeps a handle on the directory itself, and deleting
|
|
131
|
+
// it leaves that shell in a path that no longer exists — npm then dies with
|
|
132
|
+
// ENOENT uv_cwd. That happened, twice, between a rebuild and a publish.
|
|
133
|
+
await mkdir(OUT, { recursive: true });
|
|
134
|
+
for (const entry of readdirSync(OUT)) await rm(join(OUT, entry), { recursive: true, force: true });
|
|
135
|
+
for (const f of [...closure, ...assets]) {
|
|
136
|
+
const dest = join(OUT, "dist", relative(DIST, f));
|
|
137
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
138
|
+
await cp(f, dest);
|
|
139
|
+
}
|
|
140
|
+
for (const f of ["LICENSE", "NOTICE"]) await cp(join(ROOT, f), join(OUT, f));
|
|
141
|
+
|
|
142
|
+
if (main.publishConfig?.access !== "public") die("sdd-cli's package.json has no publishConfig.access=public — the verify package would publish private");
|
|
143
|
+
|
|
144
|
+
const pkg = {
|
|
145
|
+
name: "@sdd-method/verify-cli",
|
|
146
|
+
version,
|
|
147
|
+
description:
|
|
148
|
+
"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.",
|
|
149
|
+
license: main.license,
|
|
150
|
+
type: main.type,
|
|
151
|
+
bin: { "sdd-verify": "dist/verify-bin.js" },
|
|
152
|
+
engines: main.engines,
|
|
153
|
+
dependencies: deps,
|
|
154
|
+
repository: main.repository,
|
|
155
|
+
// Inherited, not assumed: a scoped package publishes PRIVATE by default, and
|
|
156
|
+
// the first publish of this one failed with E402 (private packages need a
|
|
157
|
+
// paid plan) because this field was missing. sdd-cli carries it; so must this.
|
|
158
|
+
publishConfig: main.publishConfig,
|
|
159
|
+
files: ["dist", "README.md", "LICENSE", "NOTICE"],
|
|
160
|
+
};
|
|
161
|
+
await writeFile(join(OUT, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
162
|
+
await writeFile(
|
|
163
|
+
join(OUT, "README.md"),
|
|
164
|
+
`# @sdd-method/verify-cli\n\n` +
|
|
165
|
+
`\`sdd-verify\` checks an SDD repository and **cannot change it**.\n\n` +
|
|
166
|
+
`Verbs: \`validate\`, \`list\`, \`pack validate\`, \`catalogue validate\` (incl. \`--coverage\`), ` +
|
|
167
|
+
`\`catalogue impact\`, \`catalogue baseline-check\`, \`catalogue version\`.\n\n` +
|
|
168
|
+
`The package contains only the code those verbs reach. Authoring and maintenance verbs — ` +
|
|
169
|
+
`\`sync\`, \`catalogue build\`, \`init\`, \`scaffold\`, \`mcp\` and the rest — are not present in it, ` +
|
|
170
|
+
`and are in \`@sdd-method/sdd-cli\`. Invoking one fails with exit 1.\n`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const kb = (fs) => Math.round(fs.reduce((n, f) => n + statSync(f).size, 0) / 1024);
|
|
174
|
+
process.stdout.write(
|
|
175
|
+
`assembled ${pkg.name}@${version} at build/verify-cli\n` +
|
|
176
|
+
` modules ${closure.size} (${kb([...closure])} kB) · assets ${assets.size} · deps ${Object.keys(deps).join(", ")}\n` +
|
|
177
|
+
` canaries absent: ${CANARIES.length}/${CANARIES.length}\n`,
|
|
178
|
+
);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Acceptance for the verify-only CLI — M-ADR 0212 §4.3b.
|
|
3
|
+
#
|
|
4
|
+
# The property: sdd-verify CANNOT WRITE to the repository it is pointed at.
|
|
5
|
+
# Tested, not asserted — reading help text misfiled four verbs before this
|
|
6
|
+
# existed. For each repository: a fresh clone, every verb in every mode, and
|
|
7
|
+
# `git status --porcelain --ignored` must be empty after each one (ignored paths
|
|
8
|
+
# count: a verb that drops a cache into a gitignored directory still wrote).
|
|
9
|
+
#
|
|
10
|
+
# Fails closed on:
|
|
11
|
+
# - any write, by any verb, in any mode;
|
|
12
|
+
# - an excluded verb that does not exit 1 with the refusal message;
|
|
13
|
+
# - the CONTROL: the full CLI's `catalogue graph` must dirty a clone, or the
|
|
14
|
+
# detector cannot see a write and every PASS above is vacuous;
|
|
15
|
+
# - COVERAGE: a surface verb exercised on no repository. A missing input must
|
|
16
|
+
# not silently shrink the test.
|
|
17
|
+
#
|
|
18
|
+
# usage: verify-acceptance.sh --bin <sdd-verify entry> --control-bin <sdd-cli entry> <repo>...
|
|
19
|
+
set -uo pipefail
|
|
20
|
+
|
|
21
|
+
BIN=""; CONTROL=""; REPOS=()
|
|
22
|
+
while [ $# -gt 0 ]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--bin) BIN="$2"; shift 2 ;;
|
|
25
|
+
--control-bin) CONTROL="$2"; shift 2 ;;
|
|
26
|
+
*) REPOS+=("$1"); shift ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
[ -n "$BIN" ] && [ -n "$CONTROL" ] && [ ${#REPOS[@]} -gt 0 ] || {
|
|
30
|
+
echo "usage: $0 --bin <sdd-verify> --control-bin <sdd-cli> <repo>..." >&2; exit 2; }
|
|
31
|
+
|
|
32
|
+
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
|
33
|
+
FAIL=0; RUNS=0
|
|
34
|
+
declare -A SEEN
|
|
35
|
+
SURFACE=("--version" "validate" "validate --strict" "list" "catalogue validate" \
|
|
36
|
+
"catalogue validate --strict" "catalogue validate --coverage" "catalogue impact" \
|
|
37
|
+
"catalogue baseline-check --findings" "catalogue baseline-check (stdin)" \
|
|
38
|
+
"catalogue version" "pack validate")
|
|
39
|
+
|
|
40
|
+
clean() { [ -z "$(git -C "$1" status --porcelain --ignored)" ]; }
|
|
41
|
+
|
|
42
|
+
run() { # run <repo-dir> <surface-label> <args...>
|
|
43
|
+
local dir="$1" label="$2"; shift 2
|
|
44
|
+
RUNS=$((RUNS+1))
|
|
45
|
+
( cd "$dir" && node "$BIN" "$@" >"$TMP/out" 2>&1 ); local rc=$?
|
|
46
|
+
if clean "$dir"; then
|
|
47
|
+
SEEN["$label"]=1
|
|
48
|
+
printf " %-46s exit %-3s clean\n" "$label" "$rc"
|
|
49
|
+
else
|
|
50
|
+
FAIL=1
|
|
51
|
+
printf " %-46s exit %-3s ⚠ WROTE:\n" "$label" "$rc"
|
|
52
|
+
git -C "$dir" status --porcelain --ignored | sed 's/^/ /' | head -8
|
|
53
|
+
git -C "$dir" clean -qfdx; git -C "$dir" checkout -q -- .
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
refuse() { # refuse <repo-dir> <args...> — must exit 1, name sdd-cli, write nothing
|
|
58
|
+
local dir="$1"; shift
|
|
59
|
+
( cd "$dir" && node "$BIN" "$@" >"$TMP/out" 2>&1 ); local rc=$?
|
|
60
|
+
if [ "$rc" -eq 1 ] && grep -q "not part of sdd-verify" "$TMP/out" && clean "$dir"; then
|
|
61
|
+
printf " %-46s refused (exit 1)\n" "✗ $*"
|
|
62
|
+
else
|
|
63
|
+
FAIL=1; printf " %-46s ⚠ NOT REFUSED — exit %s\n" "✗ $*" "$rc"; sed 's/^/ /' "$TMP/out" | head -3
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for repo in "${REPOS[@]}"; do
|
|
68
|
+
name="$(basename "$repo")"; dir="$TMP/$name"
|
|
69
|
+
git clone -q --no-local "$repo" "$dir" || { echo "clone failed: $repo"; FAIL=1; continue; }
|
|
70
|
+
echo "=== $name @ $(git -C "$dir" rev-parse --short HEAD) ==="
|
|
71
|
+
clean "$dir" || { echo " fresh clone is not clean — cannot test"; FAIL=1; continue; }
|
|
72
|
+
|
|
73
|
+
run "$dir" "--version" --version
|
|
74
|
+
run "$dir" "validate" validate --target-repo .
|
|
75
|
+
run "$dir" "validate --strict" validate --target-repo . --strict
|
|
76
|
+
run "$dir" "list" list --workspace-root .
|
|
77
|
+
if [ -d "$dir/catalogues" ]; then
|
|
78
|
+
run "$dir" "catalogue validate" catalogue validate --sdd-path .
|
|
79
|
+
run "$dir" "catalogue validate --strict" catalogue validate --sdd-path . --strict
|
|
80
|
+
run "$dir" "catalogue validate --coverage" catalogue validate --sdd-path . --coverage
|
|
81
|
+
[ -d "$dir/catalogues-aggregated" ] && run "$dir" "catalogue validate --coverage" \
|
|
82
|
+
catalogue validate --sdd-path . --coverage --tier aggregate --catalogues-dir catalogues-aggregated
|
|
83
|
+
node_id="$(awk -F, 'NR==2{print $1; exit}' "$dir/catalogues/services.csv" 2>/dev/null)"
|
|
84
|
+
[ -n "$node_id" ] && run "$dir" "catalogue impact" catalogue impact --sdd-path . --of "services/$node_id"
|
|
85
|
+
( cd "$dir" && node "$BIN" catalogue validate --sdd-path . --coverage >"$TMP/findings.txt" 2>/dev/null )
|
|
86
|
+
clean "$dir" || { echo " ⚠ findings capture wrote"; FAIL=1; }
|
|
87
|
+
run "$dir" "catalogue baseline-check --findings" catalogue baseline-check --sdd-path . --findings "$TMP/findings.txt"
|
|
88
|
+
RUNS=$((RUNS+1))
|
|
89
|
+
( cd "$dir" && node "$BIN" catalogue baseline-check --sdd-path . <"$TMP/findings.txt" >"$TMP/out" 2>&1 ); rc=$?
|
|
90
|
+
if clean "$dir"; then SEEN["catalogue baseline-check (stdin)"]=1; printf " %-46s exit %-3s clean\n" "catalogue baseline-check (stdin)" "$rc"
|
|
91
|
+
else FAIL=1; echo " catalogue baseline-check (stdin) ⚠ WROTE"; fi
|
|
92
|
+
run "$dir" "catalogue version" catalogue version
|
|
93
|
+
fi
|
|
94
|
+
for seed in "$dir"/orchestration/distribution/*seed*.yaml; do
|
|
95
|
+
[ -f "$seed" ] && run "$dir" "pack validate" pack validate --seed "orchestration/distribution/$(basename "$seed")"
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
refuse "$dir" sync
|
|
99
|
+
refuse "$dir" catalogue build
|
|
100
|
+
refuse "$dir" pack build --seed x
|
|
101
|
+
refuse "$dir" mcp serve
|
|
102
|
+
refuse "$dir" sync --help
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
# CONTROL — the detector must be able to see a write. Two parts, because a
|
|
106
|
+
# control that FAILS TO RUN is indistinguishable from a detector that cannot
|
|
107
|
+
# see, unless its exit code is checked. The first version of this script
|
|
108
|
+
# discarded both; `catalogue graph` errored on the first repo, wrote nothing,
|
|
109
|
+
# and the run reported the detector blind when the control had simply not run.
|
|
110
|
+
# 1. synthetic: a file written into a clone must show as dirty;
|
|
111
|
+
# 2. real: the full CLI's `catalogue graph` (writes by default) must EXIT 0
|
|
112
|
+
# on at least one repo and then dirty it.
|
|
113
|
+
ctl="$TMP/control"; git clone -q --no-local "${REPOS[0]}" "$ctl"
|
|
114
|
+
echo probe >"$ctl/.acceptance-probe"
|
|
115
|
+
if clean "$ctl"; then FAIL=1; echo "CONTROL ⚠ a synthetic write was not seen — the detector is blind"
|
|
116
|
+
else echo "CONTROL synthetic write seen"; fi
|
|
117
|
+
real_ok=0
|
|
118
|
+
for repo in "${REPOS[@]}"; do
|
|
119
|
+
c="$TMP/ctl-$(basename "$repo")"; git clone -q --no-local "$repo" "$c"
|
|
120
|
+
( cd "$c" && node "$CONTROL" catalogue graph --sdd-path . >"$TMP/ctl.out" 2>&1 ); rc=$?
|
|
121
|
+
if [ "$rc" -ne 0 ]; then
|
|
122
|
+
echo "CONTROL catalogue graph on $(basename "$repo"): exit $rc, did not run — $(head -c 110 "$TMP/ctl.out")"
|
|
123
|
+
continue
|
|
124
|
+
fi
|
|
125
|
+
if clean "$c"; then FAIL=1; echo "CONTROL ⚠ catalogue graph exited 0 on $(basename "$repo") and the detector saw no write"
|
|
126
|
+
else echo "CONTROL catalogue graph on $(basename "$repo"): exit 0, $(git -C "$c" status --porcelain --ignored | wc -l) path(s) written and SEEN"; fi
|
|
127
|
+
real_ok=1; break
|
|
128
|
+
done
|
|
129
|
+
[ "$real_ok" -eq 1 ] || { FAIL=1; echo "CONTROL ⚠ INVALID — the authoring control ran successfully on no repository, so no write was ever demonstrated"; }
|
|
130
|
+
|
|
131
|
+
# COVERAGE — every surface item must have been exercised somewhere.
|
|
132
|
+
missing=()
|
|
133
|
+
for s in "${SURFACE[@]}"; do [ -n "${SEEN[$s]:-}" ] || missing+=("$s"); done
|
|
134
|
+
if [ ${#missing[@]} -gt 0 ]; then
|
|
135
|
+
FAIL=1; echo "COVERAGE ⚠ never exercised: ${missing[*]}"
|
|
136
|
+
else
|
|
137
|
+
echo "COVERAGE every surface verb and mode exercised (${#SURFACE[@]}/${#SURFACE[@]}) across $RUNS runs"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
[ "$FAIL" -eq 0 ] && echo "ACCEPTANCE PASS — sdd-verify wrote nothing, anywhere" || echo "ACCEPTANCE FAIL"
|
|
141
|
+
exit "$FAIL"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conformance-bin.d.ts","sourceRoot":"","sources":["../src/conformance-bin.ts"],"names":[],"mappings":""}
|
package/dist/conformance-bin.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { runConformance } from "./conformance-program.js";
|
|
3
|
-
runConformance(process.argv.slice(2)).catch((err) => {
|
|
4
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5
|
-
process.stderr.write(`sdd-conformance: ${message}\n`);
|
|
6
|
-
process.exit(1);
|
|
7
|
-
});
|
|
8
|
-
//# sourceMappingURL=conformance-bin.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conformance-bin.js","sourceRoot":"","sources":["../src/conformance-bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC3D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,IAAI,CAAC,CAAC;IACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
/** The verbs this binary exposes. Asserted by test, not by convention. */
|
|
3
|
-
export declare const CONFORMANCE_VERBS: ReadonlyArray<string>;
|
|
4
|
-
export declare function runConformance(argv: readonly string[]): Promise<void>;
|
|
5
|
-
export declare function buildConformanceProgram(): Command;
|
|
6
|
-
//# sourceMappingURL=conformance-program.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conformance-program.d.ts","sourceRoot":"","sources":["../src/conformance-program.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AASpD,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,MAAM,CAAgC,CAAC;AAErF,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAwBjD"}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Second entrypoint: the CONFORMANCE surface only.
|
|
2
|
-
//
|
|
3
|
-
// M-ADR 0212 §4.6 Phase 0 — this file exists so the conformance client can be
|
|
4
|
-
// published alongside the full CLI without copying a line of verb source. It
|
|
5
|
-
// imports the SAME register functions the full program uses, so the two
|
|
6
|
-
// binaries cannot drift in behaviour: there is one implementation and two
|
|
7
|
-
// command surfaces over it. Adding a verb here is a deliberate act; nothing
|
|
8
|
-
// arrives by inheritance.
|
|
9
|
-
//
|
|
10
|
-
// The surface is what automated pipelines were MEASURED to invoke — conformance
|
|
11
|
-
// validation, distribution sync, packaging — and deliberately excludes the
|
|
12
|
-
// authoring verbs (catalogue, metamodel, init, scaffold, MCP, render, plan),
|
|
13
|
-
// which measurement showed pipelines never call.
|
|
14
|
-
import { Command, CommanderError } from "commander";
|
|
15
|
-
import { getPackageVersion } from "./lib/package-version.js";
|
|
16
|
-
import { registerPack } from "./verbs/pack.js";
|
|
17
|
-
import { registerSync } from "./verbs/sync.js";
|
|
18
|
-
import { registerValidate } from "./verbs/validate.js";
|
|
19
|
-
const SPEC_URL = "https://github.com/tomfosteruk/sdd-method/blob/main/docs/method/cli/sdd-cli-specification.md";
|
|
20
|
-
/** The verbs this binary exposes. Asserted by test, not by convention. */
|
|
21
|
-
export const CONFORMANCE_VERBS = ["sync", "validate", "pack"];
|
|
22
|
-
export async function runConformance(argv) {
|
|
23
|
-
const program = buildConformanceProgram();
|
|
24
|
-
try {
|
|
25
|
-
if (argv.length === 0) {
|
|
26
|
-
program.outputHelp();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
await program.parseAsync([...argv], { from: "user" });
|
|
30
|
-
}
|
|
31
|
-
catch (err) {
|
|
32
|
-
if (err instanceof CommanderError) {
|
|
33
|
-
if (err.code === "commander.help" ||
|
|
34
|
-
err.code === "commander.helpDisplayed" ||
|
|
35
|
-
err.code === "commander.version") {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
throw err;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export function buildConformanceProgram() {
|
|
43
|
-
const program = new Command();
|
|
44
|
-
program
|
|
45
|
-
.name("sdd-conformance")
|
|
46
|
-
.description("Conformance surface for SDD repositories — validate, sync, pack. The authoring verbs live in sdd-cli.")
|
|
47
|
-
.version(getPackageVersion(), "-v, --version")
|
|
48
|
-
.addHelpText("after", `\nSpec: ${SPEC_URL}\nAuthoring verbs (catalogue, metamodel, init, scaffold, mcp) are in @sdd-method/sdd-cli.`)
|
|
49
|
-
.exitOverride()
|
|
50
|
-
.configureOutput({
|
|
51
|
-
writeOut: (str) => process.stdout.write(str),
|
|
52
|
-
writeErr: (str) => process.stderr.write(str),
|
|
53
|
-
});
|
|
54
|
-
registerSync(program);
|
|
55
|
-
registerValidate(program);
|
|
56
|
-
registerPack(program);
|
|
57
|
-
return program;
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=conformance-program.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conformance-program.js","sourceRoot":"","sources":["../src/conformance-program.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,wEAAwE;AACxE,0EAA0E;AAC1E,4EAA4E;AAC5E,0BAA0B;AAC1B,EAAE;AACF,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,iDAAiD;AAEjD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,QAAQ,GACZ,8FAA8F,CAAC;AAEjG,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAA0B,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AAErF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAuB;IAC1D,MAAM,OAAO,GAAG,uBAAuB,EAAE,CAAC;IAC1C,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;YAClC,IACE,GAAG,CAAC,IAAI,KAAK,gBAAgB;gBAC7B,GAAG,CAAC,IAAI,KAAK,yBAAyB;gBACtC,GAAG,CAAC,IAAI,KAAK,mBAAmB,EAChC,CAAC;gBACD,OAAO;YACT,CAAC;QACH,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,iBAAiB,CAAC;SACvB,WAAW,CACV,uGAAuG,CACxG;SACA,OAAO,CAAC,iBAAiB,EAAE,EAAE,eAAe,CAAC;SAC7C,WAAW,CACV,OAAO,EACP,WAAW,QAAQ,2FAA2F,CAC/G;SACA,YAAY,EAAE;SACd,eAAe,CAAC;QACf,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5C,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;KAC7C,CAAC,CAAC;IAEL,YAAY,CAAC,OAAO,CAAC,CAAC;IACtB,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1B,YAAY,CAAC,OAAO,CAAC,CAAC;IAEtB,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
// Assemble the publishable @sdd-method/conformance-cli package.
|
|
2
|
-
//
|
|
3
|
-
// M-ADR 0212 §4.6 condition 2 — ONE SOURCE, TWO ARTEFACTS. Nothing here is a
|
|
4
|
-
// second editable copy: the compiled output is produced once by `npm run build`
|
|
5
|
-
// and this script arranges it under a second package manifest. The conformance
|
|
6
|
-
// binary imports the same compiled verb modules the full CLI does, so the two
|
|
7
|
-
// cannot drift in behaviour; only the command surface differs.
|
|
8
|
-
//
|
|
9
|
-
// The assembled directory is generated, gitignored, and safe to delete.
|
|
10
|
-
|
|
11
|
-
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
12
|
-
import { dirname, join } from "node:path";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
-
|
|
15
|
-
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
-
const OUT = join(ROOT, "build", "conformance-cli");
|
|
17
|
-
|
|
18
|
-
const main = await readFile(join(ROOT, "package.json"), "utf8").then(JSON.parse);
|
|
19
|
-
|
|
20
|
-
// The version is deliberately NOT inherited from the full CLI. The two
|
|
21
|
-
// artefacts version independently: a catalogue-engine release must not appear
|
|
22
|
-
// to move the conformance client, which is the whole point of separating them.
|
|
23
|
-
const version = process.env.CONFORMANCE_VERSION ?? "0.1.0";
|
|
24
|
-
|
|
25
|
-
const pkg = {
|
|
26
|
-
name: "@sdd-method/conformance-cli",
|
|
27
|
-
version,
|
|
28
|
-
description:
|
|
29
|
-
"Conformance surface for SDD repositories — validate, sync, pack. Built from the same source as @sdd-method/sdd-cli; the authoring verbs live there.",
|
|
30
|
-
license: main.license,
|
|
31
|
-
type: main.type,
|
|
32
|
-
bin: { "sdd-conformance": "dist/conformance-bin.js" },
|
|
33
|
-
engines: main.engines,
|
|
34
|
-
dependencies: main.dependencies,
|
|
35
|
-
repository: main.repository,
|
|
36
|
-
files: ["dist", "scripts", "README.md", "LICENSE", "NOTICE"],
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
await rm(OUT, { recursive: true, force: true });
|
|
40
|
-
await mkdir(OUT, { recursive: true });
|
|
41
|
-
await cp(join(ROOT, "dist"), join(OUT, "dist"), { recursive: true });
|
|
42
|
-
await cp(join(ROOT, "scripts"), join(OUT, "scripts"), { recursive: true });
|
|
43
|
-
for (const f of ["LICENSE", "NOTICE"]) {
|
|
44
|
-
await cp(join(ROOT, f), join(OUT, f));
|
|
45
|
-
}
|
|
46
|
-
await writeFile(
|
|
47
|
-
join(OUT, "README.md"),
|
|
48
|
-
`# @sdd-method/conformance-cli\n\n` +
|
|
49
|
-
`The conformance surface of the SDD method CLI: \`sdd-conformance sync\`, \`validate\`, \`pack\`.\n\n` +
|
|
50
|
-
`Built from the same source as \`@sdd-method/sdd-cli\` — the verbs are the same implementations, ` +
|
|
51
|
-
`exposed under a smaller surface for automated pipelines, which were measured to invoke only these.\n\n` +
|
|
52
|
-
`Authoring verbs — catalogue, metamodel, init, scaffold, mcp, render, plan — are in \`@sdd-method/sdd-cli\`.\n\n` +
|
|
53
|
-
`This package is additive: it changes nothing about \`@sdd-method/sdd-cli\`, which continues to carry every verb.\n`,
|
|
54
|
-
);
|
|
55
|
-
await writeFile(join(OUT, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
56
|
-
|
|
57
|
-
process.stdout.write(`assembled ${pkg.name}@${version} at build/conformance-cli\n`);
|