@sdd-method/sdd-cli 1.42.0 → 1.43.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.
Files changed (114) hide show
  1. package/README.md +4 -57
  2. package/dist/author-bin.d.ts +3 -0
  3. package/dist/author-bin.d.ts.map +1 -0
  4. package/dist/author-bin.js +8 -0
  5. package/dist/author-bin.js.map +1 -0
  6. package/dist/author-program.d.ts +8 -0
  7. package/dist/author-program.d.ts.map +1 -0
  8. package/dist/author-program.js +99 -0
  9. package/dist/author-program.js.map +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +0 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/agent-harness.d.ts +11 -8
  14. package/dist/lib/agent-harness.d.ts.map +1 -1
  15. package/dist/lib/agent-harness.js +17 -8
  16. package/dist/lib/agent-harness.js.map +1 -1
  17. package/dist/lib/briefing/index.d.ts +1 -1
  18. package/dist/lib/briefing/index.d.ts.map +1 -1
  19. package/dist/lib/briefing/index.js +1 -1
  20. package/dist/lib/briefing/index.js.map +1 -1
  21. package/dist/lib/briefing/render.d.ts +18 -1
  22. package/dist/lib/briefing/render.d.ts.map +1 -1
  23. package/dist/lib/briefing/render.js +17 -8
  24. package/dist/lib/briefing/render.js.map +1 -1
  25. package/dist/lib/catalogue/aggregation-manifest-loader.js +4 -4
  26. package/dist/lib/catalogue/aggregation-manifest-loader.js.map +1 -1
  27. package/dist/lib/catalogue/freshness.d.ts +1 -0
  28. package/dist/lib/catalogue/freshness.d.ts.map +1 -1
  29. package/dist/lib/catalogue/freshness.js +4 -3
  30. package/dist/lib/catalogue/freshness.js.map +1 -1
  31. package/dist/lib/context-pull/index.d.ts +15 -9
  32. package/dist/lib/context-pull/index.d.ts.map +1 -1
  33. package/dist/lib/context-pull/index.js +79 -39
  34. package/dist/lib/context-pull/index.js.map +1 -1
  35. package/dist/lib/context-pull/retired-mcp-setup.d.ts +7 -0
  36. package/dist/lib/context-pull/retired-mcp-setup.d.ts.map +1 -0
  37. package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
  38. package/dist/lib/context-pull/retired-mcp-setup.js.map +1 -0
  39. package/dist/lib/hooks-seed/entries.d.ts +15 -0
  40. package/dist/lib/hooks-seed/entries.d.ts.map +1 -1
  41. package/dist/lib/hooks-seed/entries.js +30 -1
  42. package/dist/lib/hooks-seed/entries.js.map +1 -1
  43. package/dist/lib/hooks-seed/index.d.ts +4 -9
  44. package/dist/lib/hooks-seed/index.d.ts.map +1 -1
  45. package/dist/lib/hooks-seed/index.js +4 -7
  46. package/dist/lib/hooks-seed/index.js.map +1 -1
  47. package/dist/lib/hooks-seed/merge.d.ts +7 -0
  48. package/dist/lib/hooks-seed/merge.d.ts.map +1 -1
  49. package/dist/lib/hooks-seed/merge.js +31 -0
  50. package/dist/lib/hooks-seed/merge.js.map +1 -1
  51. package/dist/lib/init/app.d.ts +0 -1
  52. package/dist/lib/init/app.d.ts.map +1 -1
  53. package/dist/lib/init/app.js +0 -7
  54. package/dist/lib/init/app.js.map +1 -1
  55. package/dist/lib/init/integration.d.ts +0 -1
  56. package/dist/lib/init/integration.d.ts.map +1 -1
  57. package/dist/lib/init/integration.js +0 -7
  58. package/dist/lib/init/integration.js.map +1 -1
  59. package/dist/lib/init/platform.d.ts +2 -10
  60. package/dist/lib/init/platform.d.ts.map +1 -1
  61. package/dist/lib/init/platform.js +6 -13
  62. package/dist/lib/init/platform.js.map +1 -1
  63. package/dist/lib/plan/index.d.ts.map +1 -1
  64. package/dist/lib/plan/index.js +4 -4
  65. package/dist/lib/plan/index.js.map +1 -1
  66. package/dist/lib/plan/read-sdd.js +1 -1
  67. package/dist/lib/plan/render-template.d.ts +8 -0
  68. package/dist/lib/plan/render-template.d.ts.map +1 -1
  69. package/dist/lib/plan/render-template.js +27 -3
  70. package/dist/lib/plan/render-template.js.map +1 -1
  71. package/dist/lib/sdd-index/repo-config.d.ts +1 -1
  72. package/dist/lib/sdd-index/repo-config.js +1 -1
  73. package/dist/lib/sdd-reader.d.ts +35 -0
  74. package/dist/lib/sdd-reader.d.ts.map +1 -0
  75. package/dist/lib/sdd-reader.js +91 -0
  76. package/dist/lib/sdd-reader.js.map +1 -0
  77. package/dist/lib/sync/briefing-merge.d.ts +1 -1
  78. package/dist/lib/sync/briefing-merge.d.ts.map +1 -1
  79. package/dist/lib/sync/briefing-merge.js +50 -34
  80. package/dist/lib/sync/briefing-merge.js.map +1 -1
  81. package/dist/lib/sync/diff.js +1 -1
  82. package/dist/lib/sync/diff.js.map +1 -1
  83. package/dist/lib/sync/index.d.ts +3 -6
  84. package/dist/lib/sync/index.d.ts.map +1 -1
  85. package/dist/lib/sync/index.js +20 -70
  86. package/dist/lib/sync/index.js.map +1 -1
  87. package/dist/lib/sync/settings-merge.d.ts +26 -48
  88. package/dist/lib/sync/settings-merge.d.ts.map +1 -1
  89. package/dist/lib/sync/settings-merge.js +70 -162
  90. package/dist/lib/sync/settings-merge.js.map +1 -1
  91. package/dist/maintain-program.d.ts.map +1 -1
  92. package/dist/maintain-program.js +5 -2
  93. package/dist/maintain-program.js.map +1 -1
  94. package/dist/maintain-version.json +2 -2
  95. package/dist/verbs/catalogue-check.d.ts +10 -0
  96. package/dist/verbs/catalogue-check.d.ts.map +1 -0
  97. package/dist/verbs/catalogue-check.js +28 -0
  98. package/dist/verbs/catalogue-check.js.map +1 -0
  99. package/dist/verbs/catalogue.d.ts.map +1 -1
  100. package/dist/verbs/catalogue.js +2 -0
  101. package/dist/verbs/catalogue.js.map +1 -1
  102. package/dist/verbs/context.js +1 -1
  103. package/dist/verbs/context.js.map +1 -1
  104. package/dist/verify-program.js +1 -1
  105. package/dist/verify-program.js.map +1 -1
  106. package/dist/verify-version.json +2 -2
  107. package/package.json +5 -5
  108. package/scripts/author-acceptance.mjs +170 -0
  109. package/scripts/author-version.json +4 -0
  110. package/scripts/build-persona-package.mjs +46 -13
  111. package/scripts/maintain-version.json +2 -2
  112. package/scripts/release-gate.mjs +1 -1
  113. package/scripts/verify-version.json +2 -2
  114. package/scripts/vitest-tmpdir.mjs +26 -0
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ // Acceptance for the author CLI — M-ADR 0220 §4.3, the scope layer.
3
+ //
4
+ // The property: no author verb writes into the MAINTAIN-ONLY part of the derived-and-managed set —
5
+ // the lock and the managed payload it records (0218 §4.3 items 1–2), the CI wrapper and its module
6
+ // (item 3), the harness settings and hooks (item 5), ADR transitions (item 6, which land in
7
+ // lock-managed ADR files), the derived model (item 7) or a pack (item 8). Tested, not asserted: each
8
+ // SDD is cloned fresh, every author verb this run can exercise is run, and after each run every path
9
+ // `git status --porcelain --ignored` reports is checked against that part of the set.
10
+ //
11
+ // Also, per verb: (b) PARITY, the full sdd-cli (--control-bin) on a twin clone leaves the same tree
12
+ // and exit code, because scope cannot see a verb that crashes or renders differently; (c) a CRASH
13
+ // (sdd-author: <message> on stderr) fails whatever the exit code. Fails closed on the CONTROL: a
14
+ // write into the maintain-only part must be SEEN, or every PASS is vacuous. Verbs it cannot exercise
15
+ // are listed under COVERAGE, never passed silently. Author verbs need not be deterministic (that is
16
+ // maintain's property), so a verb runs once per clone.
17
+ //
18
+ // usage: author-acceptance.mjs --bin <author-bin.js> --control-bin <sdd-cli cli.js> <sdd-repo>...
19
+
20
+ import { execFileSync, spawnSync } from "node:child_process";
21
+ import { createHash } from "node:crypto";
22
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
23
+ import { tmpdir } from "node:os";
24
+ import { basename, dirname, join, resolve } from "node:path";
25
+
26
+ const args = process.argv.slice(2);
27
+ const opt = (k) => { const i = args.indexOf(k); if (i === -1) return undefined; const v = args[i + 1]; args.splice(i, 2); return v; };
28
+ const BIN = resolve(opt("--bin") ?? die("--bin required"));
29
+ const CONTROL = resolve(opt("--control-bin") ?? die("--control-bin required (the full sdd-cli, for parity)"));
30
+ const REPOS = args.map((a) => resolve(a));
31
+ if (REPOS.length === 0) die("give at least one SDD repository");
32
+
33
+ function die(m) { process.stderr.write(`author-acceptance: ${m}\n`); process.exit(2); }
34
+ let FAIL = 0;
35
+ const fail = (m) => { FAIL++; process.stdout.write(` FAIL ${m}\n`); };
36
+ const ok = (m) => process.stdout.write(` ok ${m}\n`);
37
+ const exercised = new Set();
38
+
39
+ // ---- the maintain-only part of the set (M-ADR 0220 §4.3) -------------------------------------
40
+ const MAINTAIN_ONLY = [
41
+ /^method-baseline\.lock$/, /^\.sdd-repo-kind$/, // items 1–2
42
+ /^\.github\/workflows\/method-validation\.yml$/, /^\.gitlab-ci\.yml$/,
43
+ /^\.jenkins\/method-validation\.Jenkinsfile$/, /^\.dagger\/method-validation\//, // item 3
44
+ /^\.claude\/settings\.json$/, /^\.github\/hooks\//, // item 5
45
+ /^catalogues\//, /^catalogues-aggregated\//, /^catalogues(-aggregated)?-graph\.jsonl$/, // item 7
46
+ /^\.sdd\/bundle-cache\//,
47
+ ];
48
+ function lockManaged(repo) {
49
+ const p = join(repo, "method-baseline.lock");
50
+ if (!existsSync(p)) return new Set();
51
+ const out = new Set();
52
+ for (const line of readFileSync(p, "utf8").split("\n")) {
53
+ const m = /^ {6}- "?(?:[0-9a-f]{64} {2})?([^"]+)"?$/.exec(line);
54
+ if (m) out.add(m[1]);
55
+ }
56
+ return out;
57
+ }
58
+ function changed(repo) {
59
+ const st = spawnSync("git", ["-C", repo, "status", "--porcelain", "--ignored", "--untracked-files=all"], { encoding: "utf8" });
60
+ return st.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).split(" -> ").pop().replace(/^"|"$/g, ""));
61
+ }
62
+ const intoMaintainOnly = (repo, managed) => changed(repo).filter((p) => managed.has(p) || MAINTAIN_ONLY.some((rx) => rx.test(p)));
63
+ function snapshot(repo, roots) {
64
+ const h = createHash("sha256");
65
+ for (const p of changed(repo).sort()) {
66
+ h.update(p + "\0");
67
+ const abs = join(repo, p);
68
+ if (existsSync(abs) && statSync(abs).isFile()) {
69
+ let text = readFileSync(abs, "latin1");
70
+ for (const r of roots) text = text.split(r).join("<WS>");
71
+ h.update(text, "latin1");
72
+ }
73
+ }
74
+ return h.digest("hex");
75
+ }
76
+
77
+ // ---- workspace (removed on every exit; AUTHOR_ACC_KEEP=1 keeps it) ----------------------------
78
+ const workspaces = [];
79
+ process.on("exit", () => {
80
+ if (process.env.AUTHOR_ACC_KEEP) return void process.stderr.write(`author-acceptance: kept ${workspaces.join(" ")}\n`);
81
+ for (const ws of workspaces) rmSync(ws, { recursive: true, force: true });
82
+ });
83
+ function fresh(repo) {
84
+ const ws = mkdtempSync(join(tmpdir(), "author-acc-"));
85
+ workspaces.push(ws);
86
+ const clone = join(ws, basename(repo));
87
+ execFileSync("git", ["clone", "-q", "--no-hardlinks", repo, clone]);
88
+ return { ws, clone };
89
+ }
90
+ const run = (cwd, verbArgs, bin) => spawnSync("node", [bin, ...verbArgs], { cwd, encoding: "utf8" });
91
+
92
+ // One verb on a fresh pair of clones: the author's run is scope-checked, the control's is its twin.
93
+ function check(label, repo, verbArgsFor, opts = {}) {
94
+ const a = fresh(repo);
95
+ const b = fresh(repo);
96
+ try {
97
+ checkPair(label, a, b, verbArgsFor, opts);
98
+ } finally {
99
+ if (!process.env.AUTHOR_ACC_KEEP) for (const w of [a.ws, b.ws]) { rmSync(w, { recursive: true, force: true }); workspaces.splice(workspaces.indexOf(w), 1); }
100
+ }
101
+ }
102
+ function checkPair(label, a, b, verbArgsFor, { cwdFor = (c) => c, inspect = (c) => c, allowNonZero = true } = {}) {
103
+ const r = run(cwdFor(a.clone, a.ws), verbArgsFor(a.clone, a.ws), BIN);
104
+ const crashed = /^sdd-author: /m.test(r.stderr ?? "");
105
+ if (crashed || (r.status !== 0 && !allowNonZero)) {
106
+ fail(`${label}: ${crashed ? "CRASHED" : `exit ${r.status}`} — ${(r.stderr || r.stdout).trim().split("\n").pop()}`);
107
+ return;
108
+ }
109
+ const target = inspect(a.clone, a.ws);
110
+ const bad = intoMaintainOnly(target, lockManaged(a.clone));
111
+ if (bad.length) fail(`${label}: wrote into the maintain-only set: ${bad.slice(0, 5).join(", ")}`);
112
+ const rc = run(cwdFor(b.clone, b.ws), verbArgsFor(b.clone, b.ws), CONTROL);
113
+ const roots = [a.ws, b.ws];
114
+ const parity = snapshot(target, roots) === snapshot(inspect(b.clone, b.ws), roots) && rc.status === r.status;
115
+ if (!parity) fail(`${label}: PARITY — sdd-cli left a different tree or exited ${rc.status} where sdd-author exited ${r.status}`);
116
+ if (!bad.length && parity) ok(`${label} (exit ${r.status}; wrote ${changed(target).length} path(s)): none in the maintain-only set, = sdd-cli`);
117
+ exercised.add(label.split(" @")[0]);
118
+ }
119
+
120
+ // ---- control: the detector must see a write into the maintain-only set -----------------------
121
+ {
122
+ const { clone } = fresh(REPOS[0]);
123
+ mkdirSync(join(clone, "catalogues"), { recursive: true });
124
+ writeFileSync(join(clone, "catalogues", "CONTROL.csv"), "x\n");
125
+ writeFileSync(join(clone, "method-baseline.lock"), readFileSync(join(clone, "method-baseline.lock"), "utf8") + "# control\n");
126
+ const seen = intoMaintainOnly(clone, lockManaged(clone));
127
+ if (seen.includes("catalogues/CONTROL.csv") && seen.includes("method-baseline.lock")) ok("CONTROL: writes into the maintain-only set are seen");
128
+ else fail(`CONTROL: a write into the maintain-only set was NOT seen (${seen.join(", ")}) — every scope PASS below would be vacuous`);
129
+ }
130
+
131
+ for (const repo of REPOS) {
132
+ const name = basename(repo);
133
+ process.stdout.write(`=== ${name} ===\n`);
134
+ const kind = existsSync(join(repo, ".sdd-repo-kind")) ? readFileSync(join(repo, ".sdd-repo-kind"), "utf8").trim() : "";
135
+ check(`catalogue check @${name}`, repo, () => ["catalogue", "check", "--sdd-path", "."]);
136
+ check(`generate-briefing @${name}`, repo, () => ["generate-briefing", "--target-dir", "."]);
137
+ check(`scaffold domain @${name}`, repo, () => ["scaffold", "domain", "author-acc-probe", "--target-repo", "."]);
138
+ check(`plan @${name}`, repo, () => ["plan", "--pilot", "--service", "acc-probe", "--capability", "acc-probe", "--domain", "acc-probe", "--workspace-root", "."]);
139
+ check(`contracts attest @${name}`, repo, () => ["contracts", "attest", "--target-repo", ".", "--refresh"]);
140
+ check(`conformance @${name}`, repo, (_c, ws) => ["conformance", "--target-repo", ".", "--emit-finding", join(ws, "findings")]);
141
+ check(`api-surface @${name}`, repo, (_c, ws) => ["api-surface", "--source", ".", "--out", join(ws, "surface.yaml")]);
142
+ const screens = join(repo, "screens");
143
+ if (existsSync(screens)) {
144
+ const spec = readdirSync(screens).find((f) => f.endsWith(".md"));
145
+ if (spec) check(`render screen @${name}`, repo, (_c, ws) => ["render", "screen", join("screens", spec), "--out", join(ws, "screen.svg")]);
146
+ }
147
+ // context pull: a scratch code repository beside the clone, named for a repository this SDD catalogues.
148
+ const repoCsv = join(repo, "catalogues", "repositories.csv");
149
+ if (existsSync(repoCsv) && existsSync(join(repo, "orchestration/templates/briefing/code-repo/agent-instructions.md.hbs"))) {
150
+ const id = readFileSync(repoCsv, "utf8").split("\n")[1]?.split(",")[0];
151
+ if (id) {
152
+ const codeOf = (ws) => {
153
+ const c = join(ws, id);
154
+ if (!existsSync(c)) { mkdirSync(c, { recursive: true }); execFileSync("git", ["-C", c, "init", "-q"]); }
155
+ return c;
156
+ };
157
+ // Scope is checked on the SDD clone (the code repository is not an SDD); parity on the code repository.
158
+ check(`context pull @${name}`, repo, (c) => ["context", "pull", "--sdd", c], { cwdFor: (_c, ws) => codeOf(ws), inspect: (c, ws) => { const bad = intoMaintainOnly(c, lockManaged(c)); if (bad.length) fail(`context pull @${name}: wrote into the SDD's maintain-only set: ${bad.join(", ")}`); return codeOf(ws); } });
159
+ }
160
+ }
161
+ void kind;
162
+ }
163
+
164
+ const SURFACE = ["catalogue check", "generate-briefing", "scaffold domain", "plan", "contracts attest", "conformance", "api-surface", "render screen", "context pull",
165
+ "snapshot fold", "snapshot land", "snapshot verify", "snapshot diff", "snapshot targets", "snapshot advance"];
166
+ const missing = SURFACE.filter((v) => !exercised.has(v));
167
+ process.stdout.write(`COVERAGE exercised: ${[...exercised].join(", ")}\n`);
168
+ if (missing.length) process.stdout.write(`COVERAGE NOT exercised here (needs inputs this run lacks, e.g. a snapshot store): ${missing.join(", ")}\n`);
169
+ process.stdout.write(FAIL === 0 ? "ACCEPTANCE PASS — no author write reached the maintain-only set\n" : `ACCEPTANCE FAIL (${FAIL})\n`);
170
+ process.exit(FAIL === 0 ? 0 : 1);
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": "0.1.0",
3
+ "built_against_sdd_cli": "1.43.0"
4
+ }
@@ -1,5 +1,5 @@
1
1
  // Assemble a publishable persona package — @sdd-method/verify-cli (M-ADR 0212 §4.3b) or
2
- // @sdd-method/maintain-cli (M-ADR 0218). Usage: build-persona-package.mjs [verify|maintain]
2
+ // @sdd-method/maintain-cli (M-ADR 0218) or @sdd-method/author-cli (M-ADR 0220). Usage: build-persona-package.mjs [verify|maintain|author]
3
3
  // (default verify). One script, so both packages hold the same boundary discipline.
4
4
  //
5
5
  // ONE SOURCE, and this time a genuinely smaller ARTEFACT. The conformance
@@ -32,26 +32,40 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
32
32
  // into build/check-dist and checks that instead; only a release should write dist/.
33
33
  const DIST = process.env.SDD_CLI_DIST ? resolve(process.env.SDD_CLI_DIST) : join(ROOT, "dist");
34
34
  const PERSONA = process.argv[2] ?? "verify";
35
- // Modules that must never reach a package. Authoring for both; maintain's own verbs are
36
- // canaries only for verify (they are maintain's payload). Each list names modules that EXIST,
37
- // or the check below fails rather than passing vacuously.
38
- const AUTHOR_CANARIES = [
35
+ // Modules that must never reach a package. Each list names modules that EXIST, or the check below
36
+ // fails rather than passing vacuously. ADMIN: sdd-cli's alone (M-ADR 0220 §4.2). AUTHOR_ONLY: the
37
+ // author's own verbs, canaries for verify and maintain. MAINTAIN_WRITERS: the sync engine and the
38
+ // model and pack writers, canaries for verify and author. The catalogue BUILDER is not one for the
39
+ // author: `catalogue check` builds into temporary space through it (0220 §4.3 names the writers).
40
+ const ADMIN_CANARIES = [
39
41
  "cli.js",
40
42
  "index.js",
41
- "verbs/mcp.js",
42
43
  "verbs/init.js",
43
44
  "verbs/metamodel.js",
45
+ "verbs/catalogue.js",
46
+ "lib/catalogue/migration-map.js",
47
+ ];
48
+ const AUTHOR_ONLY_MODULES = [
44
49
  "verbs/conformance.js",
45
50
  "verbs/scaffold.js",
46
51
  "verbs/plan.js",
47
52
  "verbs/contracts.js",
48
53
  "verbs/api-surface.js",
49
54
  "verbs/snapshot.js",
50
- "verbs/catalogue.js",
51
- "lib/catalogue/migration-map.js",
52
- "lib/mcp/server.js",
53
55
  "lib/snapshot/land.js",
54
56
  ];
57
+ const MAINTAIN_WRITERS = [
58
+ "verbs/sync.js",
59
+ "verbs/catalogue-maintain.js",
60
+ "verbs/pack.js",
61
+ "verbs/pack-build.js",
62
+ "lib/sync/index.js",
63
+ "lib/catalogue/aggregate.js",
64
+ "lib/catalogue/graph-run.js",
65
+ "lib/snapshot/catalogue-diff.js",
66
+ "lib/pack/builder.js",
67
+ ];
68
+ const AUTHOR_CANARIES = [...ADMIN_CANARIES, ...AUTHOR_ONLY_MODULES];
55
69
  const PERSONAS = {
56
70
  verify: {
57
71
  entry: "verify-bin.js",
@@ -79,9 +93,28 @@ const PERSONAS = {
79
93
  `Verbs: \`validate\`, \`list\`, \`pack validate\`, \`catalogue validate\` (incl. \`--coverage\`), ` +
80
94
  `\`catalogue impact\`, \`catalogue baseline-check\`, \`catalogue version\`.\n\n` +
81
95
  `The package contains only the code those verbs reach. Authoring and maintenance verbs — ` +
82
- `\`sync\`, \`catalogue build\`, \`init\`, \`scaffold\`, \`mcp\` and the rest — are not present in it, ` +
96
+ `\`sync\`, \`catalogue build\`, \`init\`, \`scaffold\` and the rest — are not present in it, ` +
83
97
  `and are in \`@sdd-method/sdd-cli\`. Invoking one fails with exit 1.\n`,
84
98
  },
99
+ author: {
100
+ entry: "author-bin.js",
101
+ out: "author-cli",
102
+ versionFile: "author-version.json",
103
+ versionEnv: "AUTHOR_VERSION",
104
+ name: "@sdd-method/author-cli",
105
+ bin: "sdd-author",
106
+ canaries: [...ADMIN_CANARIES, ...MAINTAIN_WRITERS],
107
+ description:
108
+ "Re-author one SDD's content — scaffold, plan, contracts, conformance, api-surface, snapshots, briefings, catalogue check — plus every read-only verify verb. Ships only the code those verbs reach; it cannot sync the baseline or rebuild the catalogues. Maintenance is in @sdd-method/maintain-cli, administration in @sdd-method/sdd-cli.",
109
+ readme:
110
+ `# @sdd-method/author-cli\n\n` +
111
+ `\`sdd-author\` is for the people and agents who own one SDD's content. It re-authors that content and **cannot move the baseline or rebuild the model** (M-ADR 0220).\n\n` +
112
+ `Verbs: everything in \`@sdd-method/verify-cli\`, plus \`scaffold domain\`, \`plan\`, \`contracts attest\`, \`conformance\`, \`api-surface\`, ` +
113
+ `the \`snapshot\` verbs, \`render screen\`, \`generate-briefing\`, \`context pull\` and \`catalogue check\`.\n\n` +
114
+ `\`catalogue check\` reports whether the committed catalogues are what the committed specifications build to, and writes nothing. ` +
115
+ `Rebuilding them (\`catalogue build\`), \`sync\` and \`pack build\` are in \`@sdd-method/maintain-cli\`; \`init\` and the administrator's verbs are in \`@sdd-method/sdd-cli\`. ` +
116
+ `They are not present in this package, and invoking one fails with exit 1.\n`,
117
+ },
85
118
  maintain: {
86
119
  entry: "maintain-bin.js",
87
120
  out: "maintain-cli",
@@ -98,13 +131,13 @@ const PERSONAS = {
98
131
  `Verbs: everything in \`@sdd-method/verify-cli\`, plus \`sync\`, \`generate-briefing\`, \`context pull\`, ` +
99
132
  `\`catalogue build\`, \`catalogue aggregate\`, \`catalogue graph\`, \`catalogue diff\`, \`pack build\`, \`render screen\`.\n\n` +
100
133
  `Each writes only derived or method-managed content, and two runs on the same commit write the same bytes. ` +
101
- `Authoring verbs — \`init\`, \`scaffold\`, \`plan\`, \`mcp\`, \`conformance\` and the rest — are not present in it, ` +
134
+ `Authoring verbs — \`init\`, \`scaffold\`, \`plan\`, \`conformance\` and the rest — are not present in it, ` +
102
135
  `and are in \`@sdd-method/sdd-cli\`. Invoking one fails with exit 1.\n`,
103
136
  },
104
137
  };
105
138
  const P = PERSONAS[PERSONA];
106
139
  if (!P) {
107
- process.stderr.write(`build-persona-package: FAIL — unknown persona '${PERSONA}' (verify | maintain)\n`);
140
+ process.stderr.write(`build-persona-package: FAIL — unknown persona '${PERSONA}' (verify | maintain | author)\n`);
108
141
  process.exit(1);
109
142
  }
110
143
  const OUT = join(ROOT, "build", P.out);
@@ -224,7 +257,7 @@ const versionFile = JSON.parse(readFileSync(join(ROOT, "scripts", P.versionFile)
224
257
  const version = versionFile.version;
225
258
  if (!/^\d+\.\d+\.\d+$/.test(version ?? "")) die(`scripts/${P.versionFile} has no semver \`version\``);
226
259
  // The engine this package is built from. Every engine-meaning check reads it (getSddCliVersion):
227
- // a bundle's floor, catalogue stamps, the MCP identity. A stale built_against in the version file
260
+ // a bundle's floor and catalogue stamps. A stale built_against in the version file
228
261
  // would ship a package claiming an engine it is not, so the build refuses it.
229
262
  if (versionFile.built_against_sdd_cli !== main.version)
230
263
  die(`scripts/${P.versionFile} says built_against_sdd_cli ${versionFile.built_against_sdd_cli}, but this sdd-cli is ${main.version}; update the file`);
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.1.0",
3
- "built_against_sdd_cli": "1.42.0"
2
+ "version": "0.2.0",
3
+ "built_against_sdd_cli": "1.43.0"
4
4
  }
@@ -42,7 +42,7 @@ if (process.argv.includes("--audit")) {
42
42
  // M-ADR 0218 §4.6: the persona packages are released WITH sdd-cli, in one change-set. A release that
43
43
  // bumps sdd-cli without re-stamping a persona's version file would build a package that claims an
44
44
  // engine it is not, so the publish refuses (the build refuses it too).
45
- for (const persona of ["verify", "maintain"]) {
45
+ for (const persona of ["verify", "maintain", "author"]) {
46
46
  const f = JSON.parse(readFileSync(join(root, "scripts", `${persona}-version.json`), "utf-8"));
47
47
  if (f.built_against_sdd_cli !== pkg.version)
48
48
  fail(`scripts/${persona}-version.json says built_against_sdd_cli ${f.built_against_sdd_cli}, but this release is ${pkg.version}; bump the persona's version and re-stamp it in the release commit`);
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.1.0",
3
- "built_against_sdd_cli": "1.42.0"
2
+ "version": "1.2.0",
3
+ "built_against_sdd_cli": "1.43.0"
4
4
  }
@@ -0,0 +1,26 @@
1
+ // Gives each test run its own temp directory and removes it whole when the run ends.
2
+ //
3
+ // Every fixture is created under os.tmpdir(), which reads TMPDIR. Setting TMPDIR here, in the
4
+ // main process before any worker starts, sends every fixture of this run (and of the bash
5
+ // scripts the tests spawn, which honour TMPDIR via mktemp) into one directory, whatever prefix
6
+ // its test chose. vitest-teardown.mjs could only sweep the prefixes it listed: 3 of the 129 the
7
+ // tests used, so the rest accumulated in /tmp (14,203 directories on 2026-10-16). A concurrent
8
+ // run on the same machine has its own directory, so neither run can touch the other's fixtures.
9
+ import { mkdtempSync, rmSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import sweepLegacy from "./vitest-teardown.mjs";
13
+
14
+ export default function setup() {
15
+ const original = process.env.TMPDIR;
16
+ const dir = mkdtempSync(join(tmpdir(), "sdd-cli-vitest-"));
17
+ process.env.TMPDIR = dir;
18
+ return async () => {
19
+ if (process.env.SDD_CLI_KEEP_TMP) process.stdout.write(`vitest-tmpdir: kept ${dir}\n`);
20
+ else rmSync(dir, { recursive: true, force: true });
21
+ // Fixtures from runs before this setup existed sit directly in /tmp; sweep them by prefix and age.
22
+ if (original === undefined) delete process.env.TMPDIR;
23
+ else process.env.TMPDIR = original;
24
+ await sweepLegacy();
25
+ };
26
+ }