@namewta/speculo 1.0.5 → 1.0.7

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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/src/kernel.d.ts +23 -2
  3. package/dist/src/kernel.js +9 -0
  4. package/dist/src/kernel.js.map +1 -1
  5. package/dist/src/ops-resources.d.ts +3 -0
  6. package/dist/src/ops-resources.js +188 -0
  7. package/dist/src/ops-resources.js.map +1 -0
  8. package/dist/src/refresh.js +20 -2
  9. package/dist/src/refresh.js.map +1 -1
  10. package/dist/src/structured.js +7 -0
  11. package/dist/src/structured.js.map +1 -1
  12. package/dist/src/workflows.js +1 -1
  13. package/dist/src/workflows.js.map +1 -1
  14. package/package.json +1 -1
  15. package/template/.speculo/README.md +3 -3
  16. package/template/.speculo/kernel/README.md +3 -0
  17. package/template/.speculo/kernel/checkpoint.schema.json +138 -4
  18. package/template/commands/archive-and-consolidate.md +5 -1
  19. package/template/commands/status.md +2 -2
  20. package/template/workflows/ops/D-project-deploy/D-project-deploy.md +34 -0
  21. package/template/workflows/ops/H-host-manage/H-host-manage.md +30 -0
  22. package/template/workflows/ops/I-initialize/I-initialize.md +28 -0
  23. package/template/workflows/ops/INDEX.md +15 -17
  24. package/template/workflows/ops/README.md +59 -93
  25. package/template/workflows/ops/_state/status.json +15 -3
  26. package/template/workflows/ops/common/CAPABILITIES.md +23 -0
  27. package/template/workflows/ops/common/USAGE.md +112 -0
  28. package/template/workflows/ops/common/examples/README.md +7 -0
  29. package/template/workflows/ops/common/examples/binding.example.json +12 -0
  30. package/template/workflows/ops/common/examples/compose-app.example.json +56 -0
  31. package/template/workflows/ops/common/examples/credential.example.json +9 -0
  32. package/template/workflows/ops/common/examples/environment-request.example.json +7 -0
  33. package/template/workflows/ops/common/examples/register.example.json +44 -0
  34. package/template/workflows/ops/common/examples/shared-allocation.example.json +37 -0
  35. package/template/workflows/ops/common/rules/activation-and-memory.md +5 -20
  36. package/template/workflows/ops/common/rules/persistence-and-secrets.md +27 -0
  37. package/template/workflows/ops/common/rules/recovery.md +15 -0
  38. package/template/workflows/ops/common/rules/shared-services.md +13 -0
  39. package/template/workflows/ops/common/schemas/allocation.schema.json +92 -0
  40. package/template/workflows/ops/common/schemas/approval.schema.json +41 -21
  41. package/template/workflows/ops/common/schemas/binding.schema.json +73 -0
  42. package/template/workflows/ops/common/schemas/deployment.schema.json +244 -0
  43. package/template/workflows/ops/common/schemas/host.schema.json +88 -0
  44. package/template/workflows/ops/common/schemas/plan.schema.json +1573 -0
  45. package/template/workflows/ops/common/schemas/project.schema.json +55 -11
  46. package/template/workflows/ops/common/schemas/spec.schema.json +1032 -0
  47. package/template/workflows/ops/common/schemas/status.schema.json +758 -17
  48. package/template/workflows/ops/common/service-profiles/custom.md +5 -0
  49. package/template/workflows/ops/common/service-profiles/docker-engine.md +11 -0
  50. package/template/workflows/ops/common/service-profiles/minio.md +7 -0
  51. package/template/workflows/ops/common/service-profiles/mysql.md +7 -0
  52. package/template/workflows/ops/common/service-profiles/redis.md +7 -0
  53. package/template/workflows/ops/common/templates/CONTROLLER-RECORD.md +13 -0
  54. package/template/workflows/ops/common/templates/EXECUTION-PLAN.md +15 -0
  55. package/template/workflows/ops/common/templates/HOST-README.md +11 -0
  56. package/template/workflows/ops/common/templates/PROJECT-README.md +15 -0
  57. package/template/workflows/ops/common/tests/test_ops.mjs +752 -0
  58. package/template/workflows/ops/common/tools/bootstrap.ps1 +18 -0
  59. package/template/workflows/ops/common/tools/bootstrap.sh +27 -0
  60. package/template/workflows/ops/common/tools/demo-local.mjs +101 -0
  61. package/template/workflows/ops/common/tools/ops.mjs +4 -0
  62. package/template/workflows/ops/common/tools/opslib/agent.mjs +873 -0
  63. package/template/workflows/ops/common/tools/opslib/cli.mjs +311 -0
  64. package/template/workflows/ops/common/tools/opslib/core.mjs +393 -0
  65. package/template/workflows/ops/common/tools/opslib/docs.mjs +252 -0
  66. package/template/workflows/ops/common/tools/opslib/execution.mjs +378 -0
  67. package/template/workflows/ops/common/tools/opslib/host_recipes.mjs +109 -0
  68. package/template/workflows/ops/common/tools/opslib/model.mjs +272 -0
  69. package/template/workflows/ops/common/tools/opslib/native_windows.mjs +36 -0
  70. package/template/workflows/ops/common/tools/opslib/planner.mjs +687 -0
  71. package/template/workflows/ops/common/tools/opslib/services.mjs +76 -0
  72. package/template/workflows/ops/common/tools/opslib/sources.mjs +56 -0
  73. package/template/workflows/ops/common/tools/opslib/transport.mjs +127 -0
  74. package/template/workflows/ops/common/tools/validate-ops.mjs +43 -1012
  75. package/template/workflows/ops/manifest.json +60 -1
  76. package/template/workflows/ops/runtime-contract.json +1 -6
  77. package/template/workflows/ops/A-archive-and-learn/A-archive-and-learn.md +0 -73
  78. package/template/workflows/ops/A-archive-and-learn/promotion-plan-template.md +0 -41
  79. package/template/workflows/ops/A-archive-and-learn/retrospective-template.md +0 -53
  80. package/template/workflows/ops/E-execute-and-stabilize/E-execute-and-stabilize.md +0 -82
  81. package/template/workflows/ops/E-execute-and-stabilize/attempt-summary-template.md +0 -44
  82. package/template/workflows/ops/E-execute-and-stabilize/diagnosis-template.md +0 -24
  83. package/template/workflows/ops/E-execute-and-stabilize/handoff-template.md +0 -32
  84. package/template/workflows/ops/E-execute-and-stabilize/rollback-template.md +0 -26
  85. package/template/workflows/ops/E-execute-and-stabilize/verification-state-template.json +0 -30
  86. package/template/workflows/ops/E-execute-and-stabilize/verification-template.md +0 -55
  87. package/template/workflows/ops/H-computer-hygiene/H-computer-hygiene.md +0 -105
  88. package/template/workflows/ops/H-computer-hygiene/examples/README.md +0 -9
  89. package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.environment.md +0 -45
  90. package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.json +0 -970
  91. package/template/workflows/ops/H-computer-hygiene/examples/linux/2026-09-14.md +0 -216
  92. package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.environment.md +0 -45
  93. package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.json +0 -970
  94. package/template/workflows/ops/H-computer-hygiene/examples/macos/2026-09-14.md +0 -216
  95. package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.environment.md +0 -45
  96. package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.json +0 -969
  97. package/template/workflows/ops/H-computer-hygiene/examples/windows/2026-09-14.md +0 -216
  98. package/template/workflows/ops/H-computer-hygiene/references/00-sources.md +0 -58
  99. package/template/workflows/ops/H-computer-hygiene/references/01-safety.md +0 -52
  100. package/template/workflows/ops/H-computer-hygiene/references/10-windows.md +0 -47
  101. package/template/workflows/ops/H-computer-hygiene/references/11-macos.md +0 -46
  102. package/template/workflows/ops/H-computer-hygiene/references/12-linux.md +0 -13
  103. package/template/workflows/ops/H-computer-hygiene/references/20-toolchains.md +0 -83
  104. package/template/workflows/ops/H-computer-hygiene/references/21-path-migration.md +0 -64
  105. package/template/workflows/ops/H-computer-hygiene/references/22-repositories.md +0 -35
  106. package/template/workflows/ops/H-computer-hygiene/references/30-software-removal.md +0 -41
  107. package/template/workflows/ops/H-computer-hygiene/references/40-report-spec.md +0 -54
  108. package/template/workflows/ops/H-computer-hygiene/references/50-speculo-integration.md +0 -26
  109. package/template/workflows/ops/H-computer-hygiene/references/source-register.json +0 -406
  110. package/template/workflows/ops/H-computer-hygiene/rules/catalog.json +0 -191
  111. package/template/workflows/ops/H-computer-hygiene/scripts/hygiene.py +0 -1348
  112. package/template/workflows/ops/H-computer-hygiene/scripts/run.ps1 +0 -7
  113. package/template/workflows/ops/H-computer-hygiene/scripts/run.sh +0 -5
  114. package/template/workflows/ops/H-computer-hygiene/templates/config.example.json +0 -13
  115. package/template/workflows/ops/H-computer-hygiene/templates/maven-settings.fragment.xml +0 -6
  116. package/template/workflows/ops/H-computer-hygiene/templates/native-evidence.example.json +0 -19
  117. package/template/workflows/ops/H-computer-hygiene/tests/cli_smoke.py +0 -65
  118. package/template/workflows/ops/H-computer-hygiene/tests/make_examples.py +0 -114
  119. package/template/workflows/ops/H-computer-hygiene/tests/test_hygiene.py +0 -486
  120. package/template/workflows/ops/I-intake-and-assess/I-intake-and-assess.md +0 -73
  121. package/template/workflows/ops/I-intake-and-assess/change-status-template.json +0 -26
  122. package/template/workflows/ops/I-intake-and-assess/collector-catalog.md +0 -28
  123. package/template/workflows/ops/I-intake-and-assess/deployment-dossier-template.md +0 -62
  124. package/template/workflows/ops/I-intake-and-assess/global-change-status-template.json +0 -26
  125. package/template/workflows/ops/I-intake-and-assess/project-detection.md +0 -38
  126. package/template/workflows/ops/I-intake-and-assess/request-template.md +0 -44
  127. package/template/workflows/ops/I-intake-and-assess/system-report-template.md +0 -34
  128. package/template/workflows/ops/I-intake-and-assess/target-profile-template.json +0 -24
  129. package/template/workflows/ops/P-plan-and-approve/P-plan-and-approve.md +0 -72
  130. package/template/workflows/ops/P-plan-and-approve/plan-review-template.md +0 -78
  131. package/template/workflows/ops/_state/archive/.gitkeep +0 -1
  132. package/template/workflows/ops/_state/changes/.gitkeep +0 -1
  133. package/template/workflows/ops/common/rules/artifact-contract.md +0 -38
  134. package/template/workflows/ops/common/rules/closure-and-learning.md +0 -27
  135. package/template/workflows/ops/common/rules/evidence-and-redaction.md +0 -22
  136. package/template/workflows/ops/common/rules/execution-loop.md +0 -27
  137. package/template/workflows/ops/common/rules/path-and-scope-contract.md +0 -21
  138. package/template/workflows/ops/common/rules/plan-and-approval.md +0 -25
  139. package/template/workflows/ops/common/rules/project-and-change-scope.md +0 -20
  140. package/template/workflows/ops/common/rules/target-profile-and-release-gates.md +0 -44
  141. package/template/workflows/ops/common/schemas/attempt.schema.json +0 -42
  142. package/template/workflows/ops/common/schemas/change-status.schema.json +0 -43
  143. package/template/workflows/ops/common/schemas/deployment-model.schema.json +0 -26
  144. package/template/workflows/ops/common/schemas/implementation-plan.schema.json +0 -221
  145. package/template/workflows/ops/common/schemas/inventory-snapshot.schema.json +0 -31
  146. package/template/workflows/ops/common/schemas/journal-event.schema.json +0 -22
  147. package/template/workflows/ops/common/schemas/promotion-approval.schema.json +0 -20
  148. package/template/workflows/ops/common/schemas/promotion-manifest.schema.json +0 -22
  149. package/template/workflows/ops/common/schemas/target-profile.schema.json +0 -32
  150. package/template/workflows/ops/common/schemas/verification-state.schema.json +0 -30
  151. package/template/workflows/ops/common/tools/close-change.mjs +0 -177
@@ -1,177 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { createHash } from "node:crypto";
4
- import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
5
- import { mkdir, rename, rm, writeFile } from "node:fs/promises";
6
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
-
8
- const CHANGE_NAME = /^[0-9]{4}-[0-9]{2}-[0-9]{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
9
- const PROJECT_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10
- const DIGEST = /^[a-f0-9]{64}$/;
11
-
12
- function parseArgs(argv) {
13
- const options = { stateRoot: null, scope: null, project: null, change: null, apply: false, expectedDigest: null };
14
- for (let index = 0; index < argv.length; index += 1) {
15
- const arg = argv[index];
16
- if (arg === "--state-root") options.stateRoot = resolve(argv[++index] ?? "");
17
- else if (arg === "--scope") options.scope = argv[++index] ?? null;
18
- else if (arg === "--project") options.project = argv[++index] ?? null;
19
- else if (arg === "--change") options.change = argv[++index] ?? null;
20
- else if (arg === "--apply") options.apply = true;
21
- else if (arg === "--expected-digest") options.expectedDigest = argv[++index] ?? null;
22
- else throw new Error(`unknown option: ${arg}`);
23
- }
24
- if (!options.stateRoot || !["global", "project"].includes(options.scope) || !CHANGE_NAME.test(options.change ?? "")) throw new Error("--state-root, --scope and a valid --change are required");
25
- if (options.scope === "project" && !PROJECT_ID.test(options.project ?? "")) throw new Error("project scope requires a valid --project");
26
- if (options.scope === "global" && options.project) throw new Error("global scope cannot use --project");
27
- if (options.apply && !DIGEST.test(options.expectedDigest ?? "")) throw new Error("--apply requires --expected-digest");
28
- return options;
29
- }
30
-
31
- function canonicalize(value) {
32
- if (Array.isArray(value)) return value.map(canonicalize);
33
- if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]));
34
- return value;
35
- }
36
- function digestJson(value) { return createHash("sha256").update(JSON.stringify(canonicalize(value))).digest("hex"); }
37
- function digestBytes(value) { return createHash("sha256").update(value).digest("hex"); }
38
- function readJson(path, label) { try { return JSON.parse(readFileSync(path, "utf8")); } catch (error) { throw new Error(`${label}: invalid JSON (${error.message})`); } }
39
- function entryKey(entry) { return `${entry.scope}:${entry.project_id ?? "-"}:${entry.change}`; }
40
- function sameEntry(left, right) { return left?.scope === right.scope && left?.project_id === right.project_id && left?.change === right.change; }
41
-
42
- function roots(options) {
43
- const entry = { scope: options.scope, project_id: options.scope === "project" ? options.project : null, change: options.change };
44
- const prefix = entry.scope === "global" ? "" : `projects/${entry.project_id}/`;
45
- const sourcePath = `${prefix}changes/${entry.change}`;
46
- const archivePath = `${prefix}archive/${entry.change.slice(0, 7)}/${entry.change}`;
47
- return { entry, sourcePath, archivePath, source: resolveState(options.stateRoot, sourcePath), archive: resolveState(options.stateRoot, archivePath) };
48
- }
49
-
50
- function resolveState(stateRoot, locator) {
51
- if (typeof locator !== "string" || !locator || locator.startsWith("/") || locator.includes("\\") || locator.split("/").includes("..")) throw new Error(`invalid state-relative path: ${String(locator)}`);
52
- const target = resolve(stateRoot, ...locator.split("/"));
53
- const rel = relative(resolve(stateRoot), target);
54
- if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) throw new Error(`state-relative path escapes Ops root: ${locator}`);
55
- return target;
56
- }
57
-
58
- function resolveChange(changeRoot, locator) {
59
- if (typeof locator !== "string" || !locator || locator.startsWith("/") || locator.includes("\\") || locator.split("/").includes("..")) throw new Error(`invalid change-relative path: ${String(locator)}`);
60
- const target = resolve(changeRoot, ...locator.split("/"));
61
- const rel = relative(resolve(changeRoot), target);
62
- if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) throw new Error(`change-relative path escapes change root: ${locator}`);
63
- return target;
64
- }
65
-
66
- function allowedTarget(entry, locator) {
67
- const allowed = entry.scope === "project"
68
- ? [`projects/${entry.project_id}/context/`, `projects/${entry.project_id}/adr/`, `projects/${entry.project_id}/runbooks/`, "context/", "adr/", "runbooks/"]
69
- : ["context/", "adr/", "runbooks/"];
70
- return allowed.some((root) => locator.startsWith(root));
71
- }
72
-
73
- function assertNoSymlinks(root) {
74
- if (!existsSync(root)) return;
75
- function visit(path) {
76
- for (const entry of readdirSync(path, { withFileTypes: true })) {
77
- const child = join(path, entry.name);
78
- if (lstatSync(child).isSymbolicLink()) throw new Error(`Ops state contains symlink: ${relative(root, child)}`);
79
- if (entry.isDirectory()) visit(child);
80
- }
81
- }
82
- visit(root);
83
- }
84
-
85
- async function atomicWrite(path, bytes) {
86
- await mkdir(dirname(path), { recursive: true });
87
- const temp = join(dirname(path), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`);
88
- await writeFile(temp, bytes);
89
- await rename(temp, path);
90
- }
91
-
92
- function validate(options) {
93
- assertNoSymlinks(options.stateRoot);
94
- const located = roots(options);
95
- if (!existsSync(located.source)) throw new Error(`active change is missing: ${located.sourcePath}`);
96
- if (existsSync(located.archive)) throw new Error(`archive target already exists: ${located.archivePath}`);
97
- const statusPath = join(options.stateRoot, "status.json");
98
- const globalStatus = readJson(statusPath, "status.json");
99
- if (globalStatus.schema_version !== 2 || globalStatus.workflow !== "ops" || !Array.isArray(globalStatus.active) || !Array.isArray(globalStatus.archived)) throw new Error("status.json must use Ops schema v2");
100
- if (globalStatus.active.filter((item) => sameEntry(item, located.entry)).length !== 1 || globalStatus.archived.some((item) => sameEntry(item, located.entry))) throw new Error("global status does not identify exactly one active tuple");
101
- const changeStatusPath = join(located.source, ".status.json");
102
- const changeStatus = readJson(changeStatusPath, ".status.json");
103
- if (changeStatus.schema_version !== 2 || changeStatus.scope !== located.entry.scope || changeStatus.project_id !== located.entry.project_id || changeStatus.change !== located.entry.change || changeStatus.change_status !== "completed" || changeStatus.phase !== "ready_to_archive" || changeStatus.outcome === "pending" || changeStatus.blockers?.length) throw new Error("change is not ready to archive");
104
- if (!existsSync(join(located.source, "RETROSPECTIVE.md"))) throw new Error("RETROSPECTIVE.md is required");
105
- const manifestPath = join(located.source, "promotion", "manifest.json");
106
- const manifest = readJson(manifestPath, "promotion/manifest.json");
107
- const manifestDigest = digestJson(manifest);
108
- if (manifest.schema_version !== 1 || manifest.artifact !== "ops-promotion-manifest" || manifest.scope !== located.entry.scope || manifest.project_id !== located.entry.project_id || manifest.change !== located.entry.change || manifest.source_path !== located.sourcePath || manifest.archive_path !== located.archivePath || manifest.retrospective_path !== "RETROSPECTIVE.md" || !Array.isArray(manifest.writes)) throw new Error("promotion manifest identity/path mismatch");
109
- const targetSet = new Set();
110
- const writes = manifest.writes.map((item, index) => {
111
- if (!item || typeof item !== "object" || !item.staging_path?.startsWith("promotion/staging/") || !allowedTarget(located.entry, item.target_path) || !DIGEST.test(item.content_digest ?? "") || !(item.expected_target_digest === null || DIGEST.test(item.expected_target_digest))) throw new Error(`promotion write ${index} is invalid`);
112
- if (targetSet.has(item.target_path)) throw new Error(`duplicate promotion target: ${item.target_path}`);
113
- targetSet.add(item.target_path);
114
- const staging = resolveChange(located.source, item.staging_path);
115
- const target = resolveState(options.stateRoot, item.target_path);
116
- if (!existsSync(staging) || digestBytes(readFileSync(staging)) !== item.content_digest) throw new Error(`staging digest mismatch: ${item.staging_path}`);
117
- if (item.expected_target_digest === null ? existsSync(target) : !existsSync(target) || digestBytes(readFileSync(target)) !== item.expected_target_digest) throw new Error(`promotion target drift: ${item.target_path}`);
118
- if ((item.action === "create") !== (item.expected_target_digest === null)) throw new Error(`promotion action/target expectation mismatch: ${item.target_path}`);
119
- return { item, staging, target };
120
- });
121
- return { ...located, statusPath, globalStatus, changeStatusPath, changeStatus, manifest, manifestDigest, writes };
122
- }
123
-
124
- async function apply(options, context) {
125
- if (context.manifestDigest !== options.expectedDigest) throw new Error("expected digest does not match promotion manifest");
126
- const approval = readJson(join(context.source, "promotion", "approval.json"), "promotion/approval.json");
127
- if (approval.schema_version !== 1 || approval.artifact !== "ops-promotion-approval" || approval.scope !== context.entry.scope || approval.project_id !== context.entry.project_id || approval.change !== context.entry.change || approval.manifest_path !== "promotion/manifest.json" || approval.manifest_digest !== context.manifestDigest || approval.decision !== "approved" || !approval.decision_summary || Number.isNaN(Date.parse(approval.decided_at))) throw new Error("promotion approval does not bind the current manifest");
128
-
129
- const originalGlobal = readFileSync(context.statusPath);
130
- const originalChange = readFileSync(context.changeStatusPath);
131
- const originals = context.writes.map(({ target }) => existsSync(target) ? readFileSync(target) : null);
132
- const rollbackRoot = join(context.source, "promotion", "rollback");
133
- if (existsSync(rollbackRoot)) throw new Error("promotion rollback directory already exists");
134
- await mkdir(rollbackRoot, { recursive: true });
135
- const rollbackManifest = context.writes.map(({ item }, index) => ({ target_path: item.target_path, existed: originals[index] !== null, backup_path: originals[index] === null ? null : `promotion/rollback/${String(index + 1).padStart(3, "0")}.bak` }));
136
- for (const [index, bytes] of originals.entries()) if (bytes !== null) await writeFile(join(rollbackRoot, `${String(index + 1).padStart(3, "0")}.bak`), bytes);
137
- await writeFile(join(rollbackRoot, "manifest.json"), JSON.stringify({ schema_version: 1, writes: rollbackManifest }, null, 2) + "\n", "utf8");
138
-
139
- let moved = false;
140
- try {
141
- for (const { item, staging, target } of context.writes) await atomicWrite(target, readFileSync(staging));
142
- const archivedAt = new Date().toISOString();
143
- const nextChange = { ...context.changeStatus, change_status: "archived", phase: "archived", current_work: null, works_run: [...new Set([...(context.changeStatus.works_run ?? []), "ops/archive-and-learn"])], updated_at: archivedAt, archived_at: archivedAt, archive_path: `<Path>{roots.state}/ops/${context.archivePath}</Path>` };
144
- const nextGlobal = { ...context.globalStatus, active: context.globalStatus.active.filter((item) => !sameEntry(item, context.entry)), archived: [...context.globalStatus.archived, context.entry] };
145
- await atomicWrite(context.changeStatusPath, JSON.stringify(nextChange, null, 2) + "\n");
146
- await atomicWrite(context.statusPath, JSON.stringify(nextGlobal, null, 2) + "\n");
147
- await mkdir(dirname(context.archive), { recursive: true });
148
- await rename(context.source, context.archive);
149
- moved = true;
150
- } catch (error) {
151
- if (moved && existsSync(context.archive) && !existsSync(context.source)) await rename(context.archive, context.source);
152
- await atomicWrite(context.statusPath, originalGlobal);
153
- await atomicWrite(join(context.source, ".status.json"), originalChange);
154
- for (const [index, { target }] of context.writes.entries()) {
155
- if (originals[index] === null) await rm(target, { force: true });
156
- else await atomicWrite(target, originals[index]);
157
- }
158
- throw error;
159
- }
160
- if (!moved || existsSync(context.source) || !existsSync(context.archive)) throw new Error("archive move did not complete");
161
- console.log(`Ops close applied: ${entryKey(context.entry)} -> ${context.archivePath}`);
162
- }
163
-
164
- async function main() {
165
- const options = parseArgs(process.argv.slice(2));
166
- const context = validate(options);
167
- if (!options.apply) {
168
- console.log(`Ops close dry-run: ${entryKey(context.entry)}`);
169
- console.log(`Manifest digest: ${context.manifestDigest}`);
170
- console.log(`Knowledge writes: ${context.writes.length}`);
171
- console.log(`Archive target: ${context.archivePath}`);
172
- return;
173
- }
174
- await apply(options, context);
175
- }
176
-
177
- main().catch((error) => { console.error(`Ops close failed: ${error.message}`); process.exitCode = 1; });