@abelxiaoxing/cadence 1.8.4 → 2.0.1

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 (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,2006 +1,522 @@
1
- // Generated by scripts/build-workspace-io.mjs; edit operator-cli-entry.mjs and its source modules.
1
+ // Generated by scripts/build-operator.mjs; edit operator-cli-entry.mjs and its source modules.
2
2
  // src/operator-cli-entry.mjs
3
- import path9 from "node:path";
4
-
5
- // src/operator-tools.ts
6
- import { spawnSync as spawnSync2 } from "node:child_process";
7
- import { lstatSync as lstatSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, realpathSync as realpathSync4 } from "node:fs";
8
- import path8 from "node:path";
9
- import { DatabaseSync } from "node:sqlite";
10
-
11
- // src/execution-profile.ts
12
3
  import path from "node:path";
13
- import { fileURLToPath } from "node:url";
14
- function executionProfile(env = process.env) {
15
- const mode = env.ABEL_EXECUTION_MODE ?? "isolated";
16
- if (mode !== "isolated" && mode !== "local-trusted" && mode !== "host-trusted")
17
- throw new Error("execution-mode-invalid");
18
- const timeoutMs = Number(env.ABEL_VERIFICATION_TIMEOUT_MS ?? 600000);
19
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86400000)
20
- throw new Error("verification-timeout-invalid");
21
- const inheritEnvironment = (env.ABEL_VERIFICATION_ENV ?? "").split(",").map((name) => name.trim()).filter(Boolean);
22
- if (inheritEnvironment.some((name) => !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)))
23
- throw new Error("verification-environment-names-invalid");
24
- if (mode === "isolated" && inheritEnvironment.length)
25
- throw new Error("verification-environment-requires-trusted-mode");
26
- if (mode === "host-trusted" && env.ABEL_WINDOWS_JOB_HELPER !== undefined && (!env.ABEL_WINDOWS_JOB_HELPER || env.ABEL_WINDOWS_JOB_HELPER.includes("\x00") || !(path.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER) || path.win32.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER))))
27
- throw new Error("windows-job-helper-path-invalid");
28
- return {
29
- mode,
30
- timeoutMs,
31
- bwrapPath: env.ABEL_BWRAP_PATH ?? "/usr/bin/bwrap",
32
- inheritEnvironment,
33
- ...mode === "host-trusted" ? {
34
- windowsHelperPath: env.ABEL_WINDOWS_JOB_HELPER ?? fileURLToPath(new URL("./windows-job.exe", import.meta.url))
35
- } : {}
36
- };
37
- }
38
4
 
39
- // src/openspec-cli.ts
40
- import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
41
- import path2 from "node:path";
42
- var PACKAGE_NAME = "@fission-ai/openspec";
43
- var OPENSPEC_LIMITS = Object.freeze({
44
- timeoutMs: 30000,
45
- maxBuffer: 4 * 1024 * 1024
46
- });
47
-
48
- class OpenSpecCliError extends Error {
49
- diagnostic;
50
- constructor(command, phase, reason, detail = {}) {
51
- super("design-openspec-unavailable");
52
- this.name = "OpenSpecCliError";
53
- this.diagnostic = Object.freeze({
54
- code: "design-openspec-unavailable",
55
- command,
56
- phase,
57
- reason,
58
- ...["ENOENT", "EINVAL", "EACCES", "EPERM", "ENOEXEC", "E2BIG"].includes(detail.systemCode ?? "") ? { systemCode: detail.systemCode } : {},
59
- ...Number.isSafeInteger(detail.exitCode) && (detail.exitCode ?? -1) >= 0 ? { exitCode: String(detail.exitCode) } : {}
60
- });
61
- }
62
- }
63
- function record(value) {
64
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
65
- }
66
- function inside(root, target) {
67
- const relative = path2.relative(root, target);
68
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
69
- }
70
- function envValue(environment, name) {
71
- const key = Object.keys(environment).sort().find((candidate) => process.platform === "win32" ? candidate.toUpperCase() === name.toUpperCase() : candidate === name);
72
- return key === undefined ? undefined : environment[key];
73
- }
74
- function packageEntry(root) {
75
- const canonicalRoot = realpathSync(root);
76
- const manifestPath = realpathSync(path2.join(canonicalRoot, "package.json"));
77
- const manifestStat = lstatSync(manifestPath);
78
- if (!inside(canonicalRoot, manifestPath) || !manifestStat.isFile() || manifestStat.size > 1024 * 1024)
79
- throw new Error("manifest-limit");
80
- const manifest = record(JSON.parse(readFileSync(manifestPath, "utf8")));
81
- const bin = typeof manifest?.bin === "string" ? manifest.bin : record(manifest?.bin)?.openspec;
82
- if (manifest?.name !== PACKAGE_NAME || typeof bin !== "string" || path2.isAbsolute(bin))
83
- throw new Error("manifest-invalid");
84
- const entry = realpathSync(path2.resolve(canonicalRoot, bin));
85
- if (!inside(canonicalRoot, entry) || !/\.(?:cjs|mjs|js)$/iu.test(entry) || !lstatSync(entry).isFile())
86
- throw new Error("entry-invalid");
87
- return entry;
88
- }
89
- function canonicalConsumerRoot(consumerRoot) {
90
- try {
91
- const root = realpathSync(consumerRoot);
92
- if (!lstatSync(root).isDirectory())
93
- throw new Error("cwd-invalid");
94
- return root;
95
- } catch (error) {
96
- throw new OpenSpecCliError("resolve", "resolution", "cwd-unavailable", {
97
- systemCode: error.code
98
- });
99
- }
100
- }
101
- function commandIn(directory) {
102
- const names = process.platform === "win32" ? ["openspec.exe", "openspec.cmd", "openspec"] : ["openspec"];
103
- const entries = process.platform === "win32" ? readdirSync(directory) : names;
104
- for (const name of names) {
105
- const matches = entries.filter((entry) => entry.toLowerCase() === name);
106
- if (matches.length > 1)
107
- throw new Error("ambiguous-command");
108
- if (matches.length === 1) {
109
- const candidate = path2.join(directory, matches[0]);
110
- if (lstatSync(candidate, { throwIfNoEntry: false }))
111
- return candidate;
112
- }
113
- }
114
- return;
115
- }
116
- function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
117
- const configuredRoot = envValue(environment, "ABEL_OPENSPEC_PACKAGE_ROOT");
118
- const configuredNode = envValue(environment, "ABEL_OPENSPEC_NODE");
119
- const node = configuredNode ?? (process.release.name === "node" && !process.versions.bun ? process.execPath : undefined);
120
- let executable;
121
- try {
122
- if (!node || !path2.isAbsolute(node) || /\.(?:cmd|bat|ps1)$/iu.test(node)) {
123
- throw new Error("runtime-invalid");
124
- }
125
- executable = realpathSync(node);
126
- if (!lstatSync(executable).isFile())
127
- throw new Error("runtime-invalid");
128
- } catch {
129
- throw new OpenSpecCliError("resolve", "resolution", "runtime-unavailable");
130
- }
131
- if (configuredRoot !== undefined) {
132
- try {
133
- if (!path2.isAbsolute(configuredRoot))
134
- throw new Error("root-invalid");
135
- return { executable, entry: packageEntry(configuredRoot) };
136
- } catch {
137
- throw new OpenSpecCliError("resolve", "resolution", "installation-invalid");
138
- }
139
- }
140
- const consumer = canonicalConsumerRoot(consumerRoot);
141
- const directories = (envValue(environment, "PATH") ?? "").split(path2.delimiter);
142
- for (const directory of directories) {
143
- if (!path2.isAbsolute(directory))
144
- continue;
145
- let command;
146
- let canonicalDirectory;
147
- try {
148
- canonicalDirectory = realpathSync(directory);
149
- if (inside(consumer, canonicalDirectory))
150
- continue;
151
- command = commandIn(canonicalDirectory);
152
- } catch {
153
- continue;
154
- }
155
- if (!command)
156
- continue;
157
- try {
158
- const target = realpathSync(command);
159
- if (inside(consumer, target)) {
160
- throw new OpenSpecCliError("resolve", "resolution", "unsafe-installation");
161
- }
162
- const layoutRoots = [
163
- path2.join(canonicalDirectory, "node_modules", PACKAGE_NAME),
164
- path2.resolve(canonicalDirectory, "../lib/node_modules", PACKAGE_NAME)
165
- ];
166
- const roots = [];
167
- let ancestor = path2.dirname(target);
168
- for (let depth = 0;depth < 8; depth++) {
169
- roots.push(ancestor);
170
- const parent = path2.dirname(ancestor);
171
- if (parent === ancestor)
172
- break;
173
- ancestor = parent;
174
- }
175
- const linked = lstatSync(command).isSymbolicLink();
176
- const candidates = linked ? roots : layoutRoots;
177
- for (const root of candidates) {
178
- let entry;
179
- try {
180
- entry = packageEntry(root);
181
- if (linked && entry !== target)
182
- continue;
183
- } catch {
184
- continue;
185
- }
186
- if (inside(consumer, entry)) {
187
- throw new OpenSpecCliError("resolve", "resolution", "unsafe-installation");
188
- }
189
- return { executable, entry };
190
- }
191
- } catch (error) {
192
- if (error instanceof OpenSpecCliError)
193
- throw error;
194
- }
195
- throw new OpenSpecCliError("resolve", "resolution", "installation-invalid");
196
- }
197
- throw new OpenSpecCliError("resolve", "resolution", "installation-not-found");
198
- }
199
-
200
- // src/state-root.ts
201
- import { createHash } from "node:crypto";
202
- import { chmodSync, lstatSync as lstatSync2, mkdirSync, realpathSync as realpathSync2 } from "node:fs";
203
- import os from "node:os";
204
- import path3 from "node:path";
205
- class StateRootError extends Error {
206
- code;
207
- constructor(code) {
208
- super(code);
209
- this.name = "StateRootError";
210
- this.code = code;
211
- }
212
- }
213
- function isContainedBy(parent, candidate) {
214
- const relative = path3.relative(parent, candidate);
215
- return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
216
- }
217
- function inspectExistingComponents(absolutePath) {
218
- const parsed = path3.parse(absolutePath);
219
- const relativeParts = absolutePath.slice(parsed.root.length).split(path3.sep).filter(Boolean);
220
- let current = parsed.root;
221
- for (const part of relativeParts) {
222
- current = path3.join(current, part);
223
- let stat;
224
- try {
225
- stat = lstatSync2(current);
226
- } catch (error) {
227
- if (error.code === "ENOENT")
228
- return;
229
- throw error;
230
- }
231
- if (stat.isSymbolicLink()) {
232
- throw new StateRootError("state-component-symlink");
233
- }
234
- if (!stat.isDirectory() && current !== absolutePath) {
235
- throw new StateRootError("state-component-type");
236
- }
237
- }
238
- }
239
- function canonicalFuturePath(input) {
240
- const absolute = path3.resolve(input);
241
- inspectExistingComponents(absolute);
242
- let cursor = absolute;
243
- const missing = [];
244
- for (;; ) {
245
- try {
246
- return path3.join(realpathSync2.native(cursor), ...missing.reverse());
247
- } catch (error) {
248
- if (error.code !== "ENOENT")
249
- throw error;
250
- const parent = path3.dirname(cursor);
251
- if (parent === cursor)
252
- throw error;
253
- missing.push(path3.basename(cursor));
254
- cursor = parent;
255
- }
256
- }
257
- }
258
- function requireAbsoluteDirectoryBase(xdgStateHome, homeDir) {
259
- if (xdgStateHome && path3.isAbsolute(xdgStateHome)) {
260
- return canonicalFuturePath(xdgStateHome);
261
- }
262
- if (!path3.isAbsolute(homeDir)) {
263
- throw new StateRootError("invalid-state-base");
264
- }
265
- return canonicalFuturePath(path3.join(homeDir, ".local", "state"));
266
- }
267
- function resolveStateRoot(options) {
268
- let consumerRoot;
269
- try {
270
- consumerRoot = realpathSync2.native(path3.resolve(options.consumerRoot));
271
- if (!lstatSync2(consumerRoot).isDirectory()) {
272
- throw new StateRootError("consumer-root-unavailable");
273
- }
274
- } catch (error) {
275
- if (error instanceof StateRootError)
276
- throw error;
277
- throw new StateRootError("consumer-root-unavailable");
278
- }
279
- const stateBase = requireAbsoluteDirectoryBase(options.xdgStateHome, options.homeDir ?? os.homedir());
280
- const consumerRootHash = createHash("sha256").update(consumerRoot).digest("hex");
281
- const productRoot = canonicalFuturePath(path3.join(stateBase, "abel-cadence"));
282
- const rootsDir = canonicalFuturePath(path3.join(productRoot, "roots"));
283
- const rootDir = canonicalFuturePath(path3.join(rootsDir, consumerRootHash));
284
- if (isContainedBy(consumerRoot, rootDir)) {
285
- throw new StateRootError("state-root-inside-consumer");
286
- }
287
- return {
288
- consumerRoot,
289
- consumerRootHash,
290
- stateBase,
291
- productRoot,
292
- rootsDir,
293
- rootDir,
294
- databasePath: path3.join(rootDir, "control.sqlite3"),
295
- artifactsDir: path3.join(rootDir, "artifacts"),
296
- workspacesDir: path3.join(rootDir, "workspaces"),
297
- transactionsDir: path3.join(rootDir, "transactions")
298
- };
299
- }
300
-
301
- // src/transport-budget.ts
302
- var REQUEST_BOUNDS = Object.freeze({
303
- firstProgressMs: 90000,
304
- streamIdleMs: 180000,
305
- cancellationDrainMs: 250
306
- });
307
- function requestBounds(env = process.env) {
308
- const read = (name, fallback) => {
309
- const value = Number(env[name] ?? fallback);
310
- if (!Number.isSafeInteger(value) || value < 1 || value > 1200000)
311
- throw new Error("request-budget-invalid");
312
- return value;
313
- };
314
- return {
315
- ...REQUEST_BOUNDS,
316
- firstProgressMs: read("ABEL_FIRST_PROGRESS_MS", REQUEST_BOUNDS.firstProgressMs),
317
- streamIdleMs: read("ABEL_STREAM_IDLE_MS", REQUEST_BOUNDS.streamIdleMs)
318
- };
319
- }
320
-
321
- // src/verification-capability.ts
5
+ // src/task-store.ts
6
+ import { spawnSync } from "node:child_process";
7
+ import { createHash, randomUUID } from "node:crypto";
322
8
  import {
323
- lstatSync as lstatSync5,
324
- readdirSync as readdirSync3,
325
- readFileSync as readFileSync3,
326
- realpathSync as realpathSync3
9
+ closeSync,
10
+ constants,
11
+ fstatSync,
12
+ fsyncSync,
13
+ lstatSync,
14
+ mkdirSync,
15
+ openSync,
16
+ readdirSync,
17
+ readSync,
18
+ realpathSync,
19
+ renameSync,
20
+ unlinkSync,
21
+ writeFileSync
327
22
  } from "node:fs";
328
- import path5 from "node:path";
23
+ import { homedir, hostname } from "node:os";
24
+ import {
25
+ dirname,
26
+ isAbsolute,
27
+ join,
28
+ parse,
29
+ relative,
30
+ resolve,
31
+ sep
32
+ } from "node:path";
329
33
 
330
- // src/control-contracts.ts
331
- var CONTROL_SCHEMA_PROPERTIES = {
332
- command: {
333
- type: "string",
334
- enum: ["start", "status", "resume", "rebind", "cancel", "discard"]
335
- },
336
- stage: { type: "string", enum: ["abel-implement"] },
337
- change: { type: "string" },
338
- operationId: { type: "string" },
339
- deliveryRevision: { type: "integer", minimum: 1 },
340
- receiptHash: { type: "string" },
341
- routeId: { type: "string" },
342
- recovery: {
343
- type: "object",
344
- additionalProperties: false,
345
- properties: {
346
- incidentKey: { type: "string", pattern: "^[a-f0-9]{64}$" },
347
- failureSequence: { type: "integer", minimum: 1 },
348
- reason: {
349
- type: "string",
350
- enum: ["parent-directed-retry", "route-changed", "context-extended"]
351
- }
352
- },
353
- required: ["incidentKey", "failureSequence", "reason"]
354
- }
355
- };
356
- var commandSchema = (command, fields) => ({
357
- type: "object",
358
- properties: {
359
- command: { type: "string", enum: [command] },
360
- ...Object.fromEntries(fields.map((field) => [field, CONTROL_SCHEMA_PROPERTIES[field]]))
361
- },
362
- required: ["command", ...fields],
363
- additionalProperties: false
364
- });
365
- var CONTROL_COMMAND_PARAMETERS = {
366
- type: "object",
367
- properties: CONTROL_SCHEMA_PROPERTIES,
368
- required: ["command", "stage", "change"],
369
- anyOf: [
370
- commandSchema("start", ["stage", "change", "operationId"]),
371
- commandSchema("status", ["stage", "change"]),
372
- commandSchema("resume", ["stage", "change", "operationId"]),
373
- commandSchema("resume", ["stage", "change", "operationId", "recovery"]),
374
- commandSchema("resume", [
375
- "stage",
376
- "change",
377
- "operationId",
378
- "deliveryRevision",
379
- "receiptHash",
380
- "recovery"
381
- ]),
382
- commandSchema("resume", [
383
- "stage",
384
- "change",
385
- "operationId",
386
- "deliveryRevision",
387
- "receiptHash"
388
- ]),
389
- commandSchema("rebind", ["stage", "change", "operationId", "routeId"]),
390
- commandSchema("cancel", ["stage", "change", "operationId"]),
391
- commandSchema("discard", ["stage", "change", "operationId"])
392
- ],
393
- additionalProperties: false
394
- };
395
- var CONTROL_TOOL_FIELDS = new Set([
396
- "command",
397
- "stage",
398
- "change",
399
- "operationId",
400
- "deliveryRevision",
401
- "receiptHash",
402
- "routeId",
403
- "recovery"
404
- ]);
405
- var COMMAND_FIELDS = Object.freeze({
406
- start: new Set(["command", "stage", "change", "operationId"]),
407
- status: new Set(["command", "stage", "change"]),
408
- resume: new Set([
409
- "command",
410
- "stage",
411
- "change",
412
- "operationId",
413
- "deliveryRevision",
414
- "receiptHash",
415
- "recovery"
416
- ]),
417
- rebind: new Set(["command", "stage", "change", "operationId", "routeId"]),
418
- cancel: new Set(["command", "stage", "change", "operationId"]),
419
- discard: new Set(["command", "stage", "change", "operationId"])
420
- });
421
- // src/contracts.ts
422
- var LIMITS = {
423
- maxActiveChildSessions: 4,
424
- maxEnvelopeBytes: 64 * 1024,
425
- phaseTimeoutMs: 20 * 60 * 1000,
426
- maxCompleteResultBytes: 64 * 1024
427
- };
428
- var VERIFICATION_LIMITS = Object.freeze({
429
- minSteps: 1,
430
- maxSteps: 8,
431
- minTestFiles: 1,
432
- maxTestFiles: 64
433
- });
434
- var NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
435
- var RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
436
- function isValidRelativePath(p) {
437
- return typeof p === "string" && p.length > 0 && p.length <= 512 && !NONCANONICAL.test(p) && !p.startsWith("/") && !/^[a-z]:\//iu.test(p) && (p === "." || !p.startsWith("./") && !p.endsWith("/") && !p.split("/").includes(".")) && !p.includes("..") && !p.includes("\\") && !p.includes("\x00");
438
- }
439
- var UNSAFE_VERIFICATION_TOKEN = /[;&|`$<>\n\r\0]/u;
440
- var VERIFICATION_NAME = /^[a-z0-9][a-z0-9._:@/-]*$/iu;
441
- var EXECUTABLE_NAME = /^[a-z0-9][a-z0-9._-]*$/iu;
442
- var IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
443
- function validVerificationToken(value) {
444
- const optionValue = typeof value === "string" && value.includes("=") ? value.slice(value.indexOf("=") + 1) : value;
445
- return typeof value === "string" && value.length > 0 && value.length <= 512 && value.trim() === value && !UNSAFE_VERIFICATION_TOKEN.test(value) && !value.includes("\\") && !value.startsWith("/") && !/^[a-z]:\//iu.test(value) && !/(^|\/)\.\.(\/|$)/u.test(value) && typeof optionValue === "string" && !optionValue.startsWith("/") && !/^[a-z]:\//iu.test(optionValue) && !/(^|\/)\.\.(\/|$)/u.test(optionValue);
446
- }
447
- function validVerificationArgs(value) {
448
- return Array.isArray(value) && value.length <= 128 && value.every(validVerificationToken);
449
- }
450
- function validScriptCommand(value) {
451
- return typeof value === "string" && value.length > 0 && value.length <= 1024 && value.trim() === value && !value.includes("\x00");
452
- }
453
- function validVerificationIdentity(contract) {
454
- if (!validIdentifier(contract.id) || !["expected-red", "expected-green", "expected-refactor"].includes(String(contract.classification))) {
455
- return false;
456
- }
457
- return contract.classification === "expected-red" ? typeof contract.expectedFailure === "string" && contract.expectedFailure.length > 0 && contract.expectedFailure.length <= 512 : contract.expectedFailure === undefined;
458
- }
459
- function validatePackageScriptRunner(value) {
460
- if (!hasExactKeys(value, ["kind", "packageManager", "script", "command"]) || value.kind !== "package-script" || !["bun", "npm", "pnpm", "yarn"].includes(String(value.packageManager)) || typeof value.script !== "string" || !VERIFICATION_NAME.test(value.script) || value.script.includes("/") || !validScriptCommand(value.command)) {
461
- return false;
462
- }
463
- return true;
464
- }
465
- function validateExecutableRunner(value) {
466
- if (!value || typeof value !== "object" || Array.isArray(value))
467
- return false;
468
- const runner = value;
469
- if (runner.kind === "package-script") {
470
- return validatePackageScriptRunner(runner);
471
- }
472
- if (runner.kind === "local-binary" && hasExactKeys(runner, ["kind", "executable"])) {
473
- return typeof runner.executable === "string" && EXECUTABLE_NAME.test(runner.executable);
474
- }
475
- return runner.kind === "npx" && hasExactKeys(runner, ["kind", "executable", "noInstall"]) && typeof runner.executable === "string" && EXECUTABLE_NAME.test(runner.executable) && runner.noInstall === true;
476
- }
477
- function validateVitestRunner(value) {
478
- if (value.kind === "package-script") {
479
- if (UNSAFE_VERIFICATION_TOKEN.test(value.command))
480
- return false;
481
- const tokens = value.command.split(/\s+/u);
482
- return tokens[0] === "vitest" || tokens[0] === "npx" && tokens[1] === "--no-install" && tokens[2] === "vitest";
483
- }
484
- return value.executable === "vitest";
485
- }
486
- function validateAtomicVerification(value) {
487
- if (!value || typeof value !== "object" || Array.isArray(value))
488
- return false;
489
- const contract = value;
490
- if (contract.executionBindings !== undefined && (!contract.executionBindings || typeof contract.executionBindings !== "object" || Array.isArray(contract.executionBindings) || Object.entries(contract.executionBindings).some(([key, hash]) => !VERIFICATION_CONFIGURATION_PATHS.includes(key) || hash !== null && (typeof hash !== "string" || !/^[a-f0-9]{64}$/u.test(hash)))))
491
- return false;
492
- const expectedFailure = [
493
- ...contract.classification === "expected-red" ? ["expectedFailure"] : [],
494
- ...contract.executionBindings === undefined ? [] : ["executionBindings"]
495
- ];
496
- if (contract.kind === "vitest") {
497
- const executableRunner = contract.runner;
498
- return hasExactKeys(contract, [
499
- "kind",
500
- "id",
501
- "runner",
502
- "testFiles",
503
- "args",
504
- "classification",
505
- ...expectedFailure,
506
- "minTests"
507
- ]) && validVerificationIdentity(contract) && validateExecutableRunner(contract.runner) && validateVitestRunner(executableRunner) && Array.isArray(contract.testFiles) && contract.testFiles.length >= VERIFICATION_LIMITS.minTestFiles && contract.testFiles.length <= VERIFICATION_LIMITS.maxTestFiles && contract.testFiles.every(isValidRelativePath) && new Set(contract.testFiles).size === contract.testFiles.length && validVerificationArgs(contract.args) && Number.isSafeInteger(contract.minTests) && contract.minTests >= 1;
508
- }
509
- if (contract.kind === "package-script") {
510
- const runner2 = {
511
- kind: "package-script",
512
- packageManager: contract.packageManager,
513
- script: contract.script,
514
- command: contract.command
515
- };
516
- return hasExactKeys(contract, [
517
- "kind",
518
- "id",
519
- "packageManager",
520
- "script",
521
- "command",
522
- "args",
523
- "classification",
524
- ...expectedFailure
525
- ]) && validVerificationIdentity(contract) && validatePackageScriptRunner(runner2) && validVerificationArgs(contract.args);
526
- }
527
- if (contract.kind !== "static-check")
528
- return false;
529
- const runner = contract.runner;
530
- const validRunner = validateExecutableRunner(runner) || hasExactKeys(runner, ["kind", "script"]) && runner.kind === "node" && isValidRelativePath(runner.script);
531
- return hasExactKeys(contract, [
532
- "kind",
533
- "id",
534
- "runner",
535
- "args",
536
- "classification",
537
- ...expectedFailure
538
- ]) && validVerificationIdentity(contract) && validRunner && validVerificationArgs(contract.args);
539
- }
540
- function validateStructuredVerification(value) {
541
- if (!value || typeof value !== "object" || Array.isArray(value))
542
- return false;
543
- const contract = value;
544
- if (contract.kind !== "steps") {
545
- return validateAtomicVerification(contract);
546
- }
547
- if (!hasExactKeys(contract, ["kind", "id", "classification", "steps"]) || !validIdentifier(contract.id) || !["expected-red", "expected-green", "expected-refactor"].includes(String(contract.classification)) || !Array.isArray(contract.steps) || contract.steps.length < VERIFICATION_LIMITS.minSteps || contract.steps.length > VERIFICATION_LIMITS.maxSteps || !contract.steps.every((step) => validateAtomicVerification(step))) {
548
- return false;
549
- }
550
- const steps = contract.steps;
551
- return steps.slice(0, -1).every((step) => step.classification === "expected-green") && steps.at(-1)?.classification === contract.classification && new Set(steps.map((step) => step.id)).size === steps.length;
552
- }
553
- function validateVerificationContract(value) {
554
- if (validateStructuredVerification(value)) {
555
- return { ok: true, value: structuredClone(value) };
556
- }
34
+ // src/task-types.ts
35
+ var TASK_SCHEMA = "cadence-workflow-assistant/1";
36
+ var MAX_RECORD_BYTES = 128 * 1024;
37
+ function object(value) {
38
+ return value !== null && typeof value === "object" && !Array.isArray(value);
39
+ }
40
+ function boundedString(value, max) {
41
+ return typeof value === "string" && Buffer.byteLength(value) <= max && !value.includes("\x00");
42
+ }
43
+ function validId(value) {
44
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
45
+ }
46
+ function decodeCheck(value) {
47
+ if (!object(value) || !boundedString(value.name, 128) || !value.name.trim() || !boundedString(value.command, 4096) || !value.command.trim() || !Number.isInteger(value.timeoutMs) || Number(value.timeoutMs) < 100 || Number(value.timeoutMs) > 1200000)
48
+ throw new Error("Invalid check definition");
49
+ const controls = /[\x00-\x08\x0b-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/u;
50
+ if (controls.test(value.command) || controls.test(value.name) || /[\n\t]/u.test(value.name))
51
+ throw new Error("Check commands/names must be visibly representable in the terminal");
557
52
  return {
558
- ok: false,
559
- reason: "verification contract is unsupported by the Implement runtime"
53
+ name: value.name,
54
+ command: value.command,
55
+ timeoutMs: Number(value.timeoutMs)
560
56
  };
561
57
  }
562
- function verificationSteps(verification) {
563
- const normalized = validateVerificationContract(verification);
564
- if (!normalized.ok)
565
- return [];
566
- return normalized.value.kind === "steps" ? structuredClone(normalized.value.steps) : [structuredClone(normalized.value)];
567
- }
568
- var VERIFICATION_CONFIGURATION_PATHS = [
569
- "package.json",
570
- "package-lock.json",
571
- "npm-shrinkwrap.json",
572
- "bun.lock",
573
- "bun.lockb",
574
- "pnpm-lock.yaml",
575
- "yarn.lock",
576
- ".npmrc",
577
- ".yarnrc",
578
- ".yarnrc.yml",
579
- "bunfig.toml",
580
- "pnpm-workspace.yaml"
581
- ];
582
- function verificationBoundInputPaths(verification) {
583
- return [
584
- ...new Set([
585
- ...verificationInputPaths(verification),
586
- ...verificationSteps(verification).flatMap((step) => step.kind === "vitest" || step.kind === "package-script" || ("runner" in step) && step.runner.kind === "package-script" ? VERIFICATION_CONFIGURATION_PATHS : [])
587
- ])
588
- ];
589
- }
590
- function verificationInputPaths(verification) {
591
- return [
592
- ...new Set(verificationSteps(verification).flatMap((step) => {
593
- const paths = step.kind === "vitest" ? [...step.testFiles] : step.kind === "static-check" && step.runner.kind === "node" ? [step.runner.script] : [];
594
- if (step.kind === "package-script" || "runner" in step && step.runner.kind === "package-script") {
595
- paths.push("package.json");
596
- }
597
- return paths;
598
- }))
599
- ];
600
- }
601
- function hasExactKeys(value, required, optional = []) {
602
- if (!value || typeof value !== "object" || Array.isArray(value))
603
- return false;
604
- const record2 = value;
605
- const allowed = new Set([...required, ...optional]);
606
- return required.every((key) => Object.hasOwn(record2, key)) && Object.keys(record2).every((key) => allowed.has(key));
607
- }
608
- function validIdentifier(value) {
609
- return typeof value === "string" && IDENTIFIER.test(value);
58
+ function decodeTask(value) {
59
+ if (!object(value) || value.schema !== TASK_SCHEMA || typeof value.id !== "string" || !validId(value.id) || !Number.isSafeInteger(value.revision) || Number(value.revision) < 1 || typeof value.kind !== "string" || !["code", "research", "diagnosis"].includes(value.kind) || typeof value.phase !== "string" || ![
60
+ "planning",
61
+ "running",
62
+ "review",
63
+ "interrupted",
64
+ "failed",
65
+ "done"
66
+ ].includes(value.phase) || !boundedString(value.goal, 8192) || !value.goal.trim() || !boundedString(value.plan, 32768) || !boundedString(value.cwd, 4096) || !boundedString(value.result, 16384) || !boundedString(value.baseline, 16384) || !Array.isArray(value.checks) || value.checks.length > 32 || !Array.isArray(value.activity) || value.activity.length > 64)
67
+ throw new Error("Unknown or corrupt task record");
68
+ for (const check of value.checks) {
69
+ decodeCheck(check);
70
+ if (!object(check) || !boundedString(check.cwd, 4096) || !(check.exitCode === null || Number.isInteger(check.exitCode)) || typeof check.durationMs !== "number" || !Number.isFinite(check.durationMs) || check.durationMs < 0 || typeof check.status !== "string" || !["passed", "failed", "canceled", "stale"].includes(check.status) || !boundedString(check.fingerprint, 128) || check.log !== undefined && (typeof check.log !== "string" || !/^[a-f0-9-]+\.log$/.test(check.log)))
71
+ throw new Error("Corrupt check record");
72
+ }
73
+ for (const entry of value.activity) {
74
+ if (!object(entry) || !boundedString(entry.at, 64) || !boundedString(entry.text, 512))
75
+ throw new Error("Corrupt activity");
76
+ }
77
+ if (value.review !== undefined && (!object(value.review) || !boundedString(value.review.fingerprint, 128) || typeof value.review.outcome !== "string" || !["reviewed", "reviewed-with-issues"].includes(value.review.outcome)))
78
+ throw new Error("Corrupt review");
79
+ const allowed = new Set([
80
+ "schema",
81
+ "id",
82
+ "revision",
83
+ "kind",
84
+ "goal",
85
+ "plan",
86
+ "cwd",
87
+ "phase",
88
+ "result",
89
+ "baseline",
90
+ "checks",
91
+ "review",
92
+ "activity"
93
+ ]);
94
+ if (Object.keys(value).some((key) => !allowed.has(key)))
95
+ throw new Error("Unknown task fields");
96
+ return structuredClone(value);
610
97
  }
611
98
 
612
- // src/file-snapshot.ts
613
- import { createHash as createHash2 } from "node:crypto";
614
- import { lstatSync as lstatSync4, readdirSync as readdirSync2, readFileSync as readFileSync2, readlinkSync } from "node:fs";
615
- import { join, resolve } from "node:path";
99
+ // src/verification-diagnostics.ts
100
+ function diagnosticText(value, maximum = 4096, secrets = []) {
101
+ maximum = Math.max(0, Math.floor(maximum));
102
+ let clean = value;
103
+ for (const secret of [...new Set(secrets)].filter(Boolean).sort((a, b) => b.length - a.length))
104
+ clean = clean.split(secret).join("[redacted]");
105
+ clean = clean.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/g, "").replace(/(Bearer\s+)\S+/gi, "$1[redacted]").replace(/((?:api[_-]?key|token|password|secret)\s*[:=]\s*)[^\s,;]+/gi, "$1[redacted]").replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi, "$1[redacted]@");
106
+ if (Buffer.byteLength(clean) <= maximum)
107
+ return clean;
108
+ const marker = maximum >= 12 ? `
109
+ [truncated]` : ".".repeat(maximum);
110
+ const bytes = Buffer.from(clean).subarray(0, Math.max(0, maximum - Buffer.byteLength(marker)));
111
+ return bytes.toString("utf8").replace(/\ufffd$/, "") + marker;
112
+ }
616
113
 
617
- // src/safe-path.ts
618
- import { lstatSync as lstatSync3 } from "node:fs";
619
- import path4 from "node:path";
620
- function observeSafePath(root, relative) {
621
- if (!isValidRelativePath(relative)) {
622
- return { kind: "unsafe", reason: "invalid-path" };
623
- }
114
+ // src/task-store.ts
115
+ function parseRecord(data) {
624
116
  try {
625
- const resolvedRoot = path4.resolve(root);
626
- const rootStat = lstatSync3(resolvedRoot, { throwIfNoEntry: false });
627
- if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
628
- return { kind: "unsafe", reason: "root-unavailable" };
629
- }
630
- if (relative === ".")
631
- return { kind: "directory" };
632
- let current = resolvedRoot;
633
- const parts = relative.split("/");
634
- for (const [index, part] of parts.entries()) {
635
- current = path4.join(current, part);
636
- if (!current.startsWith(`${resolvedRoot}${path4.sep}`)) {
637
- return { kind: "unsafe", reason: "invalid-path" };
638
- }
639
- const stat = lstatSync3(current, { throwIfNoEntry: false });
640
- if (!stat)
641
- return { kind: "absent" };
642
- if (stat.isSymbolicLink()) {
643
- return { kind: "unsafe", reason: "symlink" };
644
- }
645
- if (index < parts.length - 1) {
646
- if (!stat.isDirectory()) {
647
- return { kind: "unsafe", reason: "parent-not-directory" };
648
- }
649
- continue;
650
- }
651
- if (stat.isFile())
652
- return { kind: "file" };
653
- if (stat.isDirectory())
654
- return { kind: "directory" };
655
- return { kind: "unsafe", reason: "special-file" };
656
- }
117
+ return JSON.parse(data.toString("utf8"));
657
118
  } catch {
658
- return { kind: "unsafe", reason: "path-unavailable" };
119
+ throw new Error("Corrupt JSON state; preserved without reset");
659
120
  }
660
- return { kind: "unsafe", reason: "invalid-path" };
661
- }
662
-
663
- // src/file-snapshot.ts
664
- function sha256(bytes) {
665
- return createHash2("sha256").update(bytes).digest("hex");
666
121
  }
667
- function snapshotFile(root, relPath) {
668
- if (observeSafePath(root, relPath).kind !== "file")
669
- return null;
670
- const abs = resolve(root, relPath);
671
- const bytes = readFileSync2(abs);
672
- return { sha256: sha256(bytes), bytes: bytes.length, kind: "file" };
122
+ function absent(error) {
123
+ return object(error) && error.code === "ENOENT";
673
124
  }
674
-
675
- // src/verification-capability.ts
676
- var WINDOWS_EXECUTABLE_EXTENSIONS = new Set([".exe", ".cmd"]);
677
- var DEFAULT_WINDOWS_PATH_EXT = ".COM;.EXE;.BAT;.CMD";
678
- var SYSTEM_MOUNT_ROOTS = ["/usr", "/bin", "/lib", "/lib64", "/etc"];
679
- var BROAD_MOUNT_ROOTS = new Set([
680
- "/",
681
- "/home",
682
- "/root",
683
- "/tmp",
684
- "/var",
685
- "/var/tmp"
686
- ]);
687
- function capabilitySuccess(verificationId, runnerBindings = []) {
688
- const seen = new Set;
689
- return {
690
- ok: true,
691
- verificationId,
692
- runnerBindings: runnerBindings.filter((binding) => {
693
- const key = `${binding.command}\x00${binding.executablePath}\x00${JSON.stringify(binding.fixedArgs ?? [])}\x00${binding.mountSource ?? ""}`;
694
- if (seen.has(key))
125
+ function safeDirectory(path, create = false, inspectExisting) {
126
+ const full = resolve(path);
127
+ const root = parse(full).root;
128
+ if (inspectExisting) {
129
+ const st = lstatSync(root);
130
+ if (!st.isDirectory() || st.isSymbolicLink())
131
+ throw new Error("Unsafe directory");
132
+ inspectExisting(root, st);
133
+ }
134
+ let at = root;
135
+ for (const part of full.slice(root.length).split(sep).filter(Boolean)) {
136
+ at = join(at, part);
137
+ let existing;
138
+ try {
139
+ const st = lstatSync(at);
140
+ if (!st.isDirectory())
141
+ throw new Error("Unsafe directory");
142
+ const checked = lstatSync(at);
143
+ if (checked.isSymbolicLink() || inspectExisting && !checked.isDirectory())
144
+ throw new Error("Unsafe directory");
145
+ existing = checked;
146
+ } catch (error) {
147
+ if (!absent(error))
148
+ throw error;
149
+ if (!create)
695
150
  return false;
696
- seen.add(key);
697
- return true;
698
- }).map((binding) => ({
699
- ...binding,
700
- ...binding.fixedArgs ? { fixedArgs: [...binding.fixedArgs] } : {}
701
- }))
702
- };
703
- }
704
- function adapterFailure(verificationId, code, message, inputObservation) {
705
- return {
706
- ok: false,
707
- diagnostic: {
708
- kind: "verification-adapter",
709
- code,
710
- verificationId,
711
- message,
712
- ...inputObservation ? { inputObservation } : {}
713
- }
714
- };
715
- }
716
- function fileStatus(root, relative) {
717
- const observation = observeSafePath(root, relative);
718
- if (observation.kind === "file")
719
- return "regular";
720
- return observation.kind === "absent" ? "absent" : "unsafe";
721
- }
722
- function regularFile(root, relative) {
723
- return fileStatus(root, relative) === "regular";
724
- }
725
- function localExecutable(root, executable, dependencyOwner = root, environment = {}) {
726
- const dependencyRoot = path5.resolve(dependencyOwner, "node_modules");
727
- try {
728
- const canonicalDependencyRoot = realpathSync3(dependencyRoot);
729
- for (const name of localExecutableNames(executable, environment)) {
730
- const unresolved = resolveCandidateName(path5.resolve(dependencyRoot, ".bin"), name, environment.platform ?? process.platform);
731
- if (!unresolved)
732
- continue;
733
- const canonical = realpathSync3(unresolved);
734
- const stat = lstatSync5(canonical);
735
- if (canonical !== canonicalDependencyRoot && pathWithin(canonicalDependencyRoot, canonical, environment.platform ?? process.platform) && regularExecutableFile(stat, environment.platform ?? process.platform)) {
736
- return true;
151
+ try {
152
+ mkdirSync(at, { mode: 448 });
153
+ } catch (mkdirError) {
154
+ if (!object(mkdirError) || mkdirError.code !== "EEXIST")
155
+ throw mkdirError;
737
156
  }
157
+ if (!lstatSync(at).isDirectory() || lstatSync(at).isSymbolicLink())
158
+ throw new Error("Unsafe directory");
738
159
  }
739
- return false;
740
- } catch {
741
- return false;
742
- }
743
- }
744
- function pathApi(platform, ...values) {
745
- if (platform !== "win32")
746
- return path5.posix;
747
- if (process.platform === "win32" || values.some((value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"))) {
748
- return path5.win32;
749
- }
750
- return path5.posix;
751
- }
752
- function pathWithin(root, candidate, platform) {
753
- const platformPath = pathApi(platform, root, candidate);
754
- const normalizedRoot = platformPath.resolve(root);
755
- const relative = platformPath.relative(normalizedRoot, platformPath.resolve(candidate));
756
- return relative === "" || relative !== ".." && !relative.startsWith(`..${platformPath.sep}`) && !platformPath.isAbsolute(relative);
757
- }
758
- function regularExecutableFile(stat, platform) {
759
- return stat.isFile() && (platform === "win32" || (stat.mode & 73) !== 0);
760
- }
761
- function windowsPathExtensions(pathExt) {
762
- const seen = new Set;
763
- const extensions = [];
764
- for (const entry of (pathExt ?? DEFAULT_WINDOWS_PATH_EXT).split(";")) {
765
- const extension = entry.trim().toLowerCase();
766
- if (!WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) || seen.has(extension)) {
767
- continue;
768
- }
769
- seen.add(extension);
770
- extensions.push(extension);
771
- }
772
- return extensions;
773
- }
774
- function runnerNames(command, environment) {
775
- if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(command))
776
- return [];
777
- if ((environment.platform ?? process.platform) !== "win32") {
778
- return [command];
779
- }
780
- const extension = path5.extname(command).toLowerCase();
781
- if (extension) {
782
- return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [command] : [];
160
+ if (existing)
161
+ inspectExisting?.(at, existing);
783
162
  }
784
- return windowsPathExtensions(environment.pathExt).map((candidateExtension) => `${command}${candidateExtension}`);
785
- }
786
- function localExecutableNames(executable, environment) {
787
- if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(executable))
788
- return [];
789
- if ((environment.platform ?? process.platform) !== "win32") {
790
- return [executable];
791
- }
792
- const extension = path5.extname(executable).toLowerCase();
793
- if (extension) {
794
- return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [executable] : [];
795
- }
796
- return [
797
- executable,
798
- ...windowsPathExtensions(environment.pathExt).map((candidateExtension) => `${executable}${candidateExtension}`)
799
- ];
800
- }
801
- function resolveCandidateName(directory, name, platform) {
802
- const exact = path5.join(directory, name);
803
- if (platform !== "win32")
804
- return exact;
805
- try {
806
- const matches = readdirSync3(directory).filter((entry) => entry.toLowerCase() === name.toLowerCase());
807
- return matches.length === 1 ? path5.join(directory, matches[0]) : null;
808
- } catch {
809
- return null;
810
- }
811
- }
812
- function within(root, candidate) {
813
- return candidate === root || candidate.startsWith(`${root}${path5.sep}`);
814
- }
815
- function commonDirectory(left, right, platform = process.platform) {
816
- const platformPath = pathApi(platform, left, right);
817
- const leftResolved = platformPath.resolve(left);
818
- const rightResolved = platformPath.resolve(right);
819
- const leftRoot = platformPath.parse(leftResolved).root;
820
- const rightRoot = platformPath.parse(rightResolved).root;
821
- const comparable = (value) => platform === "win32" ? value.toLowerCase() : value;
822
- if (comparable(leftRoot) !== comparable(rightRoot))
823
- return leftRoot;
824
- const parts = (value, root) => platformPath.relative(root, value).split(platformPath.sep).filter(Boolean);
825
- const leftParts = parts(leftResolved, leftRoot);
826
- const rightParts = parts(rightResolved, rightRoot);
827
- const common = [];
828
- for (let index = 0;index < Math.min(leftParts.length, rightParts.length); index++) {
829
- const leftPart = leftParts[index];
830
- const rightPart = rightParts[index];
831
- if (comparable(leftPart) !== comparable(rightPart))
832
- break;
833
- common.push(leftPart);
834
- }
835
- return platformPath.join(leftRoot, ...common);
836
- }
837
- function unsafeMountSource(source) {
838
- if (BROAD_MOUNT_ROOTS.has(source))
839
- return true;
840
- if (path5.resolve(source) === path5.parse(path5.resolve(source)).root)
841
- return true;
842
- const parts = path5.relative(path5.parse(path5.resolve(source)).root, path5.resolve(source)).split(path5.sep).filter(Boolean).map((part) => part.toLowerCase());
843
- return parts[0] === "home" && parts.length <= 2 || parts[0] === "users" && parts.length <= 2 || parts[0] === "root" && parts.length <= 1;
844
- }
845
- function safeRunnerSource(paths, platform) {
846
- let source = path5.dirname(paths[0]);
847
- for (const candidate of paths.slice(1)) {
848
- source = commonDirectory(source, path5.dirname(candidate), platform);
849
- }
850
- if (unsafeMountSource(source))
851
- return null;
852
- try {
853
- const stat = lstatSync5(source);
854
- if (!stat.isDirectory() || stat.isSymbolicLink())
855
- return null;
856
- } catch {
857
- return null;
858
- }
859
- return paths.every((candidate) => pathWithin(source, candidate, platform)) ? source : null;
860
- }
861
- function resolvedRunnerEnvironment(environment) {
862
- return {
863
- platform: environment.platform ?? process.platform,
864
- path: environment.path ?? process.env.PATH ?? "",
865
- pathExt: environment.pathExt ?? process.env.PATHEXT ?? DEFAULT_WINDOWS_PATH_EXT,
866
- pathDelimiter: environment.pathDelimiter ?? path5.delimiter
867
- };
163
+ return true;
868
164
  }
869
- function windowsShimBinding(command, launcherPath, environment) {
870
- const name = path5.basename(command, path5.extname(command)).toLowerCase();
871
- if (name !== "npm" && name !== "npx")
872
- return null;
873
- const node = resolveVerificationRunner("node", environment);
874
- if (!node || node.fixedArgs || path5.extname(node.executablePath).toLowerCase() !== ".exe") {
875
- return null;
876
- }
877
- const cli = path5.join(path5.dirname(launcherPath), "node_modules", "npm", "bin", `${name}-cli.js`);
165
+ function readBounded(path, max) {
166
+ if (!safeDirectory(dirname(path)))
167
+ return;
168
+ let fd;
878
169
  try {
879
- const canonicalLauncher = realpathSync3(launcherPath);
880
- const canonicalCli = realpathSync3(cli);
881
- const cliStat = lstatSync5(canonicalCli);
882
- if (!cliStat.isFile())
883
- return null;
884
- const source = safeRunnerSource([node.executablePath, canonicalLauncher, canonicalCli], environment.platform);
885
- if (!source)
886
- return null;
887
- return {
888
- command,
889
- executablePath: node.executablePath,
890
- fixedArgs: [canonicalCli]
891
- };
892
- } catch {
893
- return null;
170
+ const st = lstatSync(path);
171
+ if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > max)
172
+ throw new Error("Unsafe or oversized file");
173
+ fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
174
+ } catch (error) {
175
+ if (absent(error))
176
+ return;
177
+ throw error;
894
178
  }
895
- }
896
- function localPackageDirectories(dependencyRoot) {
897
- const packages = [];
898
179
  try {
899
- for (const entry of readdirSync3(dependencyRoot, { withFileTypes: true })) {
900
- if (!entry.isDirectory() || entry.name.startsWith("."))
901
- continue;
902
- const candidate = path5.join(dependencyRoot, entry.name);
903
- if (!entry.name.startsWith("@")) {
904
- packages.push(candidate);
905
- continue;
906
- }
907
- for (const scoped of readdirSync3(candidate, { withFileTypes: true })) {
908
- if (scoped.isDirectory() && !scoped.name.startsWith(".")) {
909
- packages.push(path5.join(candidate, scoped.name));
910
- }
911
- }
180
+ const st = fstatSync(fd);
181
+ if (!st.isFile() || st.nlink !== 1 || st.size > max)
182
+ throw new Error("Unsafe or oversized file");
183
+ const buffer = Buffer.alloc(max + 1);
184
+ let size = 0;
185
+ while (size < buffer.length) {
186
+ const n = readSync(fd, buffer, size, buffer.length - size, null);
187
+ if (!n)
188
+ break;
189
+ size += n;
912
190
  }
913
- } catch {
914
- return [];
915
- }
916
- return packages;
917
- }
918
- function localPackageBinTargets(dependencyOwner, executable, platform) {
919
- const dependencyRoot = path5.resolve(dependencyOwner, "node_modules");
920
- let canonicalDependencyRoot;
921
- try {
922
- canonicalDependencyRoot = realpathSync3(dependencyRoot);
923
- } catch {
924
- return [];
925
- }
926
- const targets = [];
927
- for (const packageDirectory of localPackageDirectories(dependencyRoot)) {
928
- try {
929
- const directoryStat = lstatSync5(packageDirectory);
930
- if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
931
- continue;
932
- }
933
- const canonicalPackage = realpathSync3(packageDirectory);
934
- if (!pathWithin(canonicalDependencyRoot, canonicalPackage, platform) || canonicalPackage === canonicalDependencyRoot) {
935
- continue;
936
- }
937
- const manifestPath = path5.join(canonicalPackage, "package.json");
938
- const manifestStat = lstatSync5(manifestPath);
939
- if (!manifestStat.isFile() || manifestStat.isSymbolicLink())
940
- continue;
941
- const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
942
- if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
943
- continue;
944
- }
945
- const record2 = manifest;
946
- const packageName = typeof record2.name === "string" ? record2.name.split("/").pop() ?? "" : "";
947
- const bin = record2.bin;
948
- let relative;
949
- if (typeof bin === "string" && (platform === "win32" ? packageName.toLowerCase() === executable.toLowerCase() : packageName === executable)) {
950
- relative = bin;
951
- } else if (bin && typeof bin === "object" && !Array.isArray(bin)) {
952
- const entries = Object.entries(bin).filter(([name]) => platform === "win32" ? name.toLowerCase() === executable.toLowerCase() : name === executable);
953
- if (entries.length === 1)
954
- relative = entries[0]?.[1];
955
- }
956
- if (typeof relative !== "string" || relative.length === 0 || relative.includes("\x00")) {
957
- continue;
958
- }
959
- const unresolved = path5.resolve(canonicalPackage, relative);
960
- if (!pathWithin(canonicalPackage, unresolved, platform))
961
- continue;
962
- const canonical = realpathSync3(unresolved);
963
- const stat = lstatSync5(canonical);
964
- if (stat.isFile() && pathWithin(canonicalPackage, canonical, platform) && pathWithin(canonicalDependencyRoot, canonical, platform)) {
965
- targets.push(canonical);
966
- }
967
- } catch {}
191
+ if (size > max)
192
+ throw new Error("Oversized file");
193
+ return buffer.subarray(0, size);
194
+ } finally {
195
+ closeSync(fd);
968
196
  }
969
- return [...new Set(targets)];
970
197
  }
971
- function resolveLocalVerificationRunner(dependencyOwner, executable, runnerEnvironment) {
972
- const environment = resolvedRunnerEnvironment(runnerEnvironment);
973
- if (environment.platform !== "win32")
974
- return null;
975
- const targets = localPackageBinTargets(dependencyOwner, executable, environment.platform);
976
- if (targets.length !== 1)
977
- return null;
978
- const target = targets[0];
979
- const extension = path5.extname(target).toLowerCase();
980
- if (extension === ".exe") {
981
- return { command: executable, executablePath: target };
982
- }
983
- if (![".js", ".cjs", ".mjs"].includes(extension))
984
- return null;
985
- const node = resolveVerificationRunner("node", environment);
986
- if (!node || node.fixedArgs)
987
- return null;
198
+ function gitEnvironment() {
988
199
  return {
989
- command: executable,
990
- executablePath: node.executablePath,
991
- fixedArgs: [target]
200
+ ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_"))),
201
+ GIT_OPTIONAL_LOCKS: "0",
202
+ LC_ALL: "C"
992
203
  };
993
204
  }
994
- function resolveVerificationRunner(command, runnerEnvironment = {}) {
995
- const environment = resolvedRunnerEnvironment(runnerEnvironment);
996
- const directories = environment.path.split(environment.pathDelimiter).filter(Boolean);
997
- const seenDirectories = new Set;
998
- const candidates = [
999
- ...command === "bun" && process.versions.bun && environment.platform === process.platform ? [process.execPath] : [],
1000
- ...directories.flatMap((directory) => {
1001
- const resolvedDirectory = path5.resolve(directory);
1002
- const key = environment.platform === "win32" ? resolvedDirectory.toLowerCase() : resolvedDirectory;
1003
- if (seenDirectories.has(key))
1004
- return [];
1005
- seenDirectories.add(key);
1006
- return runnerNames(command, environment).flatMap((name) => {
1007
- const candidate = resolveCandidateName(resolvedDirectory, name, environment.platform);
1008
- return candidate ? [candidate] : [];
1009
- });
1010
- })
1011
- ];
1012
- const seenCandidates = new Set;
1013
- for (const candidate of candidates) {
1014
- try {
1015
- const executablePath = path5.resolve(candidate);
1016
- const candidateKey = environment.platform === "win32" ? executablePath.toLowerCase() : executablePath;
1017
- if (seenCandidates.has(candidateKey))
1018
- continue;
1019
- seenCandidates.add(candidateKey);
1020
- const canonical = realpathSync3(executablePath);
1021
- const stat = lstatSync5(canonical);
1022
- if (!regularExecutableFile(stat, environment.platform))
1023
- continue;
1024
- if (environment.platform === "win32") {
1025
- const extension = path5.extname(executablePath).toLowerCase();
1026
- if (extension === ".cmd") {
1027
- const shim = windowsShimBinding(command, executablePath, environment);
1028
- if (shim)
1029
- return shim;
1030
- continue;
1031
- }
1032
- if (extension !== ".exe")
1033
- continue;
1034
- if (!safeRunnerSource([executablePath, canonical], environment.platform)) {
1035
- continue;
1036
- }
1037
- return { command, executablePath };
1038
- }
1039
- const systemVisible = SYSTEM_MOUNT_ROOTS.some((root) => within(root, executablePath) && within(root, canonical));
1040
- if (systemVisible)
1041
- return { command, executablePath };
1042
- const mountSource = commonDirectory(path5.dirname(executablePath), path5.dirname(canonical), environment.platform);
1043
- if (unsafeMountSource(mountSource))
1044
- continue;
1045
- const sourceStat = lstatSync5(mountSource);
1046
- if (!sourceStat.isDirectory() || sourceStat.isSymbolicLink())
1047
- continue;
1048
- return { command, executablePath, mountSource };
1049
- } catch {}
1050
- }
1051
- return null;
1052
- }
1053
- function packageManifest(root) {
1054
- if (!regularFile(root, "package.json"))
1055
- return null;
1056
- try {
1057
- const value = JSON.parse(readFileSync3(path5.join(root, "package.json"), "utf8"));
1058
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1059
- } catch {
1060
- return null;
1061
- }
1062
- }
1063
- function validatePackageScript(root, verificationId, packageManager, script, approvedCommand, _dependencyOwner = root, runnerEnvironment = {}) {
1064
- const packageRunner = resolveVerificationRunner(packageManager, runnerEnvironment);
1065
- if (!packageRunner) {
1066
- return adapterFailure(verificationId, "runner-missing", `approved package manager ${packageManager} is unavailable or cannot be mounted safely`);
1067
- }
1068
- const manifest = packageManifest(root);
1069
- const scripts = manifest?.scripts;
1070
- const actual = scripts && typeof scripts === "object" && !Array.isArray(scripts) ? scripts[script] : undefined;
1071
- if (typeof actual !== "string") {
1072
- return adapterFailure(verificationId, "script-missing", `package.json script ${script} is missing`);
1073
- }
1074
- if (actual !== approvedCommand) {
1075
- return adapterFailure(verificationId, "script-command-mismatch", `package.json script ${script} differs from the Gate B contract`);
1076
- }
1077
- const runtimes = ["node", "bun", "npm", "npx", "pnpm", "yarn"].flatMap((command) => {
1078
- const binding = resolveVerificationRunner(command, runnerEnvironment);
1079
- return binding ? [binding] : [];
205
+ function resolveWorkspace(cwd) {
206
+ const actual = realpathSync(cwd);
207
+ const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
208
+ cwd: actual,
209
+ env: gitEnvironment(),
210
+ encoding: "utf8",
211
+ timeout: 3000,
212
+ maxBuffer: 8192,
213
+ windowsHide: true
1080
214
  });
1081
- return capabilitySuccess(verificationId, [packageRunner, ...runtimes]);
1082
- }
1083
- function unsupportedExecutionConfiguration(source) {
1084
- const restricted = new Set([
1085
- "scriptshell",
1086
- "userconfig",
1087
- "globalconfig",
1088
- "yarnpath",
1089
- "plugins",
1090
- "auth",
1091
- "authtoken",
1092
- "password",
1093
- "token",
1094
- "npmauthtoken",
1095
- "npmauthident"
1096
- ]);
1097
- for (const line of source.split(/\r?\n/u)) {
1098
- let quote = "";
1099
- let escaped = false;
1100
- let active = "";
1101
- const parts = [];
1102
- let flowDepth = 0;
1103
- for (const character of line) {
1104
- if (escaped) {
1105
- active += character;
1106
- escaped = false;
1107
- continue;
1108
- }
1109
- if (character === "\\" && quote === '"') {
1110
- active += character;
1111
- escaped = true;
1112
- continue;
1113
- }
1114
- if (quote) {
1115
- active += character;
1116
- if (character === quote)
1117
- quote = "";
1118
- } else if (character === '"' || character === "'") {
1119
- quote = character;
1120
- active += character;
1121
- } else if (character === "#" || character === ";" && active.trim() === "") {
1122
- break;
1123
- } else if (character === "{" || character === "}" || character === "," && flowDepth > 0) {
1124
- if (character === "{")
1125
- flowDepth++;
1126
- if (character === "}")
1127
- flowDepth = Math.max(0, flowDepth - 1);
1128
- parts.push(active);
1129
- active = "";
1130
- } else
1131
- active += character;
1132
- }
1133
- parts.push(active);
1134
- for (const part of parts) {
1135
- if (part.includes("${"))
1136
- return true;
1137
- const directive = part.match(/^\s*(?:"((?:\\.|[^"\\])*)"|'([^']*)'|([^\s=]+))\s*(.*)$/u);
1138
- if (!directive)
1139
- continue;
1140
- const rawKey = directive[1] ?? directive[2] ?? directive[3];
1141
- if (!rawKey.endsWith(":") && !directive[4])
1142
- continue;
1143
- const key = rawKey.replace(/:$/u, "").split(":").at(-1)?.replace(/[-_]/gu, "").toLowerCase();
1144
- if (key && restricted.has(key))
1145
- return true;
1146
- }
1147
- }
1148
- return false;
1149
- }
1150
- function validateAtomicCapability(root, step, dependencyOwner, runnerEnvironment, executionWritePaths) {
1151
- if (step.kind === "vitest" && [
1152
- ...step.args,
1153
- ...step.runner.kind === "package-script" ? step.runner.command.split(/\s+/u) : []
1154
- ].some((arg) => /^--(?:reporters?|outputFile)(?:[.=]|$)/u.test(arg))) {
1155
- return adapterFailure(step.id, "report-arguments-conflict", "Vitest report arguments are owned by the adapter");
1156
- }
1157
- if (step.executionBindings) {
1158
- for (const [relative, expected] of Object.entries(step.executionBindings)) {
1159
- if (executionWritePaths.includes(relative))
1160
- continue;
1161
- const observation = observeSafePath(root, relative);
1162
- if (expected === null && observation.kind !== "absent" || expected !== null && snapshotFile(root, relative)?.sha256 !== expected) {
1163
- return adapterFailure(step.id, "verification-config-mismatch", "Bound execution inputs changed; recompile the verification contract");
1164
- }
1165
- }
1166
- }
1167
- for (const relative of verificationBoundInputPaths(step).filter((file) => VERIFICATION_CONFIGURATION_PATHS.includes(file))) {
1168
- const observation = observeSafePath(root, relative);
1169
- if (!["file", "absent"].includes(observation.kind))
1170
- return adapterFailure(step.id, "verification-config-unsafe", "Execution configuration must be a regular file");
1171
- if (observation.kind !== "file" || ![
1172
- ".npmrc",
1173
- ".yarnrc",
1174
- ".yarnrc.yml",
1175
- "bunfig.toml",
1176
- "pnpm-workspace.yaml"
1177
- ].includes(relative))
1178
- continue;
1179
- if (lstatSync5(path5.join(root, relative)).size > 64 * 1024 || executionProfile().mode === "isolated" && unsupportedExecutionConfiguration(readFileSync3(path5.join(root, relative), "utf8"))) {
1180
- return adapterFailure(step.id, "verification-config-unsafe", "Execution configuration requires unsupported credentials or host configuration");
1181
- }
1182
- }
1183
- if (step.kind === "package-script") {
1184
- return validatePackageScript(root, step.id, step.packageManager, step.script, step.command, dependencyOwner, runnerEnvironment);
1185
- }
1186
- const runner = step.runner;
1187
- if (runner.kind === "package-script") {
1188
- const script = validatePackageScript(root, step.id, runner.packageManager, runner.script, runner.command, dependencyOwner, runnerEnvironment);
1189
- if (!script.ok)
1190
- return script;
1191
- if (step.kind === "vitest" && !localExecutable(root, "vitest", dependencyOwner, runnerEnvironment)) {
1192
- return adapterFailure(step.id, "local-executable-missing", "Vitest is not installed in consumer node_modules");
1193
- }
1194
- return script;
1195
- }
1196
- if (runner.kind === "node") {
1197
- const node = resolveVerificationRunner("node", runnerEnvironment);
1198
- return node ? capabilitySuccess(step.id, [node]) : adapterFailure(step.id, "runner-missing", "Node is unavailable or cannot be mounted safely");
1199
- }
1200
- if (!localExecutable(root, runner.executable, dependencyOwner, runnerEnvironment)) {
1201
- return adapterFailure(step.id, "local-executable-missing", `local executable ${runner.executable} is unavailable`);
1202
- }
1203
- if (runner.kind === "npx") {
1204
- const npx = resolveVerificationRunner("npx", runnerEnvironment);
1205
- return npx ? capabilitySuccess(step.id, [npx]) : adapterFailure(step.id, "runner-missing", "npx is unavailable or cannot be mounted safely");
1206
- }
1207
- if ((runnerEnvironment.platform ?? process.platform) === "win32") {
1208
- const binding = resolveLocalVerificationRunner(dependencyOwner, runner.executable, runnerEnvironment);
1209
- return binding ? capabilitySuccess(step.id, [binding]) : adapterFailure(step.id, "local-executable-missing", `local executable ${runner.executable} has no safe direct Windows binding`);
1210
- }
1211
- return capabilitySuccess(step.id);
1212
- }
1213
- function validateVerificationAdapterCapability(root, value, options = {}) {
1214
- return validateCapability(root, value, options, false);
1215
- }
1216
- function validateCapability(root, value, options, requireInputAvailability) {
1217
- const validation = validateVerificationContract(value);
1218
- if (!validation.ok) {
1219
- const id = value && typeof value === "object" && !Array.isArray(value) && typeof value.id === "string" ? String(value.id) : undefined;
1220
- return {
1221
- ok: false,
1222
- diagnostic: {
1223
- kind: "design-readiness",
1224
- code: "verification-contract-unsupported",
1225
- ...id ? { verificationId: id } : {},
1226
- message: validation.reason
1227
- }
1228
- };
1229
- }
1230
- const dependencyOwner = options.dependencyOwner ?? root;
1231
- return validateAcceptedVerificationCapability(root, validation.value, dependencyOwner, options.runnerEnvironment ?? {}, requireInputAvailability, options.executionWritePaths ?? []);
1232
- }
1233
- function validateAcceptedVerificationCapability(root, verification, dependencyOwner, runnerEnvironment, requireInputAvailability, executionWritePaths) {
1234
- if (requireInputAvailability) {
1235
- for (const input of verificationInputPaths(verification)) {
1236
- const status = fileStatus(root, input);
1237
- if (status !== "regular") {
1238
- const inputObservation = { path: input, kind: status };
1239
- return adapterFailure(verification.id, status === "absent" ? "input-missing" : "input-unsafe", `verification input ${input} is ${status}`, inputObservation);
1240
- }
1241
- }
1242
- }
1243
- const runnerBindings = [];
1244
- for (const step of verificationSteps(verification)) {
1245
- const capability = validateAtomicCapability(root, step, dependencyOwner, runnerEnvironment, executionWritePaths);
1246
- if (!capability.ok)
1247
- return capability;
1248
- runnerBindings.push(...capability.runnerBindings);
1249
- }
1250
- return capabilitySuccess(verification.id, runnerBindings);
1251
- }
1252
-
1253
- // src/windows-job-backend.ts
1254
- import { spawn as spawn2, spawnSync } from "node:child_process";
1255
- import { createHash as createHash3 } from "node:crypto";
1256
- import { lstatSync as lstatSync7, readFileSync as readFileSync4 } from "node:fs";
1257
- import path7 from "node:path";
1258
-
1259
- // src/isolation-backend.ts
1260
- import { spawn } from "node:child_process";
1261
- import { accessSync, constants, lstatSync as lstatSync6 } from "node:fs";
1262
- import path6 from "node:path";
1263
- var ISOLATION_RUN_LIMITS = Object.freeze({
1264
- timeoutMs: 10 * 60000,
1265
- maxOutputBytes: 256 * 1024,
1266
- terminateGraceMs: 1000
1267
- });
1268
- function defaultProbe(executable) {
215
+ const topLevel = result.stdout?.replace(process.platform === "win32" ? /\r?\n$/ : /\n$/, "");
216
+ if (result.status === 0 && topLevel)
217
+ return { root: realpathSync(topLevel), cwd: actual };
218
+ if (result.error && !object(result.error))
219
+ throw new Error("Cannot resolve workspace");
220
+ if (result.error && object(result.error) && result.error.code !== "ENOENT")
221
+ throw new Error("Workspace lookup failed");
222
+ if (!result.error && !result.stderr.includes("not a git repository"))
223
+ throw new Error("Git workspace lookup refused");
224
+ return { root: actual, cwd: actual };
225
+ }
226
+ function defaultStateHome() {
227
+ const candidate = process.platform === "win32" ? process.env.LOCALAPPDATA : process.env.XDG_STATE_HOME;
228
+ if (candidate && !isAbsolute(candidate))
229
+ throw new Error("State home must be absolute");
230
+ return candidate ?? join(homedir(), process.platform === "win32" ? "AppData/Local" : ".local/state");
231
+ }
232
+ function recordText(record) {
233
+ return `${JSON.stringify(record, null, 2)}
234
+ `;
235
+ }
236
+ function contentIdentity(text) {
237
+ return createHash("sha256").update(text).digest("hex");
238
+ }
239
+ function canonicalAmbientRoot(path) {
240
+ const full = resolve(path);
1269
241
  try {
1270
- accessSync(executable, constants.X_OK);
1271
- return true;
1272
- } catch {
1273
- return false;
242
+ return realpathSync(full);
243
+ } catch (error) {
244
+ if (!absent(error))
245
+ throw error;
246
+ try {
247
+ lstatSync(full);
248
+ throw new Error("Dangling ambient root");
249
+ } catch (statError) {
250
+ if (!absent(statError))
251
+ throw statError;
252
+ }
253
+ const parent = dirname(full);
254
+ if (parent === full)
255
+ throw error;
256
+ return join(canonicalAmbientRoot(parent), full.slice(parent.length));
1274
257
  }
1275
258
  }
1276
- function validEnvironmentName(name) {
1277
- return /^[A-Za-z_][A-Za-z0-9_]*$/u.test(name);
1278
- }
1279
259
 
1280
- class BoundedLog {
1281
- bytes = 0;
1282
- #head = Buffer.alloc(0);
1283
- #tail = Buffer.alloc(0);
1284
- limit;
1285
- #witness;
1286
- #witnessTail = Buffer.alloc(0);
1287
- witnessMatched = false;
1288
- constructor(limit, witness) {
1289
- this.limit = limit;
1290
- if (witness)
1291
- this.#witness = Buffer.from(witness);
1292
- }
1293
- append(chunk) {
1294
- const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1295
- this.bytes += input.byteLength;
1296
- if (this.#witness && !this.witnessMatched) {
1297
- const searchable = Buffer.concat([this.#witnessTail, input]);
1298
- this.witnessMatched = searchable.includes(this.#witness);
1299
- this.#witnessTail = this.witnessMatched ? Buffer.alloc(0) : Buffer.from(searchable.subarray(Math.max(0, searchable.length - this.#witness.length + 1)));
1300
- }
1301
- const headLimit = Math.ceil(this.limit / 2);
1302
- const take = Math.min(input.length, headLimit - this.#head.length);
1303
- if (take > 0)
1304
- this.#head = Buffer.concat([this.#head, input.subarray(0, take)]);
1305
- const rest = input.subarray(take);
1306
- const tailLimit = this.limit - headLimit;
1307
- if (rest.length && tailLimit) {
1308
- this.#tail = rest.length >= tailLimit ? Buffer.from(rest.subarray(-tailLimit)) : Buffer.concat([
1309
- this.#tail.subarray(Math.max(0, this.#tail.length + rest.length - tailLimit)),
1310
- rest
1311
- ]);
260
+ class TaskStore {
261
+ root;
262
+ tasks;
263
+ privateRoot;
264
+ constructor(root, stateHome = defaultStateHome()) {
265
+ this.root = realpathSync(root);
266
+ this.tasks = join(this.root, ".cadence", "assistant", "tasks");
267
+ this.privateRoot = join(canonicalAmbientRoot(stateHome), "cadence-assistant-v1", createHash("sha256").update(this.root).digest("hex"));
268
+ const within = relative(this.root, resolve(this.privateRoot));
269
+ if (!within || !within.startsWith(`..${sep}`) && within !== ".." && !isAbsolute(within))
270
+ throw new Error("Private state home must be outside the working copy");
271
+ }
272
+ get lockPath() {
273
+ return join(this.privateRoot, "checkout.lock");
274
+ }
275
+ inspectPrivate() {
276
+ const namespace = dirname(this.privateRoot);
277
+ for (const path of [namespace, this.privateRoot]) {
278
+ if (!safeDirectory(path))
279
+ continue;
280
+ const st = lstatSync(path);
281
+ if (process.platform !== "win32" && ((st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
282
+ throw new Error("Private state permissions refused");
1312
283
  }
1313
284
  }
1314
- text() {
1315
- return Buffer.concat([this.#head, this.#tail]).toString("utf8");
1316
- }
1317
- metadata() {
1318
- return { bytes: this.bytes, truncated: this.bytes > this.limit };
1319
- }
1320
- }
1321
-
1322
- class BubblewrapIsolationBackend {
1323
- #localTrusted;
1324
- #bwrapPath;
1325
- #probe;
1326
- #spawn;
1327
- #timeoutMs;
1328
- #maxOutputBytes;
1329
- #terminateGraceMs;
1330
- constructor(options = {}) {
1331
- this.#localTrusted = options.localTrusted === true;
1332
- this.#bwrapPath = options.bwrapPath ?? "/usr/bin/bwrap";
1333
- this.#probe = options.probe ?? defaultProbe;
1334
- this.#spawn = options.spawnProcess ?? spawn;
1335
- this.#timeoutMs = options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
1336
- this.#maxOutputBytes = options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes;
1337
- this.#terminateGraceMs = options.terminateGraceMs ?? ISOLATION_RUN_LIMITS.terminateGraceMs;
1338
- if (!Number.isSafeInteger(this.#timeoutMs) || this.#timeoutMs < 1 || !Number.isSafeInteger(this.#maxOutputBytes) || this.#maxOutputBytes < 1 || !Number.isSafeInteger(this.#terminateGraceMs) || this.#terminateGraceMs < 1) {
1339
- throw new Error("isolation-run-limits-invalid");
285
+ inspectPrivateFile(path, max) {
286
+ this.inspectPrivate();
287
+ if (!safeDirectory(this.privateRoot))
288
+ return;
289
+ try {
290
+ const st = lstatSync(path);
291
+ if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > max || process.platform !== "win32" && ((st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
292
+ throw new Error("Unsafe private state file");
293
+ } catch (error) {
294
+ if (!absent(error))
295
+ throw error;
1340
296
  }
1341
297
  }
1342
- async available() {
298
+ inspectLock() {
299
+ this.inspectPrivateFile(this.lockPath, 8192);
300
+ const data = readBounded(this.lockPath, 8192);
301
+ if (!data)
302
+ return;
303
+ const lock = parseRecord(data);
304
+ if (!object(lock) || lock.schema !== "cadence-assistant-lock/1" || lock.root !== this.root || typeof lock.token !== "string" || !validId(lock.token) || typeof lock.runId !== "string" || !validId(lock.runId) || typeof lock.host !== "string" || !Number.isInteger(lock.pid) || Number(lock.pid) < 1)
305
+ throw new Error("Unknown or corrupt coordination lock");
306
+ return lock;
307
+ }
308
+ acquire() {
309
+ this.inspectPrivate();
310
+ safeDirectory(this.privateRoot, true);
311
+ const lock = {
312
+ schema: "cadence-assistant-lock/1",
313
+ token: randomUUID(),
314
+ runId: randomUUID(),
315
+ pid: process.pid,
316
+ host: hostname(),
317
+ root: this.root
318
+ };
319
+ let fd;
1343
320
  try {
1344
- if (this.#localTrusted && process.platform !== "linux")
1345
- return false;
1346
- return Boolean(await this.#probe(this.#bwrapPath));
321
+ fd = openSync(this.lockPath, "wx", 384);
1347
322
  } catch {
1348
- return false;
323
+ throw new Error("Checkout busy or unresolved lock; inspect and explicitly recover only a dead owner");
1349
324
  }
1350
- }
1351
- async run(input) {
1352
- if (input.outputWitness !== undefined && (typeof input.outputWitness !== "string" || input.outputWitness.length < 1 || input.outputWitness.length > 512))
1353
- throw new Error("isolation-output-witness-invalid");
1354
- if (!await this.available()) {
1355
- return {
1356
- ok: false,
1357
- state: "paused",
1358
- code: "isolation-backend-unavailable"
1359
- };
1360
- }
1361
- if (input.signal?.aborted) {
1362
- return { ok: false, state: "cancelled", code: "cancelled" };
1363
- }
1364
- const root = path6.resolve(input.root);
1365
- const rootStat = lstatSync6(root, { throwIfNoEntry: false });
1366
- if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
1367
- throw new Error("isolation-root-invalid");
1368
- }
1369
- const argv = this.#localTrusted ? [
1370
- "--die-with-parent",
1371
- "--new-session",
1372
- "--unshare-pid",
1373
- "--bind",
1374
- "/",
1375
- "/",
1376
- "--proc",
1377
- "/proc",
1378
- "--dev",
1379
- "/dev",
1380
- "--chdir",
1381
- root,
1382
- "--clearenv"
1383
- ] : [
1384
- "--die-with-parent",
1385
- "--new-session",
1386
- "--unshare-all",
1387
- "--proc",
1388
- "/proc",
1389
- "--dev",
1390
- "/dev",
1391
- "--tmpfs",
1392
- "/tmp",
1393
- "--bind",
1394
- root,
1395
- "/workspace",
1396
- "--chdir",
1397
- "/workspace",
1398
- "--clearenv",
1399
- "--setenv",
1400
- "PATH",
1401
- "/usr/local/bin:/usr/bin:/bin"
1402
- ];
1403
- if (!this.#localTrusted) {
1404
- for (const systemRoot of ["/usr", "/bin", "/lib", "/lib64"]) {
1405
- if (lstatSync6(systemRoot, { throwIfNoEntry: false }))
1406
- argv.push("--ro-bind", systemRoot, systemRoot);
1407
- }
1408
- for (const mount of input.mounts ?? []) {
1409
- if (!path6.isAbsolute(mount.source) || !path6.isAbsolute(mount.target))
1410
- throw new Error("isolation-mount-invalid");
1411
- argv.push(mount.writable ? "--bind" : "--ro-bind", mount.source, mount.target);
1412
- }
1413
- }
1414
- for (const [name, value] of Object.entries(input.environment ?? {}).sort()) {
1415
- if (!validEnvironmentName(name) || value.includes("\x00"))
1416
- throw new Error("isolation-environment-invalid");
1417
- argv.push("--setenv", name, value);
325
+ try {
326
+ writeFileSync(fd, JSON.stringify(lock));
327
+ fsyncSync(fd);
328
+ } finally {
329
+ closeSync(fd);
330
+ }
331
+ return lock;
332
+ }
333
+ assertOwner(lock) {
334
+ if (JSON.stringify(this.inspectLock()) !== JSON.stringify(lock))
335
+ throw new Error("Coordination lock changed");
336
+ }
337
+ release(lock) {
338
+ if (lock.pid !== process.pid || lock.host !== hostname())
339
+ throw new Error("Only the owning process may release a lock");
340
+ this.assertOwner(lock);
341
+ unlinkSync(this.lockPath);
342
+ }
343
+ recover(expected, acknowledgeEscapedProcesses) {
344
+ if (!acknowledgeEscapedProcesses || expected.host !== hostname())
345
+ throw new Error("Recovery requires informed local-owner confirmation");
346
+ this.assertOwner(expected);
347
+ try {
348
+ process.kill(expected.pid, 0);
349
+ } catch (error) {
350
+ if (!object(error) || error.code !== "ESRCH")
351
+ throw new Error("Owner status unknown");
352
+ this.assertOwner(expected);
353
+ unlinkSync(this.lockPath);
354
+ return;
1418
355
  }
1419
- argv.push("--", input.executable, ...input.args ?? []);
1420
- return new Promise((resolve2) => {
1421
- let settled = false;
1422
- const stdout = new BoundedLog(this.#maxOutputBytes, input.outputWitness);
1423
- const stderr = new BoundedLog(this.#maxOutputBytes, input.outputWitness);
1424
- let child;
1425
- let executionTimer;
1426
- let killTimer;
1427
- let terminationResult;
1428
- const settle = (result) => {
1429
- if (settled)
1430
- return;
1431
- settled = true;
1432
- if (executionTimer)
1433
- clearTimeout(executionTimer);
1434
- input.signal?.removeEventListener("abort", abort);
1435
- resolve2(result);
1436
- };
1437
- const kill = (signal) => {
1438
- child?.kill(signal);
1439
- };
1440
- const terminate = (result) => {
1441
- if (settled || terminationResult)
1442
- return;
1443
- terminationResult = result;
1444
- if (executionTimer)
1445
- clearTimeout(executionTimer);
1446
- killTimer = setTimeout(() => kill("SIGKILL"), this.#terminateGraceMs);
1447
- killTimer.unref?.();
1448
- kill("SIGTERM");
1449
- };
1450
- const abort = () => {
1451
- terminate({ ok: false, state: "cancelled", code: "cancelled" });
1452
- };
1453
- try {
1454
- child = this.#spawn(this.#bwrapPath, argv, {
1455
- cwd: root,
1456
- shell: false,
1457
- stdio: ["ignore", "pipe", "pipe"]
1458
- });
1459
- } catch {
1460
- settle({
1461
- ok: false,
1462
- state: "paused",
1463
- code: "isolation-backend-launch-failed"
1464
- });
1465
- return;
1466
- }
1467
- const capture = (destination, chunk) => {
1468
- if (!settled && !terminationResult)
1469
- destination.append(chunk);
1470
- };
1471
- child.stdout?.on("data", (chunk) => {
1472
- capture(stdout, chunk);
1473
- });
1474
- child.stderr?.on("data", (chunk) => {
1475
- capture(stderr, chunk);
1476
- });
1477
- child.once("error", () => {
1478
- if (terminationResult)
1479
- return;
1480
- settle({
1481
- ok: false,
1482
- state: "paused",
1483
- code: "isolation-backend-launch-failed"
1484
- });
1485
- });
1486
- child.once("close", (code) => {
1487
- if (killTimer)
1488
- clearTimeout(killTimer);
1489
- settle(terminationResult ?? {
1490
- ok: true,
1491
- state: "completed",
1492
- exitCode: code ?? 1,
1493
- stdout: stdout.text(),
1494
- stderr: stderr.text(),
1495
- logs: { stdout: stdout.metadata(), stderr: stderr.metadata() },
1496
- ...input.outputWitness ? {
1497
- outputWitnessMatched: stdout.witnessMatched || stderr.witnessMatched
1498
- } : {}
1499
- });
1500
- });
1501
- input.signal?.addEventListener("abort", abort, { once: true });
1502
- if (input.signal?.aborted) {
1503
- abort();
1504
- return;
1505
- }
1506
- executionTimer = setTimeout(() => terminate({
1507
- ok: false,
1508
- state: "paused",
1509
- code: "isolation-execution-timeout"
1510
- }), this.#timeoutMs);
1511
- executionTimer.unref?.();
1512
- });
1513
- }
1514
- }
1515
-
1516
- // src/windows-job-backend.ts
1517
- var SHUTDOWN_MS = 5000;
1518
- var invalid = () => {
1519
- throw new Error("windows-job-request-invalid");
1520
- };
1521
- var text = (value) => typeof value === "string" && value.length <= 16384 && !value.includes("\x00") && !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value);
1522
- var absolute = (value) => text(value) && /^[A-Za-z]:\\/u.test(value) && path7.win32.isAbsolute(value);
1523
- function encodeWindowsJobRequest(input, timeoutMs) {
1524
- if (!absolute(input.root) || !absolute(input.executable) || !input.executable.toLowerCase().endsWith(".exe") || input.mounts?.length || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86400000)
1525
- invalid();
1526
- const args = input.args ?? [];
1527
- if (args.length > 128 || !args.every(text))
1528
- invalid();
1529
- const seen = new Set;
1530
- const env = Object.entries(input.environment ?? {});
1531
- for (const [name, value] of env) {
1532
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || !text(value) || seen.has(name.toUpperCase()))
1533
- invalid();
1534
- seen.add(name.toUpperCase());
356
+ throw new Error("Owner is live; stop it first");
1535
357
  }
1536
- const quote = (value) => `"${value.replace(/(\\*)"/gu, "$1$1\\\"").replace(/(\\+)$/gu, "$1$1")}"`;
1537
- const command = [input.executable, ...args].map(quote).join(" ");
1538
- if (command.length >= 32767)
1539
- invalid();
1540
- const environment = `${env.sort(([a], [b]) => a.toUpperCase() < b.toUpperCase() ? -1 : 1).map(([k, v]) => `${k}=${v}`).join("\x00")}\x00\x00`;
1541
- const parts = [
1542
- `${input.executable}\x00`,
1543
- `${command}\x00`,
1544
- `${input.root}\x00`,
1545
- environment
1546
- ].map((s) => Buffer.from(s, "utf16le"));
1547
- const header = Buffer.alloc(36);
1548
- [2, timeoutMs, SHUTDOWN_MS, 0, ...parts.map((p) => p.length), 0].forEach((n, i) => {
1549
- header.writeUInt32LE(n, i * 4);
1550
- });
1551
- const wire = Buffer.concat([header, ...parts]);
1552
- if (wire.length > 65536 || parts.some((p) => p.length > 60000))
1553
- invalid();
1554
- return wire;
1555
- }
1556
- function verifyWindowsJobHelper(helper) {
1557
- try {
1558
- if (!path7.isAbsolute(helper))
1559
- return false;
1560
- const files = [helper, `${helper}.json`];
1561
- if (files.some((file) => {
1562
- const s = lstatSync7(file);
1563
- return !s.isFile() || s.isSymbolicLink() || s.size > 8 * 1024 * 1024;
1564
- }))
1565
- return false;
1566
- const bytes = readFileSync4(helper);
1567
- const pe = bytes.length >= 64 ? bytes.readUInt32LE(60) : -1;
1568
- if (bytes.toString("ascii", 0, 2) !== "MZ" || pe < 64 || pe + 6 > bytes.length || bytes.readUInt32LE(pe) !== 17744 || bytes.readUInt16LE(pe + 4) !== 34404)
1569
- return false;
1570
- const hash = (data) => createHash3("sha256").update(data).digest("hex");
1571
- const manifest = JSON.parse(readFileSync4(`${helper}.json`, "utf8"));
1572
- return manifest.version === 2 && manifest.arch === "x64" && manifest.helperSha256 === hash(bytes) && manifest.sourceSha256 === hash(readFileSync4(new URL("./windows-job.c", import.meta.url)));
1573
- } catch {
1574
- return false;
358
+ load(id) {
359
+ return this.loadSnapshot(id)?.record;
1575
360
  }
1576
- }
1577
- function observation(value) {
1578
- if (!value || typeof value !== "object")
1579
- return;
1580
- const v = value;
1581
- if (Object.keys(v).sort().join(",") !== "exitCode,managedSettled,outcome,reason,rootExited,version" || v.version !== 2 || !["complete", "failed", "uncertain"].includes(v.outcome) || ![
1582
- "exit",
1583
- "timeout",
1584
- "cancelled",
1585
- "launch-failed",
1586
- "termination-unconfirmed"
1587
- ].includes(v.reason) || typeof v.rootExited !== "boolean" || typeof v.managedSettled !== "boolean" || !(v.exitCode === null || Number.isInteger(v.exitCode) && v.exitCode >= 0 && v.exitCode <= 4294967295))
1588
- return;
1589
- if (v.outcome === "complete" && (v.reason !== "exit" || !v.rootExited || !v.managedSettled || v.exitCode === null))
1590
- return;
1591
- if (v.outcome === "failed" && (!v.managedSettled || !["timeout", "cancelled", "launch-failed"].includes(v.reason)))
1592
- return;
1593
- return v;
1594
- }
1595
- var uncertain = () => ({
1596
- ok: false,
1597
- state: "paused",
1598
- code: "isolation-termination-unconfirmed"
1599
- });
1600
- function classify(v, helperExit, output, cancelled) {
1601
- if (!v?.managedSettled || v.outcome === "uncertain" || helperExit !== 0)
1602
- return uncertain();
1603
- if (cancelled || v.reason === "cancelled")
1604
- return { ok: false, state: "cancelled", code: "cancelled" };
1605
- if (v.reason === "timeout")
1606
- return { ok: false, state: "paused", code: "isolation-execution-timeout" };
1607
- if (v.reason === "launch-failed")
1608
- return {
1609
- ok: false,
1610
- state: "paused",
1611
- code: "isolation-backend-launch-failed"
1612
- };
1613
- if (v.outcome !== "complete" || v.exitCode === null)
1614
- return uncertain();
1615
- return {
1616
- ok: true,
1617
- state: "completed",
1618
- exitCode: v.exitCode,
1619
- stdout: output.text(),
1620
- stderr: "",
1621
- outputWitnessMatched: output.witnessMatched,
1622
- logs: { stdout: output.metadata(), stderr: { bytes: 0, truncated: false } }
1623
- };
1624
- }
1625
-
1626
- class WindowsJobBackend {
1627
- #options;
1628
- constructor(options) {
1629
- for (const [value, maximum] of [
1630
- [options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs, 86400000],
1631
- [
1632
- options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes,
1633
- 1024 * 1024
1634
- ]
1635
- ])
1636
- if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
1637
- throw new Error("isolation-run-limits-invalid");
1638
- this.#options = options;
1639
- }
1640
- available() {
1641
- return (this.#options.platform ?? process.platform) === "win32" && (this.#options.arch ?? process.arch) === "x64" && (this.#options.probe ?? verifyWindowsJobHelper)(this.#options.helperPath);
361
+ loadSnapshot(id) {
362
+ if (!validId(id))
363
+ throw new Error("Invalid task id");
364
+ const directory = join(this.tasks, id);
365
+ if (safeDirectory(directory) && readdirSync(directory).some((name) => name !== "task.json"))
366
+ throw new Error("Unknown task directory content; preserved without reset");
367
+ const data = readBounded(join(this.tasks, id, "task.json"), MAX_RECORD_BYTES);
368
+ if (!data)
369
+ return;
370
+ const record = decodeTask(parseRecord(data));
371
+ if (record.id !== id)
372
+ throw new Error("Task identity mismatch");
373
+ return { record, identity: contentIdentity(data) };
374
+ }
375
+ list() {
376
+ if (!safeDirectory(this.tasks))
377
+ return [];
378
+ const entries = readdirSync(this.tasks);
379
+ if (entries.length > 1000)
380
+ throw new Error("Task listing limit exceeded");
381
+ return entries.map((id) => {
382
+ const task = this.load(id);
383
+ if (!task)
384
+ throw new Error("Unknown task directory");
385
+ return task;
386
+ });
1642
387
  }
1643
- async run(input) {
1644
- if (!this.available())
1645
- return {
1646
- ok: false,
1647
- state: "paused",
1648
- code: "isolation-backend-unavailable"
1649
- };
1650
- if (input.signal?.aborted)
1651
- return { ok: false, state: "cancelled", code: "cancelled" };
1652
- if (input.outputWitness !== undefined && (!text(input.outputWitness) || input.outputWitness.length < 1 || input.outputWitness.length > 512))
1653
- return {
1654
- ok: false,
1655
- state: "paused",
1656
- code: "isolation-backend-launch-failed"
1657
- };
1658
- const timeoutMs = this.#options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
1659
- let wire;
388
+ save(record, expectedRevision, held, expectedIdentity) {
389
+ const next = decodeTask(record);
390
+ if (next.revision !== expectedRevision + 1)
391
+ throw new Error("Revision conflict");
392
+ const text = recordText(next);
393
+ if (Buffer.byteLength(text) > MAX_RECORD_BYTES)
394
+ throw new Error("Task record limit");
395
+ const lock = held ?? this.acquire();
396
+ if (lock.pid !== process.pid || lock.host !== hostname())
397
+ throw new Error("Not the lock owner");
1660
398
  try {
1661
- wire = encodeWindowsJobRequest(input, timeoutMs);
1662
- } catch {
1663
- return {
1664
- ok: false,
1665
- state: "paused",
1666
- code: "isolation-backend-launch-failed"
1667
- };
1668
- }
1669
- const output = new BoundedLog(this.#options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes, input.outputWitness);
1670
- return new Promise((resolve2) => {
1671
- let child;
1672
- let line = "", final, bad = false, closed = false, launchFailed = false;
1673
- let timer;
1674
- let stopping = false;
1675
- const armDeadline = (duration) => {
1676
- clearTimeout(timer);
1677
- timer = setTimeout(() => {
1678
- bad = true;
1679
- child.kill();
1680
- finish(uncertain());
1681
- }, duration);
1682
- timer.unref?.();
1683
- };
1684
- const cancel = () => {
1685
- if (stopping || closed)
1686
- return;
1687
- stopping = true;
1688
- child.stdin?.end();
1689
- armDeadline(SHUTDOWN_MS + 5000);
1690
- };
1691
- const finish = (result) => {
1692
- if (closed)
1693
- return;
1694
- closed = true;
1695
- clearTimeout(timer);
1696
- input.signal?.removeEventListener("abort", cancel);
1697
- resolve2(result);
1698
- };
399
+ this.assertOwner(lock);
400
+ const current = this.loadSnapshot(next.id);
401
+ if ((current?.record.revision ?? 0) !== expectedRevision)
402
+ throw new Error("Revision conflict");
403
+ if (current?.identity !== expectedIdentity)
404
+ throw new Error("Task content changed; reopen it");
405
+ const dir = join(this.tasks, next.id);
406
+ safeDirectory(dir, true);
407
+ if (!current && readdirSync(dir).length)
408
+ throw new Error("Unknown task directory");
409
+ const temporary = join(dir, `task.${randomUUID()}.tmp`);
410
+ const fd = openSync(temporary, "wx", 384);
1699
411
  try {
1700
- child = (this.#options.spawnProcess ?? spawn2)(this.#options.helperPath, [], {
1701
- cwd: input.root,
1702
- shell: false,
1703
- windowsHide: true,
1704
- stdio: ["pipe", "pipe", "pipe"]
1705
- });
1706
- } catch {
1707
- finish({
1708
- ok: false,
1709
- state: "paused",
1710
- code: "isolation-backend-launch-failed"
1711
- });
1712
- return;
412
+ writeFileSync(fd, text);
413
+ fsyncSync(fd);
414
+ } finally {
415
+ closeSync(fd);
1713
416
  }
1714
- const malformed = () => {
1715
- bad = true;
1716
- cancel();
1717
- };
1718
- child.stdout?.on("data", (chunk) => {
1719
- if (closed || bad)
1720
- return;
1721
- line += chunk.toString("utf8");
1722
- while (line.includes(`
1723
- `)) {
1724
- const end = line.indexOf(`
1725
- `);
1726
- const row = line.slice(0, end);
1727
- line = line.slice(end + 1);
1728
- if (row.length > 4096 || final) {
1729
- malformed();
1730
- return;
1731
- }
1732
- try {
1733
- const value = JSON.parse(row);
1734
- if (Object.keys(value).length === 1 && typeof value.outputHex === "string" && /^(?:[a-f0-9]{2}){1,1024}$/u.test(value.outputHex))
1735
- output.append(Buffer.from(value.outputHex, "hex"));
1736
- else {
1737
- final = observation(value);
1738
- if (!final) {
1739
- malformed();
1740
- return;
1741
- }
1742
- }
1743
- } catch {
1744
- malformed();
1745
- return;
1746
- }
1747
- }
1748
- if (line.length > 4096)
1749
- malformed();
1750
- });
1751
- child.stderr?.on("data", () => {});
1752
- child.stdin?.on("error", () => {
1753
- bad = true;
1754
- });
1755
- child.once("error", () => {
1756
- bad = true;
1757
- launchFailed = child.pid === undefined;
1758
- });
1759
- child.once("close", (code) => finish(launchFailed ? {
1760
- ok: false,
1761
- state: "paused",
1762
- code: "isolation-backend-launch-failed"
1763
- } : bad || line.length ? uncertain() : classify(final, code, output, input.signal?.aborted === true)));
1764
- armDeadline(timeoutMs + SHUTDOWN_MS + 1e4);
1765
- input.signal?.addEventListener("abort", cancel, { once: true });
1766
- child.stdin?.write(wire);
1767
- if (input.signal?.aborted)
1768
- cancel();
417
+ this.assertOwner(lock);
418
+ const latest = readBounded(join(dir, "task.json"), MAX_RECORD_BYTES);
419
+ if ((latest ? contentIdentity(latest) : undefined) !== expectedIdentity)
420
+ throw new Error("Task content changed; replacement preserved without overwrite");
421
+ renameSync(temporary, join(dir, "task.json"));
422
+ return next;
423
+ } finally {
424
+ if (!held)
425
+ this.release(lock);
426
+ }
427
+ }
428
+ checks() {
429
+ const data = readBounded(join(this.root, ".cadence", "assistant", "config.json"), 32768);
430
+ if (!data)
431
+ return [];
432
+ const value = parseRecord(data);
433
+ if (!object(value) || value.schema !== TASK_CONFIG_SCHEMA || !Array.isArray(value.checks) || value.checks.length > 32 || Object.keys(value).some((key) => !["schema", "checks"].includes(key)))
434
+ throw new Error("Unknown check configuration");
435
+ const checks = value.checks.map(decodeCheck);
436
+ if (new Set(checks.map((check) => check.name)).size !== checks.length || checks.some((check) => check.name === "Manual command"))
437
+ throw new Error("Duplicate or reserved check name");
438
+ return checks;
439
+ }
440
+ readLog(name) {
441
+ if (!/^[a-f0-9-]{36}\.log$/.test(name))
442
+ throw new Error("Invalid diagnostic name");
443
+ this.inspectPrivateFile(join(this.privateRoot, name), 65536);
444
+ const data = readBounded(join(this.privateRoot, name), 65536);
445
+ return data ? diagnosticText(data.toString("utf8"), 65536) : "Private diagnostic unavailable";
446
+ }
447
+ writeLog(lock, output) {
448
+ if (lock.pid !== process.pid || lock.host !== hostname())
449
+ throw new Error("Not the lock owner");
450
+ this.assertOwner(lock);
451
+ const name = `${randomUUID()}.log`;
452
+ writeFileSync(join(this.privateRoot, name), diagnosticText(output, 65536), {
453
+ flag: "wx",
454
+ mode: 384
1769
455
  });
456
+ return name;
1770
457
  }
1771
458
  }
1772
- function probeWindowsJob(helper) {
1773
- if (process.platform !== "win32" || process.arch !== "x64" || !verifyWindowsJobHelper(helper))
1774
- return false;
1775
- const wire = encodeWindowsJobRequest({
1776
- root: process.cwd(),
1777
- executable: process.execPath,
1778
- args: ["-e", "process.exit(0)"],
1779
- environment: { SystemRoot: process.env.SystemRoot ?? "C:\\Windows" }
1780
- }, 5000);
1781
- const result = spawnSync(helper, ["--probe"], {
1782
- input: wire,
1783
- shell: false,
1784
- encoding: "utf8",
1785
- timeout: 15000,
1786
- maxBuffer: 65536
1787
- });
1788
- try {
1789
- const v = observation(JSON.parse(result.stdout.trim()));
1790
- return !result.error && result.status === 0 && v?.outcome === "complete" && v.exitCode === 0;
1791
- } catch {
1792
- return false;
1793
- }
1794
- }
459
+ var TASK_CONFIG_SCHEMA = "cadence-assistant-checks/1";
1795
460
 
1796
461
  // src/operator-tools.ts
1797
- var PACKAGE_LOCKS = {
1798
- bun: ["bun.lock", "bun.lockb"],
1799
- npm: ["package-lock.json", "npm-shrinkwrap.json"],
1800
- pnpm: ["pnpm-lock.yaml"],
1801
- yarn: ["yarn.lock"]
1802
- };
1803
- function packageManagerSelection(root, declaration) {
1804
- const managers = Object.keys(PACKAGE_LOCKS);
1805
- const lockfiles = managers.flatMap((manager) => PACKAGE_LOCKS[manager].filter((file) => lstatSync8(path8.join(root, file), { throwIfNoEntry: false })?.isFile()));
1806
- const inferred = managers.filter((manager) => PACKAGE_LOCKS[manager].some((file) => lockfiles.includes(file)));
1807
- const declared = typeof declaration === "string" ? managers.find((manager) => new RegExp(`^${manager}@[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][a-z0-9._+-]+)?$`, "iu").test(declaration)) : undefined;
1808
- const selectedPackageManager = declaration !== undefined ? declared ?? null : inferred.length === 1 ? inferred[0] ?? null : inferred.length > 1 ? null : "npm";
1809
- const selectionSource = declaration !== undefined ? declared ? "package.json" : "invalid-declaration" : inferred.length === 1 ? "lockfile" : inferred.length > 1 ? "ambiguous" : "default";
462
+ function inspectConsumer(cwd) {
463
+ const workspace = resolveWorkspace(cwd);
464
+ const store = new TaskStore(workspace.root);
465
+ const tasks = store.list();
466
+ const checks = store.checks();
467
+ const lock = store.inspectLock();
1810
468
  return {
1811
- selectedPackageManager,
1812
- selectionSource,
1813
- lockfiles,
1814
- ambiguousLockfiles: inferred.length > 1
1815
- };
1816
- }
1817
- function inspectConsumer(root) {
1818
- root = realpathSync4(root);
1819
- const checks = [];
1820
- const check = (name, action, nextStep) => {
1821
- try {
1822
- checks.push({ name, ok: true, detail: action() });
1823
- } catch (error) {
1824
- checks.push({
1825
- name,
1826
- ok: false,
1827
- detail: error instanceof Error ? error.message : "unavailable",
1828
- nextStep
1829
- });
1830
- }
469
+ ok: true,
470
+ schema: "cadence-assistant-doctor/1",
471
+ root: workspace.root,
472
+ cwd: workspace.cwd,
473
+ taskCount: tasks.length,
474
+ checks: checks.map(({ name, timeoutMs }) => ({
475
+ name: diagnosticText(name),
476
+ timeoutMs
477
+ })),
478
+ checkStatus: "not-run",
479
+ coordination: lock ? "locked; inspect in the terminal workflow" : "unlocked",
480
+ note: "Observation only. Missing state stays absent. Old history is not imported. External and older writers are not contained."
1831
481
  };
1832
- check("node", () => {
1833
- if (Number(process.versions.node.split(".")[0]) < 22 || Number(process.versions.node.split(".")[0]) === 22 && Number(process.versions.node.split(".")[1]) < 13)
1834
- throw new Error("Node 22.13+ required");
1835
- return process.versions.node;
1836
- }, "Install Node >=22.13.");
1837
- check("git", () => {
1838
- const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
1839
- cwd: root,
1840
- encoding: "utf8",
1841
- timeout: 5000,
1842
- maxBuffer: 65536
1843
- });
1844
- if (result.status !== 0)
1845
- throw new Error("git-repository-unavailable");
1846
- return result.stdout.trim();
1847
- }, "Install Git and initialize the consumer repository.");
1848
- check("openspec", () => {
1849
- resolveOpenSpecInvocation(root);
1850
- return "resolved (protocol not executed)";
1851
- }, "Install @fission-ai/openspec@1.5.0, or configure ABEL_OPENSPEC_PACKAGE_ROOT / ABEL_OPENSPEC_NODE before launching the host.");
1852
- check("execution", () => {
1853
- const profile = executionProfile();
1854
- if (profile.mode === "host-trusted") {
1855
- if (!probeWindowsJob(profile.windowsHelperPath ?? ""))
1856
- throw new Error("windows-job-backend-unavailable");
1857
- return "host-trusted: Windows x64 Job probe passed; host files/network accessible";
1858
- }
1859
- if (process.platform !== "linux")
1860
- throw new Error("linux-pid-isolation-required");
1861
- const result = spawnSync2(profile.bwrapPath, [
1862
- "--ro-bind",
1863
- "/",
1864
- "/",
1865
- profile.mode === "local-trusted" ? "--unshare-pid" : "--unshare-all",
1866
- "--",
1867
- "/bin/true"
1868
- ], { timeout: 5000, encoding: "utf8", maxBuffer: 65536 });
1869
- if (result.status !== 0)
1870
- throw new Error("bubblewrap-probe-failed");
1871
- return `${profile.mode}: Bubblewrap PID namespace probe passed`;
1872
- }, "On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows x64 trusted projects may explicitly select ABEL_EXECUTION_MODE=host-trusted and install the matching native Job helper (ABEL_WINDOWS_JOB_HELPER).");
1873
- check("request-budgets", () => JSON.stringify(requestBounds()), "Set ABEL_FIRST_PROGRESS_MS and ABEL_STREAM_IDLE_MS to positive milliseconds <=1200000.");
1874
- check("state-root", () => resolveStateRoot({
1875
- consumerRoot: root,
1876
- xdgStateHome: process.env.XDG_STATE_HOME
1877
- }).rootDir, "Use an absolute repository-external XDG_STATE_HOME.");
1878
- check("workspace-dependencies", () => {
1879
- const dependencies = path8.join(root, "node_modules");
1880
- if (!lstatSync8(dependencies, { throwIfNoEntry: false }))
1881
- return "no node_modules; prepare dependencies before verification";
1882
- const names = readdirSync4(dependencies).flatMap((name) => name.startsWith("@") && lstatSync8(path8.join(dependencies, name)).isDirectory() ? readdirSync4(path8.join(dependencies, name)).map((child) => `${name}/${child}`) : [name]);
1883
- if (names.length > 20000)
1884
- throw new Error("dependency-view-too-large");
1885
- let links = 0;
1886
- for (const name of names) {
1887
- const file = path8.join(dependencies, name);
1888
- if (!lstatSync8(file).isSymbolicLink())
1889
- continue;
1890
- const resolved = realpathSync4(file);
1891
- const relative = path8.relative(root, resolved);
1892
- if (relative === ".." || relative.startsWith(`..${path8.sep}`) || path8.isAbsolute(relative))
1893
- throw new Error("dependency-link-outside-consumer");
1894
- if (!resolved.startsWith(`${dependencies}${path8.sep}`))
1895
- links++;
1896
- }
1897
- return `${links} repository workspace link(s); their targets must be included in the candidate baseline`;
1898
- }, "Repair dangling/external workspace links and ensure local package sources are tracked or admitted in the candidate.");
1899
- const manifest = path8.join(root, "package.json");
1900
- let selection = packageManagerSelection(root, undefined);
1901
- if (lstatSync8(manifest, { throwIfNoEntry: false })?.isFile()) {
1902
- check("manifest", () => {
1903
- const parsed2 = JSON.parse(readFileSync5(manifest, "utf8"));
1904
- if (!parsed2 || typeof parsed2 !== "object")
1905
- throw new Error("manifest-invalid");
1906
- return Object.keys(parsed2.scripts ?? {}).join(", ") || "no package scripts";
1907
- }, "Repair package.json.");
1908
- const parsed = (() => {
1909
- try {
1910
- const value = JSON.parse(readFileSync5(manifest, "utf8"));
1911
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
1912
- } catch {
1913
- return {};
1914
- }
1915
- })();
1916
- selection = packageManagerSelection(root, parsed.packageManager);
1917
- check("package-manager", () => {
1918
- if (!selection.selectedPackageManager)
1919
- throw new Error(`package-manager-${selection.selectionSource}`);
1920
- return `${selection.selectedPackageManager} (${selection.selectionSource})`;
1921
- }, "Declare the intended packageManager in package.json; doctor does not read private plans, install runners or remove lockfiles.");
1922
- for (const script of ["test", "check", "build"].filter((name) => typeof parsed.scripts?.[name] === "string")) {
1923
- check(`script:${script}`, () => {
1924
- const manager = selection.selectedPackageManager;
1925
- if (!manager)
1926
- throw new Error(`package-manager-${selection.selectionSource}`);
1927
- const result = validateVerificationAdapterCapability(root, {
1928
- kind: "package-script",
1929
- id: `doctor-${script}`,
1930
- packageManager: manager,
1931
- script,
1932
- command: parsed.scripts[script],
1933
- args: [],
1934
- classification: "expected-green"
1935
- });
1936
- if (!result.ok)
1937
- throw new Error(`${manager}:${result.diagnostic.code}`);
1938
- return "statically admitted; tests not executed";
1939
- }, "Check the runner, installed dependencies and execution configuration; this probe does not execute your tests.");
1940
- }
1941
- }
1942
- return { root, ...selection, ok: checks.every((entry) => entry.ok), checks };
1943
482
  }
1944
- function storageUsage(root, maximum = 1e5) {
1945
- let entries = 0;
1946
- let bytes = 0;
1947
- let truncated = false;
1948
- const visit = (file) => {
1949
- if (entries >= maximum) {
1950
- truncated = true;
1951
- return;
1952
- }
1953
- const stat = lstatSync8(file, { throwIfNoEntry: false });
1954
- if (!stat)
1955
- return;
1956
- entries++;
1957
- if (stat.isFile())
1958
- bytes += stat.size;
1959
- else if (stat.isDirectory())
1960
- for (const name of readdirSync4(file)) {
1961
- visit(path8.join(file, name));
1962
- if (truncated)
1963
- break;
1964
- }
483
+ function inspectRuns(cwd) {
484
+ const workspace = resolveWorkspace(cwd);
485
+ const store = new TaskStore(workspace.root);
486
+ return {
487
+ ok: true,
488
+ schema: "cadence-assistant-history/1",
489
+ root: workspace.root,
490
+ tasks: store.list().map((task) => ({
491
+ id: task.id,
492
+ kind: task.kind,
493
+ phase: task.phase === "running" ? "interrupted" : task.phase,
494
+ goal: diagnosticText(task.goal, 512),
495
+ revision: task.revision,
496
+ checks: task.checks.map(({ name, status, exitCode, durationMs }) => ({
497
+ name: diagnosticText(name, 128),
498
+ status,
499
+ exitCode,
500
+ durationMs
501
+ })),
502
+ review: task.review?.outcome ?? "not-reviewed"
503
+ })),
504
+ note: "Context only; no work resumed. Stored check outcomes are historical, not a current freshness verification. Legacy stores remain untouched."
1965
505
  };
1966
- visit(root);
1967
- return { entries, bytes, truncated };
1968
- }
1969
- function inspectRuns(root) {
1970
- const state = resolveStateRoot({
1971
- consumerRoot: root,
1972
- xdgStateHome: process.env.XDG_STATE_HOME
1973
- });
1974
- const usage = storageUsage(state.rootDir);
1975
- if (!lstatSync8(state.databasePath, { throwIfNoEntry: false }))
1976
- return { stateRoot: state.rootDir, usage, runs: [], truncated: false };
1977
- const db = new DatabaseSync(state.databasePath, { readOnly: true });
1978
- try {
1979
- const rows = db.prepare("SELECT run_id, stage, change_name, state, sequence FROM runs ORDER BY run_id LIMIT 1001").all();
1980
- return {
1981
- stateRoot: state.rootDir,
1982
- usage,
1983
- runs: rows.slice(0, 1000),
1984
- truncated: rows.length > 1000
1985
- };
1986
- } finally {
1987
- db.close();
1988
- }
1989
506
  }
1990
507
 
1991
508
  // src/operator-cli-entry.mjs
1992
- var [command = "doctor", root = process.cwd()] = process.argv.slice(2);
1993
509
  try {
1994
- if (!["doctor", "runs"].includes(command))
1995
- throw new Error("Usage: node --experimental-strip-types <cadence>/src/operator-cli.mjs doctor|runs [consumer-root]");
1996
- const result = command === "doctor" ? inspectConsumer(path9.resolve(root)) : inspectRuns(path9.resolve(root));
510
+ const [command = "doctor", root = process.cwd(), ...extra] = process.argv.slice(2);
511
+ if (!["doctor", "runs"].includes(command) || extra.length)
512
+ throw new Error("usage");
513
+ const result = command === "doctor" ? inspectConsumer(path.resolve(root)) : inspectRuns(path.resolve(root));
1997
514
  console.log(JSON.stringify(result, null, 2));
1998
- if (command === "doctor" && !result.ok)
1999
- process.exitCode = 1;
2000
- } catch (error) {
515
+ } catch {
2001
516
  console.error(JSON.stringify({
2002
517
  ok: false,
2003
- error: error instanceof Error ? error.message : "operator-command-failed"
518
+ error: "operator-refused",
519
+ usage: "node <cadence>/src/operator-cli.mjs doctor|runs [workspace]"
2004
520
  }));
2005
521
  process.exitCode = 1;
2006
522
  }