@peterxiaoyang/superspec 0.1.14 → 0.1.16-alpha

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 (131) hide show
  1. package/README.md +37 -36
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +301 -0
  4. package/dist/format.d.ts +29 -0
  5. package/dist/format.js +141 -0
  6. package/dist/install.d.ts +18 -0
  7. package/dist/install.js +161 -0
  8. package/dist/next.d.ts +3 -0
  9. package/dist/next.js +161 -0
  10. package/dist/openspec.d.ts +9 -0
  11. package/dist/openspec.js +41 -0
  12. package/dist/record.d.ts +20 -0
  13. package/dist/record.js +266 -0
  14. package/dist/skill_loop.d.ts +23 -0
  15. package/dist/skill_loop.js +58 -0
  16. package/dist/store.d.ts +31 -0
  17. package/dist/store.js +213 -0
  18. package/dist/sync.d.ts +3 -0
  19. package/dist/sync.js +135 -0
  20. package/dist/task.d.ts +7 -0
  21. package/dist/task.js +44 -0
  22. package/dist/transition.d.ts +34 -0
  23. package/dist/transition.js +400 -0
  24. package/dist/types.d.ts +141 -0
  25. package/dist/types.js +3 -0
  26. package/package.json +8 -53
  27. package/templates/workflow/agents/architect.toml +13 -0
  28. package/{adapters/codex → templates/workflow}/agents/code-reviewer.toml +2 -2
  29. package/templates/workflow/agents/critic.toml +13 -0
  30. package/{adapters/codex → templates/workflow}/agents/executor.toml +2 -2
  31. package/{adapters/codex → templates/workflow}/agents/explore.toml +3 -3
  32. package/templates/workflow/agents/final-audit.toml +13 -0
  33. package/templates/workflow/agents/proposal-auditor.toml +13 -0
  34. package/{adapters/codex → templates/workflow}/agents/test-engineer.toml +4 -4
  35. package/{adapters/codex → templates/workflow}/agents/test-runner.toml +2 -2
  36. package/{adapters/codex → templates/workflow}/agents/verifier.toml +2 -2
  37. package/templates/workflow/prompts/architect.md +16 -0
  38. package/templates/workflow/prompts/critic.md +16 -0
  39. package/templates/workflow/prompts/explore.md +1 -1
  40. package/templates/workflow/prompts/final-audit.md +32 -0
  41. package/templates/workflow/prompts/proposal-auditor.md +34 -0
  42. package/templates/workflow/prompts/test-engineer.md +16 -0
  43. package/templates/workflow/skills/superspec-apply/SKILL.md +39 -121
  44. package/templates/workflow/skills/superspec-archive/SKILL.md +17 -49
  45. package/templates/workflow/skills/superspec-explore/SKILL.md +34 -70
  46. package/templates/workflow/skills/superspec-propose/SKILL.md +57 -60
  47. package/templates/workflow/skills/superspec-review/SKILL.md +18 -85
  48. package/adapters/codex/agents/architect.toml +0 -13
  49. package/adapters/codex/agents/critic.toml +0 -13
  50. package/adapters/codex/install-map.json +0 -116
  51. package/bin/launch.js +0 -37
  52. package/bin/superspec-check.js +0 -4
  53. package/bin/superspec-hook.js +0 -4
  54. package/bin/superspec-init.js +0 -4
  55. package/bin/superspec.js +0 -4
  56. package/dist/src/apply_worker_chain.d.ts +0 -60
  57. package/dist/src/apply_worker_chain.js +0 -1283
  58. package/dist/src/apply_worker_chain_lifecycle.d.ts +0 -21
  59. package/dist/src/apply_worker_chain_lifecycle.js +0 -409
  60. package/dist/src/archive.d.ts +0 -23
  61. package/dist/src/archive.js +0 -430
  62. package/dist/src/cli.d.ts +0 -1
  63. package/dist/src/cli.js +0 -33
  64. package/dist/src/cli_args.d.ts +0 -32
  65. package/dist/src/cli_args.js +0 -454
  66. package/dist/src/core.d.ts +0 -20
  67. package/dist/src/core.js +0 -394
  68. package/dist/src/disclosure.d.ts +0 -35
  69. package/dist/src/disclosure.js +0 -671
  70. package/dist/src/doctor.d.ts +0 -44
  71. package/dist/src/doctor.js +0 -230
  72. package/dist/src/evidence.d.ts +0 -29
  73. package/dist/src/evidence.js +0 -1107
  74. package/dist/src/gates.d.ts +0 -19
  75. package/dist/src/gates.js +0 -1631
  76. package/dist/src/git.d.ts +0 -8
  77. package/dist/src/git.js +0 -112
  78. package/dist/src/hooks/adapter.d.ts +0 -5
  79. package/dist/src/hooks/adapter.js +0 -347
  80. package/dist/src/hooks/guard_api.d.ts +0 -12
  81. package/dist/src/hooks/guard_api.js +0 -797
  82. package/dist/src/hooks/health.d.ts +0 -4
  83. package/dist/src/hooks/health.js +0 -86
  84. package/dist/src/hooks/policy_event.d.ts +0 -41
  85. package/dist/src/hooks/policy_event.js +0 -2634
  86. package/dist/src/hooks/types.d.ts +0 -72
  87. package/dist/src/hooks/types.js +0 -1
  88. package/dist/src/hooks/validation.d.ts +0 -3
  89. package/dist/src/hooks/validation.js +0 -70
  90. package/dist/src/i18n.d.ts +0 -21
  91. package/dist/src/i18n.js +0 -720
  92. package/dist/src/init_cli.d.ts +0 -28
  93. package/dist/src/init_cli.js +0 -404
  94. package/dist/src/install_engine.d.ts +0 -71
  95. package/dist/src/install_engine.js +0 -538
  96. package/dist/src/invariants.d.ts +0 -16
  97. package/dist/src/invariants.js +0 -363
  98. package/dist/src/openspec.d.ts +0 -37
  99. package/dist/src/openspec.js +0 -274
  100. package/dist/src/packet_render.d.ts +0 -5
  101. package/dist/src/packet_render.js +0 -1767
  102. package/dist/src/packet_schema.d.ts +0 -59
  103. package/dist/src/packet_schema.js +0 -1
  104. package/dist/src/paths.d.ts +0 -22
  105. package/dist/src/paths.js +0 -203
  106. package/dist/src/project_init.d.ts +0 -33
  107. package/dist/src/project_init.js +0 -191
  108. package/dist/src/self_update.d.ts +0 -14
  109. package/dist/src/self_update.js +0 -56
  110. package/dist/src/state.d.ts +0 -37
  111. package/dist/src/state.js +0 -464
  112. package/dist/src/tasks.d.ts +0 -38
  113. package/dist/src/tasks.js +0 -373
  114. package/dist/src/util.d.ts +0 -163
  115. package/dist/src/util.js +0 -792
  116. package/dist/superspec.d.ts +0 -6
  117. package/dist/superspec.js +0 -97
  118. package/dist/superspec_guard.d.ts +0 -4
  119. package/dist/superspec_guard.js +0 -19
  120. package/dist/superspec_hook.d.ts +0 -4
  121. package/dist/superspec_hook.js +0 -35
  122. package/dist/superspec_init.d.ts +0 -2
  123. package/dist/superspec_init.js +0 -17
  124. package/schemas/hook-event.schema.json +0 -27
  125. package/schemas/install-manifest.schema.json +0 -97
  126. package/templates/hooks/codex-hooks.json +0 -35
  127. package/templates/sidecar/archive-preservation.json +0 -11
  128. package/templates/sidecar/business-invariants.md +0 -38
  129. package/templates/sidecar/config.yaml +0 -13
  130. package/templates/sidecar/discovery.md +0 -33
  131. package/templates/sidecar/test-contract.md +0 -27
@@ -1,538 +0,0 @@
1
- // Manifest-driven install/update/uninstall engine (audit G-1, Phase 5 decision D4, 2026-06-10).
2
- // Single engine behind `superspec_init` / `--update` / `--uninstall`, per DISTRIBUTION.md §5-§6:
3
- // - install-map (adapters/codex/install-map.json) is the only source of what gets installed;
4
- // - install-manifest (.codex/superspec/install-manifest.json) is the only basis for update/uninstall;
5
- // - manifest sha256 is the managed BASELINE: mismatch means the user edited the file, which the
6
- // engine must never overwrite or delete (dpkg-style *.new / skip + warn).
7
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
8
- import { dirname, join, resolve } from "node:path";
9
- import { fileURLToPath } from "node:url";
10
- import { SCHEMA_VERSION, isObject, sha256_file } from "./util.js";
11
- function find_package_root(moduleUrl) {
12
- const moduleDir = dirname(fileURLToPath(moduleUrl));
13
- for (const candidate of [resolve(moduleDir, ".."), resolve(moduleDir, "..", "..")]) {
14
- if (existsSync(join(candidate, "package.json")) && existsSync(join(candidate, INSTALL_MAP_REL)))
15
- return candidate;
16
- }
17
- return resolve(moduleDir, "..");
18
- }
19
- export const INSTALL_MAP_REL = join("adapters", "codex", "install-map.json");
20
- export const PACKAGE_ROOT = find_package_root(import.meta.url);
21
- export const PROJECT_INSTALL_MANIFEST_REL = join(".codex", "superspec", "install-manifest.json");
22
- export const USER_INSTALL_MANIFEST_REL = join("superspec", "install-manifest.json");
23
- export const INSTALL_MANIFEST_REL = PROJECT_INSTALL_MANIFEST_REL;
24
- export const CODEX_NATIVE_AGENT_MAX_THREADS = 12;
25
- export const CODEX_NATIVE_AGENT_MAX_DEPTH = 1;
26
- const CODEX_CONFIG_ENTRIES = [
27
- { table: "features", key: "multi_agent", value: "true" },
28
- { table: "features", key: "child_agents_md", value: "true" },
29
- { table: "agents", key: "max_threads", value: String(CODEX_NATIVE_AGENT_MAX_THREADS) },
30
- { table: "agents", key: "max_depth", value: String(CODEX_NATIVE_AGENT_MAX_DEPTH) },
31
- ];
32
- const HOOK_COMMAND = 'superspec-hook --change "$SUPERSPEC_CHANGE"';
33
- const LEGACY_FOUR_HOOK_MATRIX = [
34
- { eventName: "PreToolUse", matcher: "Bash|apply_patch|Edit|Write|mcp__.*", statusMessage: "SuperSpec 写入策略检查" },
35
- { eventName: "PostToolUse", matcher: "Bash", statusMessage: "SuperSpec 运行证据记录" },
36
- { eventName: "SubagentStart", matcher: ".*", statusMessage: "SuperSpec 子智能体启动记录" },
37
- { eventName: "SubagentStop", matcher: ".*", statusMessage: "SuperSpec 子智能体停止记录" },
38
- ];
39
- function package_version(packageRoot) {
40
- try {
41
- const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
42
- return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
43
- }
44
- catch {
45
- return "0.0.0";
46
- }
47
- }
48
- function has_exact_keys(value, keys) {
49
- const actual = Object.keys(value).sort();
50
- const expected = [...keys].sort();
51
- return actual.length === expected.length && actual.every((key, idx) => key === expected[idx]);
52
- }
53
- function legacy_hook_entry_matches(entry, expected) {
54
- if (!isObject(entry) || !has_exact_keys(entry, ["matcher", "hooks"]))
55
- return false;
56
- if (entry.matcher !== expected.matcher || !Array.isArray(entry.hooks) || entry.hooks.length !== 1)
57
- return false;
58
- const hook = entry.hooks[0];
59
- return isObject(hook)
60
- && has_exact_keys(hook, ["type", "command", "timeout", "statusMessage"])
61
- && hook.type === "command"
62
- && hook.command === HOOK_COMMAND
63
- && hook.timeout === 120
64
- && hook.statusMessage === expected.statusMessage;
65
- }
66
- function is_legacy_managed_four_hook_manifest(parsed) {
67
- if (!isObject(parsed) || !has_exact_keys(parsed, ["superspec", "hooks"]))
68
- return false;
69
- if (!isObject(parsed.superspec) || !has_exact_keys(parsed.superspec, ["managed", "adapter_version", "strict_profile_default"]))
70
- return false;
71
- if (parsed.superspec.managed !== true
72
- || parsed.superspec.adapter_version !== "superspec-hook@2"
73
- || parsed.superspec.strict_profile_default !== "audit-only-until-r1-provenance-passes") {
74
- return false;
75
- }
76
- if (!isObject(parsed.hooks) || !has_exact_keys(parsed.hooks, LEGACY_FOUR_HOOK_MATRIX.map((entry) => entry.eventName)))
77
- return false;
78
- return LEGACY_FOUR_HOOK_MATRIX.every((expected) => {
79
- const entries = parsed.hooks[expected.eventName];
80
- return Array.isArray(entries)
81
- && entries.length === 1
82
- && legacy_hook_entry_matches(entries[0], expected);
83
- });
84
- }
85
- function target_is_legacy_managed_four_hook_manifest(targetAbs) {
86
- try {
87
- return is_legacy_managed_four_hook_manifest(JSON.parse(readFileSync(targetAbs, "utf8")));
88
- }
89
- catch {
90
- return false;
91
- }
92
- }
93
- export function load_install_map(packageRoot = PACKAGE_ROOT) {
94
- const mapPath = join(packageRoot, INSTALL_MAP_REL);
95
- if (!existsSync(mapPath))
96
- return { mappings: [], problems: [`install map missing: ${mapPath}`] };
97
- let raw;
98
- try {
99
- raw = JSON.parse(readFileSync(mapPath, "utf8"));
100
- }
101
- catch (err) {
102
- return { mappings: [], problems: [`install map unparsable: ${err.message}`] };
103
- }
104
- if (!isObject(raw) || !Array.isArray(raw.mappings))
105
- return { mappings: [], problems: ["install map must contain a mappings list"] };
106
- const mappings = [];
107
- const problems = [];
108
- raw.mappings.forEach((item, idx) => {
109
- if (!isObject(item) || typeof item.kind !== "string" || typeof item.source !== "string" || !item.source
110
- || typeof item.target !== "string" || !item.target) {
111
- problems.push(`install map mappings[${idx}] is malformed`);
112
- return;
113
- }
114
- if (!existsSync(join(packageRoot, item.source))) {
115
- problems.push(`install map source missing: ${item.source}`);
116
- return;
117
- }
118
- mappings.push({ kind: item.kind, source: item.source, target: item.target });
119
- });
120
- return { mappings, problems };
121
- }
122
- export function manifest_shape_problems(manifest) {
123
- // Lightweight mirror of schemas/install-manifest.schema.json (no runtime deps allowed).
124
- const problems = [];
125
- if (!isObject(manifest))
126
- return ["manifest is not an object"];
127
- if (typeof manifest.superspecVersion !== "string" || !manifest.superspecVersion)
128
- problems.push("superspecVersion missing");
129
- if (typeof manifest.installedAt !== "string" || Number.isNaN(Date.parse(manifest.installedAt)))
130
- problems.push("installedAt missing or not a date-time");
131
- if (!Number.isInteger(manifest.guardSchemaVersion) || manifest.guardSchemaVersion < 1)
132
- problems.push("guardSchemaVersion missing");
133
- if (!["global-bin", "npm-bin", "repo-wrapper"].includes(manifest.guardWiring))
134
- problems.push("guardWiring must be global-bin, npm-bin, or repo-wrapper");
135
- if (manifest.installScope !== undefined && !["project", "user"].includes(manifest.installScope))
136
- problems.push("installScope must be project or user");
137
- if (!Array.isArray(manifest.createdDirs) || manifest.createdDirs.some((item) => typeof item !== "string" || !item))
138
- problems.push("createdDirs malformed");
139
- if (!Array.isArray(manifest.dataGlobs) || manifest.dataGlobs.some((item) => typeof item !== "string" || !item))
140
- problems.push("dataGlobs malformed");
141
- if (manifest.configPatch !== undefined) {
142
- if (!isObject(manifest.configPatch)
143
- || typeof manifest.configPatch.path !== "string"
144
- || !manifest.configPatch.path
145
- || manifest.configPatch.retainedOnUninstall !== true
146
- || manifest.configPatch.managed !== false) {
147
- problems.push("configPatch malformed");
148
- }
149
- }
150
- if (!Array.isArray(manifest.files)) {
151
- problems.push("files missing");
152
- }
153
- else {
154
- manifest.files.forEach((entry, idx) => {
155
- if (!isObject(entry) || typeof entry.path !== "string" || !entry.path
156
- || typeof entry.sha256 !== "string" || !/^sha256:[0-9a-f]{64}$/.test(entry.sha256)
157
- || typeof entry.managed !== "boolean" || typeof entry.preexisting !== "boolean") {
158
- problems.push(`files[${idx}] malformed`);
159
- }
160
- });
161
- }
162
- return problems;
163
- }
164
- export function read_install_manifest(repoRoot, opts = {}) {
165
- const manifestPath = join(repoRoot, install_manifest_rel(opts.scope ?? "project"));
166
- if (!existsSync(manifestPath))
167
- return { manifest: null, problems: [] };
168
- let parsed;
169
- try {
170
- parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
171
- }
172
- catch (err) {
173
- return { manifest: null, problems: [`install manifest unparsable: ${err.message}`] };
174
- }
175
- const shapeProblems = manifest_shape_problems(parsed);
176
- if (shapeProblems.length > 0)
177
- return { manifest: null, problems: shapeProblems.map((item) => `install manifest invalid: ${item}`) };
178
- return { manifest: parsed, problems: [] };
179
- }
180
- export function install_manifest_rel(scope = "project") {
181
- return scope === "user" ? USER_INSTALL_MANIFEST_REL : PROJECT_INSTALL_MANIFEST_REL;
182
- }
183
- function scoped_mapping(mapping, scope) {
184
- if (scope === "project")
185
- return mapping;
186
- if (mapping.kind === "wrapper")
187
- return null;
188
- const prefix = ".codex/";
189
- return {
190
- ...mapping,
191
- target: mapping.target.startsWith(prefix) ? mapping.target.slice(prefix.length) : mapping.target,
192
- };
193
- }
194
- function scoped_mappings(mappings, scope) {
195
- return mappings.flatMap((mapping) => {
196
- const scoped = scoped_mapping(mapping, scope);
197
- return scoped === null ? [] : [scoped];
198
- });
199
- }
200
- export function codex_config_rel(scope = "project") {
201
- return scope === "user" ? "config.toml" : join(".codex", "config.toml");
202
- }
203
- function table_header_name(line) {
204
- const match = /^\s*\[([A-Za-z0-9_.-]+)\]\s*(?:#.*)?$/u.exec(line);
205
- return match?.[1] ?? null;
206
- }
207
- function is_any_table_header(line) {
208
- return /^\s*\[[^\]]+\]\s*(?:#.*)?$/u.test(line);
209
- }
210
- function escape_regexp(text) {
211
- return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
212
- }
213
- function split_toml_lines(text) {
214
- if (!text)
215
- return [];
216
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
217
- if (lines[lines.length - 1] === "")
218
- lines.pop();
219
- return lines;
220
- }
221
- function ensure_toml_key(lines, table, key, value, overwriteExisting) {
222
- let start = -1;
223
- let end = lines.length;
224
- for (let idx = 0; idx < lines.length; idx += 1) {
225
- if (table_header_name(lines[idx]) !== table)
226
- continue;
227
- start = idx;
228
- end = lines.length;
229
- for (let next = idx + 1; next < lines.length; next += 1) {
230
- if (!is_any_table_header(lines[next]))
231
- continue;
232
- end = next;
233
- break;
234
- }
235
- break;
236
- }
237
- if (start === -1) {
238
- if (lines.length > 0 && lines[lines.length - 1].trim() !== "")
239
- lines.push("");
240
- lines.push(`[${table}]`, `${key} = ${value}`);
241
- return true;
242
- }
243
- const keyRe = new RegExp(`^\\s*${escape_regexp(key)}\\s*=`, "u");
244
- const desired = `${key} = ${value}`;
245
- for (let idx = start + 1; idx < end; idx += 1) {
246
- if (!keyRe.test(lines[idx]))
247
- continue;
248
- if (lines[idx] === desired)
249
- return false;
250
- if (!overwriteExisting)
251
- return false;
252
- lines[idx] = desired;
253
- return true;
254
- }
255
- let insertAt = end;
256
- while (insertAt > start + 1 && lines[insertAt - 1].trim() === "")
257
- insertAt -= 1;
258
- lines.splice(insertAt, 0, desired);
259
- return true;
260
- }
261
- export function merge_codex_config(text, opts = {}) {
262
- const lines = split_toml_lines(text);
263
- for (const entry of CODEX_CONFIG_ENTRIES) {
264
- ensure_toml_key(lines, entry.table, entry.key, entry.value, opts.force === true);
265
- }
266
- return `${lines.join("\n")}\n`;
267
- }
268
- export function ensure_codex_config(repoRoot, scope = "project", opts = {}) {
269
- const rel = codex_config_rel(scope);
270
- const configPath = join(repoRoot, rel);
271
- const existed = existsSync(configPath);
272
- try {
273
- const before = existed ? readFileSync(configPath, "utf8") : "";
274
- const after = merge_codex_config(before, opts);
275
- if (after === before)
276
- return { action: { action: `configure ${rel}`, status: "ok" }, problems: [] };
277
- mkdirSync(dirname(configPath), { recursive: true });
278
- writeFileSync(configPath, after, "utf8");
279
- return {
280
- action: {
281
- action: `configure ${rel}`,
282
- status: existed ? "updated" : "created",
283
- detail: `Codex native subagent concurrency set to ${CODEX_NATIVE_AGENT_MAX_THREADS}`,
284
- },
285
- problems: [],
286
- };
287
- }
288
- catch (err) {
289
- return {
290
- action: { action: `configure ${rel}`, status: "failed" },
291
- problems: [`Codex config update failed: ${err.message}`],
292
- };
293
- }
294
- }
295
- function write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope) {
296
- const manifest = {
297
- superspecVersion: package_version(packageRoot),
298
- installedAt: new Date().toISOString(),
299
- guardSchemaVersion: SCHEMA_VERSION,
300
- guardWiring: "global-bin",
301
- installScope: scope,
302
- files,
303
- createdDirs: [...new Set(createdDirs)].sort(),
304
- dataGlobs: ["**/.superspec"],
305
- configPatch: {
306
- path: codex_config_rel(scope),
307
- retainedOnUninstall: true,
308
- managed: false,
309
- },
310
- };
311
- const manifestPath = join(repoRoot, install_manifest_rel(scope));
312
- mkdirSync(dirname(manifestPath), { recursive: true });
313
- writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
314
- return manifest;
315
- }
316
- function ensure_parent_dirs(repoRoot, targetAbs, createdDirs) {
317
- const parent = dirname(targetAbs);
318
- if (existsSync(parent))
319
- return;
320
- // Record every directory level this install creates so uninstall can remove exactly those.
321
- let probe = parent;
322
- const missing = [];
323
- while (!existsSync(probe) && probe.startsWith(repoRoot) && probe !== repoRoot) {
324
- missing.push(probe);
325
- probe = dirname(probe);
326
- }
327
- mkdirSync(parent, { recursive: true });
328
- for (const dir of missing)
329
- createdDirs.push(dir.slice(repoRoot.length + 1));
330
- }
331
- function install_one(repoRoot, packageRoot, mapping, createdDirs) {
332
- const sourceAbs = join(packageRoot, mapping.source);
333
- const targetAbs = join(repoRoot, mapping.target);
334
- const sourceSha = sha256_file(sourceAbs);
335
- ensure_parent_dirs(repoRoot, targetAbs, createdDirs);
336
- copyFileSync(sourceAbs, targetAbs);
337
- if (mapping.kind === "wrapper")
338
- chmodSync(targetAbs, 0o755);
339
- return {
340
- entry: { path: mapping.target, sha256: sourceSha, managed: true, preexisting: false },
341
- action: { action: `install ${mapping.target}`, status: "created" },
342
- };
343
- }
344
- export function install_workflow(repoRoot, opts = {}) {
345
- const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
346
- const scope = opts.scope ?? "project";
347
- const loaded = load_install_map(packageRoot);
348
- const mappings = scoped_mappings(loaded.mappings, scope);
349
- const problems = loaded.problems;
350
- const actions = [];
351
- if (problems.length > 0)
352
- return { actions, problems, manifest: null };
353
- const files = [];
354
- const createdDirs = [];
355
- for (const mapping of mappings) {
356
- const sourceAbs = join(packageRoot, mapping.source);
357
- const targetAbs = join(repoRoot, mapping.target);
358
- const sourceSha = sha256_file(sourceAbs);
359
- const targetSha = sha256_file(targetAbs);
360
- if (targetSha === null) {
361
- const result = install_one(repoRoot, packageRoot, mapping, createdDirs);
362
- files.push(result.entry);
363
- actions.push(result.action);
364
- }
365
- else if (targetSha === sourceSha) {
366
- // Identical content: adopt as managed (idempotent re-init).
367
- files.push({ path: mapping.target, sha256: sourceSha, managed: true, preexisting: false });
368
- actions.push({ action: `install ${mapping.target}`, status: "ok" });
369
- }
370
- else if (mapping.kind === "hook" && target_is_legacy_managed_four_hook_manifest(targetAbs)) {
371
- // Previous SuperSpec default hooks installed PreToolUse/PostToolUse. Re-init must migrate
372
- // that unmodified managed baseline so old blocking hooks do not stay in the workflow.
373
- copyFileSync(sourceAbs, targetAbs);
374
- files.push({ path: mapping.target, sha256: sourceSha, managed: true, preexisting: false });
375
- actions.push({ action: `install ${mapping.target}`, status: "updated", detail: "legacy managed four-hook manifest migrated to subagent-only manifest" });
376
- }
377
- else if (opts.force) {
378
- copyFileSync(targetAbs, `${targetAbs}.bak`);
379
- copyFileSync(sourceAbs, targetAbs);
380
- if (mapping.kind === "wrapper")
381
- chmodSync(targetAbs, 0o755);
382
- files.push({ path: mapping.target, sha256: sourceSha, managed: true, preexisting: false });
383
- actions.push({ action: `install ${mapping.target}`, status: "updated", detail: `existing file backed up to ${mapping.target}.bak` });
384
- }
385
- else if (mapping.kind === "hook") {
386
- copyFileSync(sourceAbs, `${targetAbs}.new`);
387
- files.push({ path: mapping.target, sha256: targetSha, managed: false, preexisting: true });
388
- actions.push({ action: `install ${mapping.target}`, status: "skipped", detail: `pre-existing hooks manifest kept; current SuperSpec manifest written to ${mapping.target}.new` });
389
- }
390
- else {
391
- // Pre-existing different file: never overwrite, never delete (DISTRIBUTION §5 red line).
392
- files.push({ path: mapping.target, sha256: targetSha, managed: false, preexisting: true });
393
- actions.push({ action: `install ${mapping.target}`, status: "skipped", detail: "pre-existing file with different content kept; rerun with --force to overwrite (backs up *.bak)" });
394
- }
395
- }
396
- const configResult = ensure_codex_config(repoRoot, scope, { force: opts.force === true });
397
- actions.push(configResult.action);
398
- problems.push(...configResult.problems);
399
- const manifest = write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope);
400
- return { actions, problems, manifest };
401
- }
402
- export function update_workflow(repoRoot, opts = {}) {
403
- const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
404
- const scope = opts.scope ?? "project";
405
- const actions = [];
406
- const { manifest: previous, problems: manifestProblems } = read_install_manifest(repoRoot, { scope });
407
- if (manifestProblems.length > 0)
408
- return { actions, problems: manifestProblems, manifest: null };
409
- if (previous === null)
410
- return { actions, problems: ["install manifest missing; run superspec init before --update"], manifest: null };
411
- const loaded = load_install_map(packageRoot);
412
- const mappings = scoped_mappings(loaded.mappings, scope);
413
- const problems = loaded.problems;
414
- if (problems.length > 0)
415
- return { actions, problems, manifest: null };
416
- const prevByPath = new Map(previous.files.map((entry) => [entry.path, entry]));
417
- const files = [];
418
- const createdDirs = [...previous.createdDirs];
419
- const mappedTargets = new Set();
420
- for (const mapping of mappings) {
421
- mappedTargets.add(mapping.target);
422
- const sourceAbs = join(packageRoot, mapping.source);
423
- const targetAbs = join(repoRoot, mapping.target);
424
- const sourceSha = sha256_file(sourceAbs);
425
- const targetSha = sha256_file(targetAbs);
426
- const prev = prevByPath.get(mapping.target);
427
- if (prev?.preexisting) {
428
- // Was the user's file before we arrived: keep hands off forever.
429
- files.push({ ...prev, sha256: targetSha ?? prev.sha256 });
430
- actions.push({ action: `update ${mapping.target}`, status: "skipped", detail: "preexisting file is never touched" });
431
- }
432
- else if (targetSha === null) {
433
- const result = install_one(repoRoot, packageRoot, mapping, createdDirs);
434
- files.push(result.entry);
435
- actions.push({ ...result.action, action: `update ${mapping.target}` });
436
- }
437
- else if (targetSha === sourceSha) {
438
- files.push({ path: mapping.target, sha256: sourceSha, managed: true, preexisting: false });
439
- actions.push({ action: `update ${mapping.target}`, status: "ok" });
440
- }
441
- else if (prev && targetSha === prev.sha256) {
442
- // Managed and unmodified since install: safe to roll forward.
443
- copyFileSync(sourceAbs, targetAbs);
444
- if (mapping.kind === "wrapper")
445
- chmodSync(targetAbs, 0o755);
446
- files.push({ path: mapping.target, sha256: sourceSha, managed: true, preexisting: false });
447
- actions.push({ action: `update ${mapping.target}`, status: "updated" });
448
- }
449
- else {
450
- // User-modified managed file: keep the user's version, ship ours as *.new (dpkg style).
451
- // Manifest keeps the OLD baseline sha so uninstall still detects the modification.
452
- copyFileSync(sourceAbs, `${targetAbs}.new`);
453
- files.push(prev ?? { path: mapping.target, sha256: targetSha, managed: false, preexisting: true });
454
- actions.push({ action: `update ${mapping.target}`, status: "skipped", detail: `user-modified file kept; new version written to ${mapping.target}.new` });
455
- }
456
- }
457
- // Mappings removed from the new install map: delete the old managed file only when unmodified.
458
- for (const prev of prevByPath.values()) {
459
- if (mappedTargets.has(prev.path) || !prev.managed || prev.preexisting)
460
- continue;
461
- const targetAbs = join(repoRoot, prev.path);
462
- const targetSha = sha256_file(targetAbs);
463
- if (targetSha === null)
464
- continue;
465
- if (targetSha === prev.sha256) {
466
- unlinkSync(targetAbs);
467
- actions.push({ action: `update ${prev.path}`, status: "removed", detail: "managed file no longer shipped" });
468
- }
469
- else {
470
- files.push(prev);
471
- actions.push({ action: `update ${prev.path}`, status: "skipped", detail: "no longer shipped but user-modified; kept" });
472
- }
473
- }
474
- const configResult = ensure_codex_config(repoRoot, scope);
475
- actions.push(configResult.action);
476
- problems.push(...configResult.problems);
477
- const manifest = write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope);
478
- return { actions, problems, manifest };
479
- }
480
- function remove_empty_created_dirs(repoRoot, createdDirs, actions) {
481
- const byDepth = [...new Set(createdDirs)].sort((a, b) => b.split("/").length - a.split("/").length);
482
- for (const rel of byDepth) {
483
- const abs = join(repoRoot, rel);
484
- if (!existsSync(abs) || !statSync(abs).isDirectory())
485
- continue;
486
- if (readdirSync(abs).length > 0)
487
- continue;
488
- rmdirSync(abs);
489
- actions.push({ action: `uninstall rmdir ${rel}`, status: "removed" });
490
- }
491
- }
492
- export function uninstall_workflow(repoRoot, opts = {}) {
493
- const scope = opts.scope ?? "project";
494
- const actions = [];
495
- const { manifest, problems: manifestProblems } = read_install_manifest(repoRoot, { scope });
496
- if (manifestProblems.length > 0)
497
- return { actions, problems: manifestProblems, manifest: null };
498
- if (manifest === null)
499
- return { actions, problems: ["install manifest missing; nothing to uninstall (manifest is the only removal authority)"], manifest: null };
500
- for (const entry of manifest.files) {
501
- const targetAbs = join(repoRoot, entry.path);
502
- if (!entry.managed || entry.preexisting) {
503
- actions.push({ action: `uninstall ${entry.path}`, status: "skipped", detail: "preexisting/unmanaged file is never touched" });
504
- continue;
505
- }
506
- const targetSha = sha256_file(targetAbs);
507
- if (targetSha === null) {
508
- actions.push({ action: `uninstall ${entry.path}`, status: "ok", detail: "already absent" });
509
- continue;
510
- }
511
- if (targetSha !== entry.sha256) {
512
- actions.push({ action: `uninstall ${entry.path}`, status: "skipped", detail: "user-modified since install; kept" });
513
- continue;
514
- }
515
- if (opts.dryRun) {
516
- actions.push({ action: `uninstall ${entry.path}`, status: "would_remove" });
517
- }
518
- else {
519
- unlinkSync(targetAbs);
520
- actions.push({ action: `uninstall ${entry.path}`, status: "removed" });
521
- }
522
- }
523
- if (!opts.dryRun) {
524
- remove_empty_created_dirs(repoRoot, manifest.createdDirs, actions);
525
- const manifestRel = install_manifest_rel(scope);
526
- const manifestPath = join(repoRoot, manifestRel);
527
- if (existsSync(manifestPath)) {
528
- unlinkSync(manifestPath);
529
- actions.push({ action: `uninstall ${manifestRel}`, status: "removed" });
530
- }
531
- const manifestDir = dirname(manifestPath);
532
- if (existsSync(manifestDir) && readdirSync(manifestDir).length === 0)
533
- rmdirSync(manifestDir);
534
- }
535
- // .superspec runtime data (dataGlobs) is intentionally untouched: default uninstall keeps all
536
- // evidence/state; a --purge with preservation bundle is future work (DISTRIBUTION §6.4).
537
- return { actions, problems: [], manifest };
538
- }
@@ -1,16 +0,0 @@
1
- import type { JsonMap, Reason, TaskInfo } from "./util.ts";
2
- export declare function business_invariants_text(changeRoot: string): string;
3
- export declare function parse_business_invariant_records(changeRoot: string): JsonMap[];
4
- export declare function business_invariant_ids(changeRoot: string): Set<string>;
5
- export declare function hard_business_invariant_ids(changeRoot: string): Set<string>;
6
- export declare function automated_hard_business_invariant_ids(changeRoot: string): Set<string>;
7
- export declare function post_implementation_business_invariant_ids(changeRoot: string): Set<string>;
8
- export declare function human_confirmation_business_invariant_ids(changeRoot: string): Set<string>;
9
- export declare function business_invariant_validation_reasons(changeRoot: string): Reason[];
10
- export declare function test_contract_invariant_ids(changeRoot: string): Set<string>;
11
- export declare function task_invariant_refs(tasks: Record<string, TaskInfo>): Set<string>;
12
- export declare function evidence_invariant_refs(ev: JsonMap): Set<string>;
13
- export declare function red_green_invariant_ids(evidences: JsonMap[]): Set<string>;
14
- export declare function evidence_invariant_ref_reasons(evidences: JsonMap[], taskId: string, declared: Set<string>, validIds: Set<string>, semanticStatus: string): Reason[];
15
- export declare function evidence_test_contract_invariant_reasons(evidences: JsonMap[], taskId: string, byTest: Map<string, Set<string>>, semanticStatus: string): Reason[];
16
- export declare function invariant_matrix_coverage_reasons(changeRoot: string, ev: JsonMap, evidences: JsonMap[]): Reason[];