opencode-swarm 7.107.5 → 7.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/skills/codebase-review-swarm/SKILL.md +15 -0
- package/.opencode/skills/codebase-review-swarm/references/review-protocol-v8.2.md +15 -0
- package/.opencode/skills/engineering-conventions/SKILL.md +1 -1
- package/.opencode/skills/execute/SKILL.md +2 -0
- package/.opencode/skills/phase-wrap/SKILL.md +6 -1
- package/.opencode/skills/swarm-implement/SKILL.md +161 -0
- package/.opencode/skills/swarm-pr-feedback/SKILL.md +5 -2
- package/.opencode/skills/swarm-pr-review/SKILL.md +61 -0
- package/.opencode/skills/writing-tests/SKILL.md +7 -0
- package/dist/cli/{capability-probe-wsjzcp48.js → capability-probe-sh9bwh39.js} +2 -1
- package/dist/cli/{config-doctor-kdq3vae4.js → config-doctor-q354tb2w.js} +2 -2
- package/dist/cli/core-vev13ej2.js +34 -0
- package/dist/cli/{curator-drift-ed7p5mfy.js → curator-drift-fdfgj9qf.js} +3 -3
- package/dist/cli/{curator-k9swkynp.js → curator-js92gc8a.js} +24 -20
- package/dist/cli/curator-llm-factory-n70bxf40.js +40 -0
- package/dist/cli/{dispatch-anmxxhen.js → dispatch-zr1xja4t.js} +2 -2
- package/dist/cli/{evidence-summary-service-6zsgw7x7.js → evidence-summary-service-bjhr75v0.js} +9 -7
- package/dist/cli/{gate-evidence-pgr7d942.js → gate-evidence-v6ds8rcp.js} +5 -4
- package/dist/cli/guardrail-explain-sn2a4pb7.js +41 -0
- package/dist/cli/{guardrail-log-fjypsmek.js → guardrail-log-8mqqsssq.js} +4 -4
- package/dist/cli/hive-promoter-3hh8ph4a.js +48 -0
- package/dist/cli/{index-9b7qp18e.js → index-1cn967sa.js} +2 -2
- package/dist/cli/{index-vzq4h90s.js → index-1emhz3zb.js} +1380 -2013
- package/dist/cli/{index-n1sptn1w.js → index-2nt4mq9n.js} +1 -1
- package/dist/cli/{index-6h82c2h0.js → index-48mc4d48.js} +1056 -192
- package/dist/cli/{index-bq8kd9r9.js → index-5gnp8fyw.js} +3 -3
- package/dist/cli/index-5mkc2f9z.js +154 -0
- package/dist/cli/{index-0kxc7t16.js → index-6jng9c8t.js} +1 -1
- package/dist/cli/{index-jrka8jd4.js → index-7jyndvsy.js} +2 -2
- package/dist/cli/{index-b9v501fr.js → index-7xgqky26.js} +33 -5
- package/dist/cli/{index-4r5z8sgw.js → index-85bacexr.js} +2 -2
- package/dist/cli/{index-bpgqbwmf.js → index-9qxntjtx.js} +6 -6
- package/dist/cli/index-f1qyj61w.js +418 -0
- package/dist/cli/{index-wvxhynxq.js → index-fm7xz1ne.js} +1 -1
- package/dist/cli/{index-dfdg4ws5.js → index-fz1jgbvw.js} +6 -6
- package/dist/cli/{index-pchpahdf.js → index-h4c4rrwx.js} +20 -3
- package/dist/cli/{index-kxm3bdjh.js → index-mmny81g1.js} +6 -6
- package/dist/cli/{index-v4fcn4tr.js → index-q1exe2b3.js} +2 -198
- package/dist/cli/{index-57ymmh7h.js → index-r3j3458j.js} +4 -4
- package/dist/cli/{index-3naa2ajc.js → index-vvk2g744.js} +10 -156
- package/dist/cli/{index-zej8cy0j.js → index-vx76tcxe.js} +1 -1
- package/dist/cli/{index-xmarctsf.js → index-wg0665ft.js} +7 -7
- package/dist/cli/{index-4vsgp5a8.js → index-wj4jeavn.js} +1 -1
- package/dist/cli/index-wvrj16f0.js +206 -0
- package/dist/cli/{index-w1tvqh4x.js → index-x06d2qqm.js} +28 -24
- package/dist/cli/index-x2q0gnt8.js +254 -0
- package/dist/cli/{index-xg18az81.js → index-yw8qpf51.js} +1 -1
- package/dist/cli/index.js +24 -20
- package/dist/cli/{knowledge-escalator-3sgwrgy9.js → knowledge-escalator-6v7sjprx.js} +8 -7
- package/dist/cli/{knowledge-events-kfc6xmkw.js → knowledge-events-6n3x3he2.js} +6 -5
- package/dist/cli/{knowledge-store-kyacem71.js → knowledge-store-exkw6yb1.js} +6 -5
- package/dist/cli/{knowledge-validator-ez3ws8ef.js → knowledge-validator-exymfcb9.js} +7 -6
- package/dist/cli/{pending-delegations-vfnpm8zt.js → pending-delegations-yyz3fcfn.js} +5 -4
- package/dist/cli/{pr-subscriptions-bpvazmsw.js → pr-subscriptions-vv8byxe6.js} +6 -5
- package/dist/cli/{schema-x0ekqzvs.js → schema-x17zmd03.js} +1 -1
- package/dist/cli/scope-persistence-6m5nsp1r.js +20 -0
- package/dist/cli/{skill-generator-x83re96d.js → skill-generator-mx9br4ty.js} +9 -8
- package/dist/commands/_shared/url-security.d.ts +13 -3
- package/dist/commands/close.d.ts +11 -0
- package/dist/commands/lanes.d.ts +35 -0
- package/dist/commands/registry.d.ts +7 -0
- package/dist/config/bundled-skills.d.ts +1 -1
- package/dist/config/schema.d.ts +102 -0
- package/dist/config/skill-mirrors.d.ts +1 -1
- package/dist/git/branch.d.ts +53 -7
- package/dist/git/index.d.ts +5 -1
- package/dist/git/pr.d.ts +35 -2
- package/dist/hooks/delegation-gate/worktree-isolation.d.ts +118 -2
- package/dist/hooks/delegation-gate/worktree-merge-status.d.ts +17 -0
- package/dist/hooks/init-orphan-recovery-advisory.d.ts +58 -0
- package/dist/hooks/init-orphan-recovery.d.ts +65 -0
- package/dist/index.js +427 -416
- package/dist/sandbox/executor.d.ts +11 -1
- package/dist/sandbox/linux/bubblewrap-executor.d.ts +3 -1
- package/dist/sandbox/macos/sandbox-exec-executor.d.ts +3 -1
- package/dist/sandbox/win32/native-sandbox-executor.d.ts +1 -1
- package/dist/sandbox/win32/restricted-environment-executor.d.ts +4 -1
- package/dist/turbo/lean/runner.d.ts +11 -20
- package/dist/turbo/lean/worktree.d.ts +9 -3
- package/dist/utils/bun-compat.d.ts +30 -0
- package/dist/worktree/core.d.ts +55 -0
- package/dist/worktree/merge.d.ts +5 -1
- package/dist/worktree/types.d.ts +38 -0
- package/package.json +2 -1
- package/dist/cli/curator-llm-factory-ed67kywr.js +0 -36
- package/dist/cli/guardrail-explain-vd6da30t.js +0 -37
- package/dist/cli/hive-promoter-gh0nva5c.js +0 -44
|
@@ -3,15 +3,16 @@ import {
|
|
|
3
3
|
readEffectiveSpecSync
|
|
4
4
|
} from "./index-91j1sqzm.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
_internals,
|
|
7
|
+
buildDefaultPolicy,
|
|
8
|
+
execute,
|
|
9
|
+
init_runner_client,
|
|
10
|
+
probe
|
|
11
|
+
} from "./index-q1exe2b3.js";
|
|
11
12
|
import {
|
|
12
13
|
readSwarmFileAsync,
|
|
13
14
|
validateSwarmPath
|
|
14
|
-
} from "./index-
|
|
15
|
+
} from "./index-r3j3458j.js";
|
|
15
16
|
import {
|
|
16
17
|
readCachedParsedFile
|
|
17
18
|
} from "./index-jtqkh8jf.js";
|
|
@@ -21,20 +22,825 @@ import {
|
|
|
21
22
|
init_logger,
|
|
22
23
|
warn
|
|
23
24
|
} from "./index-zgwm4ryv.js";
|
|
25
|
+
import {
|
|
26
|
+
isValidTaskId,
|
|
27
|
+
readTaskEvidence,
|
|
28
|
+
readTaskEvidenceRaw,
|
|
29
|
+
sanitizeTaskId
|
|
30
|
+
} from "./index-6jng9c8t.js";
|
|
24
31
|
import {
|
|
25
32
|
ZodError,
|
|
26
33
|
exports_external
|
|
27
34
|
} from "./index-293f68mj.js";
|
|
28
35
|
import {
|
|
29
36
|
withEvidenceLock
|
|
30
|
-
} from "./index-
|
|
31
|
-
import {
|
|
32
|
-
bunHash,
|
|
33
|
-
bunWrite
|
|
34
|
-
} from "./index-b9v501fr.js";
|
|
37
|
+
} from "./index-5mkc2f9z.js";
|
|
35
38
|
import {
|
|
36
39
|
emit
|
|
37
40
|
} from "./index-vn263hqt.js";
|
|
41
|
+
import {
|
|
42
|
+
bunHash,
|
|
43
|
+
bunWrite,
|
|
44
|
+
mergeEnvForChild
|
|
45
|
+
} from "./index-7xgqky26.js";
|
|
46
|
+
import {
|
|
47
|
+
__esm,
|
|
48
|
+
__export,
|
|
49
|
+
__require,
|
|
50
|
+
__toCommonJS
|
|
51
|
+
} from "./index-a76rekgs.js";
|
|
52
|
+
|
|
53
|
+
// src/sandbox/linux/bubblewrap-executor.ts
|
|
54
|
+
import { spawnSync } from "child_process";
|
|
55
|
+
function probeBwrap() {
|
|
56
|
+
try {
|
|
57
|
+
const result = spawnSync("bwrap", ["--version"], {
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
encoding: "utf-8",
|
|
60
|
+
timeout: 5000,
|
|
61
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
62
|
+
});
|
|
63
|
+
if (result.error) {
|
|
64
|
+
const code = result.error.code;
|
|
65
|
+
if (code && BWRAP_UNAVAILABLE_CODES.has(code)) {
|
|
66
|
+
warn(`Sandbox disabled: bwrap error (${code}). Falling through to tool-layer enforcement.`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
warn(`Sandbox disabled: bwrap spawn error (${result.error.message}). Falling through to tool-layer enforcement.`);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return result.status === BWRAP_VERSION_EXIT && result.stdout.trim().length > 0;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
warn(`Sandbox disabled: probe threw (${message}). Falling through to tool-layer enforcement.`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function shellEscape(s) {
|
|
80
|
+
return s.replace(/'/g, "'\\''");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class BubblewrapSandboxExecutor {
|
|
84
|
+
mechanism = "Bubblewrap";
|
|
85
|
+
_scopePaths;
|
|
86
|
+
_tempDir;
|
|
87
|
+
_available;
|
|
88
|
+
_disabledReason;
|
|
89
|
+
constructor(scopePaths = [], tempDir) {
|
|
90
|
+
this._scopePaths = scopePaths;
|
|
91
|
+
this._tempDir = tempDir;
|
|
92
|
+
this._available = false;
|
|
93
|
+
this._disabledReason = null;
|
|
94
|
+
try {
|
|
95
|
+
if (!_internals2.probeBwrap()) {
|
|
96
|
+
this._disabledReason = "bwrap not available or not functional";
|
|
97
|
+
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
98
|
+
} else {
|
|
99
|
+
this._available = true;
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
103
|
+
this._disabledReason = `constructor threw: ${message}`;
|
|
104
|
+
this._available = false;
|
|
105
|
+
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
isAvailable() {
|
|
109
|
+
return this._available;
|
|
110
|
+
}
|
|
111
|
+
disable(reason) {
|
|
112
|
+
this._available = false;
|
|
113
|
+
this._disabledReason = reason;
|
|
114
|
+
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
115
|
+
}
|
|
116
|
+
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
117
|
+
if (!this._available) {
|
|
118
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
119
|
+
}
|
|
120
|
+
const temp = tempDir ?? this._tempDir ?? "/tmp";
|
|
121
|
+
const allScopes = [...this._scopePaths, ...scopePaths];
|
|
122
|
+
const bindArgs = allScopes.flatMap((p) => [
|
|
123
|
+
"--bind",
|
|
124
|
+
`'${shellEscape(p)}'`,
|
|
125
|
+
`'${shellEscape(p)}'`
|
|
126
|
+
]);
|
|
127
|
+
const envArgs = [];
|
|
128
|
+
if (envOverrides) {
|
|
129
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
130
|
+
if (!isValidEnvKey(key)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (value === null) {
|
|
134
|
+
envArgs.push("--unsetenv", key);
|
|
135
|
+
} else {
|
|
136
|
+
envArgs.push("--setenv", key, value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const args = [
|
|
141
|
+
"--unshare-user",
|
|
142
|
+
"--unshare-net",
|
|
143
|
+
"--unshare-ipc",
|
|
144
|
+
"--die-with-parent",
|
|
145
|
+
"--new-session",
|
|
146
|
+
"--cap-drop",
|
|
147
|
+
"ALL",
|
|
148
|
+
...bindArgs,
|
|
149
|
+
"--dev",
|
|
150
|
+
"/dev",
|
|
151
|
+
"--size",
|
|
152
|
+
"500M",
|
|
153
|
+
"--tmpfs",
|
|
154
|
+
`'${shellEscape(temp)}'`,
|
|
155
|
+
"--ro-bind",
|
|
156
|
+
"/etc",
|
|
157
|
+
"/etc",
|
|
158
|
+
"--ro-bind",
|
|
159
|
+
"/usr",
|
|
160
|
+
"/usr",
|
|
161
|
+
"--ro-bind",
|
|
162
|
+
"/lib",
|
|
163
|
+
"/lib",
|
|
164
|
+
"--ro-bind",
|
|
165
|
+
"/lib64",
|
|
166
|
+
"/lib64",
|
|
167
|
+
"--proc",
|
|
168
|
+
"/proc",
|
|
169
|
+
"--unshare-pid",
|
|
170
|
+
...envArgs,
|
|
171
|
+
"--",
|
|
172
|
+
"bash",
|
|
173
|
+
"-c",
|
|
174
|
+
`'${shellEscape(command)}'`
|
|
175
|
+
];
|
|
176
|
+
return `bwrap ${args.join(" ")}`;
|
|
177
|
+
}
|
|
178
|
+
getEnvOverrides() {
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
var BWRAP_VERSION_EXIT = 0, BWRAP_UNAVAILABLE_CODES, _internals2;
|
|
183
|
+
var init_bubblewrap_executor = __esm(() => {
|
|
184
|
+
init_logger();
|
|
185
|
+
init_executor();
|
|
186
|
+
BWRAP_UNAVAILABLE_CODES = new Set(["ENOENT", "EACCES", "ENOSPC"]);
|
|
187
|
+
_internals2 = {
|
|
188
|
+
probeBwrap
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// src/sandbox/executors/bubblewrap.ts
|
|
193
|
+
var exports_bubblewrap = {};
|
|
194
|
+
__export(exports_bubblewrap, {
|
|
195
|
+
BubblewrapSandboxExecutor: () => BubblewrapSandboxExecutor
|
|
196
|
+
});
|
|
197
|
+
var init_bubblewrap = __esm(() => {
|
|
198
|
+
init_bubblewrap_executor();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// src/sandbox/macos/sandbox-exec-executor.ts
|
|
202
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
203
|
+
import { mkdtempSync, writeFileSync } from "fs";
|
|
204
|
+
import * as os from "os";
|
|
205
|
+
import * as path from "path";
|
|
206
|
+
function probeSandboxExec() {
|
|
207
|
+
try {
|
|
208
|
+
const result = spawnSync2("sandbox-exec", ["--version"], {
|
|
209
|
+
windowsHide: true,
|
|
210
|
+
encoding: "utf-8",
|
|
211
|
+
timeout: 5000,
|
|
212
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
213
|
+
});
|
|
214
|
+
if (result.error) {
|
|
215
|
+
const code = result.error.code;
|
|
216
|
+
if (code && SANDBOX_UNAVAILABLE_CODES.has(code)) {
|
|
217
|
+
warn(`Sandbox disabled: sandbox-exec error (${code}). Falling through to tool-layer enforcement.`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
warn(`Sandbox disabled: spawn error (${result.error.message}). Falling through to tool-layer enforcement.`);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return result.status === 0;
|
|
224
|
+
} catch (err) {
|
|
225
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
226
|
+
warn(`Sandbox disabled: probe threw (${message}). Falling through to tool-layer enforcement.`);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function shellEscape2(s) {
|
|
231
|
+
return s.replace(/'/g, "'\\''");
|
|
232
|
+
}
|
|
233
|
+
function sbplEscapePath(path2) {
|
|
234
|
+
const withoutControlChars = path2.split("").filter((ch) => {
|
|
235
|
+
const cp = ch.codePointAt(0);
|
|
236
|
+
return cp >= 32 && cp !== 127;
|
|
237
|
+
}).join("");
|
|
238
|
+
return withoutControlChars.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
239
|
+
}
|
|
240
|
+
function buildSandboxProfile(scopePaths, tempDir, envOverrides) {
|
|
241
|
+
const rwPaths = [...scopePaths];
|
|
242
|
+
if (tempDir) {
|
|
243
|
+
rwPaths.push(tempDir);
|
|
244
|
+
}
|
|
245
|
+
const rwAllowLines = rwPaths.map((p) => `(allow file-write* (subpath "${sbplEscapePath(p)}"))`).join(`
|
|
246
|
+
`);
|
|
247
|
+
const envLines = [];
|
|
248
|
+
if (envOverrides) {
|
|
249
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
250
|
+
if (!isValidEnvKey(key)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (value === null) {
|
|
254
|
+
envLines.push(`(unsetenv ${key})`);
|
|
255
|
+
} else {
|
|
256
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
257
|
+
envLines.push(`(setenv ${key} "${escaped}")`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const profile = `(version 1)
|
|
262
|
+
(allow default)
|
|
263
|
+
(allow file-read* (subpath "/usr"))
|
|
264
|
+
(allow file-read* (subpath "/bin"))
|
|
265
|
+
(allow file-read* (subpath "/sbin"))
|
|
266
|
+
(allow file-read* (subpath "/lib"))
|
|
267
|
+
(allow file-read* (subpath "/lib64"))
|
|
268
|
+
${rwAllowLines}
|
|
269
|
+
${envLines.join(`
|
|
270
|
+
`)}
|
|
271
|
+
(deny file-write*)`;
|
|
272
|
+
return profile;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
class MacOSSandboxExecutor {
|
|
276
|
+
mechanism = "sandbox-exec";
|
|
277
|
+
_scopePaths;
|
|
278
|
+
_tempDir;
|
|
279
|
+
_available;
|
|
280
|
+
_disabledReason;
|
|
281
|
+
constructor(scopePaths = [], tempDir) {
|
|
282
|
+
if (process.platform !== "darwin") {
|
|
283
|
+
throw new Error("MacOSSandboxExecutor not yet implemented");
|
|
284
|
+
}
|
|
285
|
+
this._scopePaths = scopePaths;
|
|
286
|
+
this._tempDir = tempDir;
|
|
287
|
+
this._available = false;
|
|
288
|
+
this._disabledReason = null;
|
|
289
|
+
try {
|
|
290
|
+
if (!_internals3.probeSandboxExec()) {
|
|
291
|
+
this._disabledReason = "sandbox-exec not available or not functional";
|
|
292
|
+
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
293
|
+
} else {
|
|
294
|
+
this._available = true;
|
|
295
|
+
}
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
298
|
+
this._disabledReason = `constructor threw: ${message}`;
|
|
299
|
+
this._available = false;
|
|
300
|
+
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
isAvailable() {
|
|
304
|
+
return this._available;
|
|
305
|
+
}
|
|
306
|
+
disable(reason) {
|
|
307
|
+
this._disabledReason = reason;
|
|
308
|
+
this._available = false;
|
|
309
|
+
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
310
|
+
}
|
|
311
|
+
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
312
|
+
if (!this._available) {
|
|
313
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
314
|
+
}
|
|
315
|
+
if (!_internals3.probeSandboxExec()) {
|
|
316
|
+
this._available = false;
|
|
317
|
+
this._disabledReason = "sandbox-exec became unavailable between calls";
|
|
318
|
+
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
319
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
320
|
+
}
|
|
321
|
+
const temp = tempDir ?? this._tempDir ?? os.tmpdir();
|
|
322
|
+
const allScopes = [...this._scopePaths, ...scopePaths];
|
|
323
|
+
const profile = buildSandboxProfile(allScopes, temp, envOverrides);
|
|
324
|
+
let profilePath;
|
|
325
|
+
try {
|
|
326
|
+
const profileDir = mkdtempSync(path.join(os.tmpdir(), "sandbox-"));
|
|
327
|
+
profilePath = path.join(profileDir, `profile-${process.pid}-${Date.now()}.sb`);
|
|
328
|
+
writeFileSync(profilePath, profile, { mode: 384 });
|
|
329
|
+
} catch (err) {
|
|
330
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
331
|
+
warn(`Sandbox disabled: failed to write profile (${message}). Falling through to tool-layer enforcement.`);
|
|
332
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
333
|
+
}
|
|
334
|
+
const escapedCommand = shellEscape2(command);
|
|
335
|
+
const escapedProfilePath = shellEscape2(profilePath);
|
|
336
|
+
return `sandbox-exec -f '${escapedProfilePath}' bash -c '${escapedCommand}'`;
|
|
337
|
+
}
|
|
338
|
+
getEnvOverrides() {
|
|
339
|
+
return {
|
|
340
|
+
DYLD_INSERT_LIBRARIES: null,
|
|
341
|
+
DYLD_LIBRARY_PATH: null,
|
|
342
|
+
DYLD_FRAMEWORK_PATH: null,
|
|
343
|
+
DYLD_ROOT_PATH: null,
|
|
344
|
+
PATH: "/usr/bin:/bin:/usr/sbin:/sbin"
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
var SANDBOX_UNAVAILABLE_CODES, _internals3;
|
|
349
|
+
var init_sandbox_exec_executor = __esm(() => {
|
|
350
|
+
init_logger();
|
|
351
|
+
init_executor();
|
|
352
|
+
SANDBOX_UNAVAILABLE_CODES = new Set(["ENOENT", "EACCES", "ENOSPC"]);
|
|
353
|
+
_internals3 = {
|
|
354
|
+
probeSandboxExec
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// src/sandbox/executors/macos.ts
|
|
359
|
+
var exports_macos = {};
|
|
360
|
+
__export(exports_macos, {
|
|
361
|
+
MacOSSandboxExecutor: () => MacOSSandboxExecutor
|
|
362
|
+
});
|
|
363
|
+
var init_macos = __esm(() => {
|
|
364
|
+
init_sandbox_exec_executor();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// src/sandbox/win32/edge-cases.ts
|
|
368
|
+
function detectPowerShellEscape(command) {
|
|
369
|
+
if (process.platform !== "win32")
|
|
370
|
+
return false;
|
|
371
|
+
const encodedCommandPattern = /-EncodedCommand/i;
|
|
372
|
+
if (encodedCommandPattern.test(command)) {
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
const encodedBase64Pattern = /-Enc(odedCommand)?\s+[A-Za-z0-9+/=]+/i;
|
|
376
|
+
if (encodedBase64Pattern.test(command)) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
const psExePattern = /powershell\.exe?\s+.*-(?:e(?:n(?:c(?:odedCommand)?)?)?|wmi|remote)/i;
|
|
380
|
+
if (psExePattern.test(command)) {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
const iexPattern = /\biex\b/i;
|
|
384
|
+
if (iexPattern.test(command)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
const invokeExpressionPattern = /Invoke-Expression/i;
|
|
388
|
+
if (invokeExpressionPattern.test(command)) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
const invokeCommandRemotePattern = /Invoke-Command\s+.*-ComputerName/i;
|
|
392
|
+
if (invokeCommandRemotePattern.test(command)) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
const psCommandPattern = /powershell.*-Command\s+["'].*\$/i;
|
|
396
|
+
if (psCommandPattern.test(command)) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
const execPolicyPattern = /-ExecutionPolicy\s+(?:Bypass|RemoteSigned|Unrestricted)/i;
|
|
400
|
+
if (execPolicyPattern.test(command)) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
const noProfilePattern = /-NoProfile\s+-/i;
|
|
404
|
+
if (noProfilePattern.test(command)) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
const automationPattern = /New-Object\s+.*Management\.Automation/i;
|
|
408
|
+
if (automationPattern.test(command)) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
const downloadPattern = /\[System\.Net\.(?:WebClient|Http).*\]\.(?:DownloadString|DownloadFile)/i;
|
|
412
|
+
if (downloadPattern.test(command)) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
const hiddenWindowPattern = /-WindowStyle\s+Hidden/i;
|
|
416
|
+
if (hiddenWindowPattern.test(command)) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
const scriptInjectionPattern = /\$(?:env|input|host)\b.*\|/i;
|
|
420
|
+
if (scriptInjectionPattern.test(command)) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
const obfuscatedIexPattern = /IEX|i:eX/i;
|
|
424
|
+
if (obfuscatedIexPattern.test(command)) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
var init_edge_cases = () => {};
|
|
430
|
+
|
|
431
|
+
// src/sandbox/win32/restricted-environment-executor.ts
|
|
432
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
433
|
+
import * as os2 from "os";
|
|
434
|
+
import * as path2 from "path";
|
|
435
|
+
function probeWindowsSandbox() {
|
|
436
|
+
try {
|
|
437
|
+
const result = spawnSync3("cmd", ["/c", "echo", "ok"], {
|
|
438
|
+
windowsHide: true,
|
|
439
|
+
encoding: "utf-8",
|
|
440
|
+
timeout: 5000,
|
|
441
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
442
|
+
});
|
|
443
|
+
if (result.error) {
|
|
444
|
+
const code = result.error.code;
|
|
445
|
+
if (code && SANDBOX_UNAVAILABLE_CODES2.has(code)) {
|
|
446
|
+
warn(`Sandbox disabled: spawn error (${code}). Falling through to tool-layer enforcement.`);
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
warn(`Sandbox disabled: spawn error (${result.error.message}). Falling through to tool-layer enforcement.`);
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
return result.status === 0;
|
|
453
|
+
} catch (err) {
|
|
454
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
455
|
+
warn(`Sandbox disabled: probe threw (${message}). Falling through to tool-layer enforcement.`);
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function psStringEscape(s) {
|
|
460
|
+
return s.replace(/[`"$]/g, "`$&");
|
|
461
|
+
}
|
|
462
|
+
function getSafeWindowsPath() {
|
|
463
|
+
const raw = process.env.SystemRoot;
|
|
464
|
+
const isValid = typeof raw === "string" && raw.length > 0 && /^[A-Za-z]:[\\/](?:[^\\/;"'<>|*?\r\n]+[\\/]?)*$/.test(raw);
|
|
465
|
+
const sysRoot = isValid ? raw : "C:\\Windows";
|
|
466
|
+
return `${sysRoot}\\System32;${sysRoot}`;
|
|
467
|
+
}
|
|
468
|
+
function isPowerShellNativeCommand(command) {
|
|
469
|
+
return /^(?:Remove-Item|rm|del|erase|Copy-Item|cp|copy|Move-Item|mv|move|Rename-Item|ren|New-Item|ni|Get-Item|gi|Get-ChildItem|ls|dir|gci|Get-Content|cat|type|gc|Set-Content|sc|Add-Content|ac|Clear-Content|clc|Test-Path|Resolve-Path|Split-Path|Join-Path|Out-File|Get-Date)\b/i.test(command.trimStart());
|
|
470
|
+
}
|
|
471
|
+
function isSafePsCommandBody(command) {
|
|
472
|
+
return !/[;&|`$()\r\n]/.test(command);
|
|
473
|
+
}
|
|
474
|
+
function isPathInScopes(command, scopes) {
|
|
475
|
+
if (scopes.length === 0)
|
|
476
|
+
return true;
|
|
477
|
+
const pathPattern = /[A-Za-z]:(?:[^\\/:*?"<>|\r\n]+(?:\\[^\\/:*?"<>|\r\n]+)*)/g;
|
|
478
|
+
const paths = command.match(pathPattern) || [];
|
|
479
|
+
if (paths.length === 0)
|
|
480
|
+
return true;
|
|
481
|
+
const normalizedPaths = paths.map((p) => path2.win32.resolve(p));
|
|
482
|
+
const normalizedScopes = scopes.map((s) => s.toLowerCase().replace(/\\+$/, ""));
|
|
483
|
+
return normalizedPaths.every((p) => {
|
|
484
|
+
const lower = p.toLowerCase();
|
|
485
|
+
return normalizedScopes.some((scope) => lower.startsWith(scope));
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
class WindowsSandboxExecutor {
|
|
490
|
+
mechanism = "powershell-wrapper";
|
|
491
|
+
_scopePaths;
|
|
492
|
+
_tempDir;
|
|
493
|
+
_available;
|
|
494
|
+
_disabled;
|
|
495
|
+
_disabledReason;
|
|
496
|
+
constructor(scopePaths = [], tempDir) {
|
|
497
|
+
this._scopePaths = scopePaths;
|
|
498
|
+
this._tempDir = tempDir;
|
|
499
|
+
this._available = false;
|
|
500
|
+
this._disabled = false;
|
|
501
|
+
this._disabledReason = null;
|
|
502
|
+
try {
|
|
503
|
+
if (!_internals4.probeWindowsSandbox()) {
|
|
504
|
+
this._available = false;
|
|
505
|
+
this._disabledReason = "Windows sandbox not available or not functional";
|
|
506
|
+
warn(`Sandbox unavailable: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
507
|
+
} else {
|
|
508
|
+
this._available = true;
|
|
509
|
+
}
|
|
510
|
+
} catch (err) {
|
|
511
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
512
|
+
this._available = false;
|
|
513
|
+
this._disabledReason = `constructor probe threw: ${message}`;
|
|
514
|
+
warn(`Sandbox unavailable: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
isAvailable() {
|
|
518
|
+
return this._available && !this._disabled;
|
|
519
|
+
}
|
|
520
|
+
disable(reason) {
|
|
521
|
+
this._disabled = true;
|
|
522
|
+
this._disabledReason = reason;
|
|
523
|
+
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
524
|
+
}
|
|
525
|
+
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
526
|
+
if (!this.isAvailable()) {
|
|
527
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
528
|
+
}
|
|
529
|
+
if (!_internals4.probeWindowsSandbox()) {
|
|
530
|
+
this._available = false;
|
|
531
|
+
this._disabledReason = "Windows sandbox became unavailable between calls";
|
|
532
|
+
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
533
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
534
|
+
}
|
|
535
|
+
const temp = tempDir ?? this._tempDir ?? os2.tmpdir();
|
|
536
|
+
const _allScopes = [...this._scopePaths, ...scopePaths];
|
|
537
|
+
if (detectPowerShellEscape(command)) {
|
|
538
|
+
throw new SandboxError("Command contains PowerShell escape patterns", "DETECT_POWERSHELL_ESCAPE");
|
|
539
|
+
}
|
|
540
|
+
if (!isPathInScopes(command, _allScopes)) {
|
|
541
|
+
throw new SandboxError("Command targets paths outside authorized scopes", "PATH_ESCAPE_SCOPE");
|
|
542
|
+
}
|
|
543
|
+
if (isPowerShellNativeCommand(command) && !isSafePsCommandBody(command)) {
|
|
544
|
+
throw new SandboxError("PowerShell-native command body contains unsafe characters", "UNSAFE_PS_COMMAND");
|
|
545
|
+
}
|
|
546
|
+
const safePath = getSafeWindowsPath();
|
|
547
|
+
const escapedTemp = psStringEscape(temp);
|
|
548
|
+
const escapedCommand = psStringEscape(command);
|
|
549
|
+
const envOverrideLines = [];
|
|
550
|
+
if (envOverrides) {
|
|
551
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
552
|
+
if (!isValidEnvKey(key)) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (value === null) {
|
|
556
|
+
envOverrideLines.push(`Remove-Item Env:${key} -Force -ErrorAction SilentlyContinue;`);
|
|
557
|
+
} else {
|
|
558
|
+
const psEscaped = value.replace(/'/g, "''");
|
|
559
|
+
envOverrideLines.push(`$env:${key} = '${psEscaped}';`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const commandExec = isPowerShellNativeCommand(command) ? `Invoke-Expression "${escapedCommand}"` : `cmd /c "${escapedCommand}"`;
|
|
564
|
+
const envOverrideBlock = envOverrideLines.length > 0 ? `
|
|
565
|
+
${envOverrideLines.join(`
|
|
566
|
+
`)}` : "";
|
|
567
|
+
const psScript = `
|
|
568
|
+
$ErrorActionPreference = 'Stop';
|
|
569
|
+
try {
|
|
570
|
+
# Set scoped temp directory
|
|
571
|
+
$env:TEMP = '${escapedTemp}';
|
|
572
|
+
$env:TMP = '${escapedTemp}';
|
|
573
|
+
|
|
574
|
+
# Restrict PATH to safe system paths only
|
|
575
|
+
$env:PATH = '${safePath}';
|
|
576
|
+
|
|
577
|
+
# Remove dangerous environment variables that could be used to bypass restrictions
|
|
578
|
+
# Note: LD_PRELOAD, DYLD_* don't apply on Windows but we clear them for completeness
|
|
579
|
+
$dangerousVars = @(
|
|
580
|
+
'LD_PRELOAD',
|
|
581
|
+
'DYLD_INSERT_LIBRARIES',
|
|
582
|
+
'DYLD_LIBRARY_PATH',
|
|
583
|
+
'DYLD_FRAMEWORK_PATH',
|
|
584
|
+
'DYLD_ROOT_PATH',
|
|
585
|
+
'DYLD_FORCE_FLAT_NAMESPACE'
|
|
586
|
+
);
|
|
587
|
+
foreach ($v in $dangerousVars) {
|
|
588
|
+
if (Test-Path Env:$v) {
|
|
589
|
+
Remove-Item Env:$v -Force -ErrorAction SilentlyContinue;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
${envOverrideBlock}
|
|
593
|
+
|
|
594
|
+
# Execute the command \u2014 PS-native cmdlets via Invoke-Expression, others via the standard command interpreter
|
|
595
|
+
${commandExec};
|
|
596
|
+
} catch {
|
|
597
|
+
Write-Error $_.Exception.Message;
|
|
598
|
+
exit 1;
|
|
599
|
+
}`;
|
|
600
|
+
return `powershell -ExecutionPolicy Bypass -NoProfile -WindowStyle Hidden -Command "${psScript.replace(/\n/g, " ").trim()}"`;
|
|
601
|
+
}
|
|
602
|
+
getEnvOverrides() {
|
|
603
|
+
return {
|
|
604
|
+
PATH: getSafeWindowsPath(),
|
|
605
|
+
TEMP: null,
|
|
606
|
+
TMP: null,
|
|
607
|
+
LD_PRELOAD: null,
|
|
608
|
+
DYLD_INSERT_LIBRARIES: null,
|
|
609
|
+
DYLD_LIBRARY_PATH: null,
|
|
610
|
+
DYLD_FRAMEWORK_PATH: null,
|
|
611
|
+
DYLD_ROOT_PATH: null,
|
|
612
|
+
DYLD_FORCE_FLAT_NAMESPACE: null
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
var SANDBOX_UNAVAILABLE_CODES2, _internals4;
|
|
617
|
+
var init_restricted_environment_executor = __esm(() => {
|
|
618
|
+
init_logger();
|
|
619
|
+
init_executor();
|
|
620
|
+
init_edge_cases();
|
|
621
|
+
SANDBOX_UNAVAILABLE_CODES2 = new Set([
|
|
622
|
+
"ENOENT",
|
|
623
|
+
"EACCES",
|
|
624
|
+
"EPERM",
|
|
625
|
+
"ENOSPC"
|
|
626
|
+
]);
|
|
627
|
+
_internals4 = {
|
|
628
|
+
probeWindowsSandbox
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// src/sandbox/win32/native-sandbox-executor.ts
|
|
633
|
+
import * as crypto from "crypto";
|
|
634
|
+
import * as fs from "fs";
|
|
635
|
+
import * as os3 from "os";
|
|
636
|
+
import * as path3 from "path";
|
|
637
|
+
|
|
638
|
+
class NativeWindowsSandboxExecutor {
|
|
639
|
+
mechanism;
|
|
640
|
+
_probeResult;
|
|
641
|
+
_fallbackExecutor;
|
|
642
|
+
_strength;
|
|
643
|
+
_disabled;
|
|
644
|
+
_disabledReason;
|
|
645
|
+
constructor(scopePaths = [], tempDir) {
|
|
646
|
+
this._disabled = false;
|
|
647
|
+
this._disabledReason = null;
|
|
648
|
+
this._fallbackExecutor = new WindowsSandboxExecutor(scopePaths, tempDir);
|
|
649
|
+
this._probeResult = probe();
|
|
650
|
+
if (this._probeResult.available) {
|
|
651
|
+
this._strength = "strong";
|
|
652
|
+
this.mechanism = `native-runner/${this._probeResult.mode}`;
|
|
653
|
+
} else if (this._fallbackExecutor.isAvailable()) {
|
|
654
|
+
this._strength = "weak";
|
|
655
|
+
this.mechanism = "powershell-wrapper";
|
|
656
|
+
warn(`Native sandbox runner unavailable (${this._probeResult.error ?? "unknown"}); ` + "falling back to PowerShell environment restrictions (weak sandbox)");
|
|
657
|
+
} else {
|
|
658
|
+
this._strength = "none";
|
|
659
|
+
this.mechanism = "none";
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
get strength() {
|
|
663
|
+
if (this._disabled)
|
|
664
|
+
return "none";
|
|
665
|
+
return this._strength;
|
|
666
|
+
}
|
|
667
|
+
isAvailable() {
|
|
668
|
+
return !this._disabled && this._strength !== "none";
|
|
669
|
+
}
|
|
670
|
+
disable(reason) {
|
|
671
|
+
this._disabled = true;
|
|
672
|
+
this._disabledReason = reason;
|
|
673
|
+
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
674
|
+
}
|
|
675
|
+
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
676
|
+
if (!this.isAvailable()) {
|
|
677
|
+
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
678
|
+
}
|
|
679
|
+
if (this._strength === "strong") {
|
|
680
|
+
return this._wrapWithRunner(command, scopePaths, tempDir, envOverrides);
|
|
681
|
+
}
|
|
682
|
+
return this._fallbackExecutor.wrapCommand(command, scopePaths, tempDir, envOverrides);
|
|
683
|
+
}
|
|
684
|
+
_wrapWithRunner(command, scopePaths, tempDir, _envOverrides) {
|
|
685
|
+
const binary = _internals.findRunnerBinary();
|
|
686
|
+
if (!binary) {
|
|
687
|
+
throw new SandboxError("Runner binary not found", "RUNNER_NOT_FOUND");
|
|
688
|
+
}
|
|
689
|
+
const workspaceRoot = scopePaths[0] ?? process.cwd();
|
|
690
|
+
const runId = `swarm-${crypto.randomUUID?.() ?? Date.now()}`;
|
|
691
|
+
const policy = buildDefaultPolicy(workspaceRoot, runId);
|
|
692
|
+
if (tempDir) {
|
|
693
|
+
policy.temp_root = tempDir;
|
|
694
|
+
}
|
|
695
|
+
policy.workspace_roots = scopePaths.length > 0 ? scopePaths : [workspaceRoot];
|
|
696
|
+
policy.writable_roots = policy.workspace_roots;
|
|
697
|
+
if (_envOverrides) {
|
|
698
|
+
for (const [key, value] of Object.entries(_envOverrides)) {
|
|
699
|
+
if (!isValidEnvKey(key))
|
|
700
|
+
continue;
|
|
701
|
+
if (typeof value === "string") {
|
|
702
|
+
policy.env_overrides[key] = value;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const policyJson = JSON.stringify(policy);
|
|
707
|
+
const policyDir = path3.join(os3.tmpdir(), "swarm-sandbox-policies");
|
|
708
|
+
try {
|
|
709
|
+
fs.mkdirSync(policyDir, { recursive: true });
|
|
710
|
+
} catch {}
|
|
711
|
+
const policyFile = path3.join(policyDir, `${runId}.json`);
|
|
712
|
+
fs.writeFileSync(policyFile, policyJson, "utf-8");
|
|
713
|
+
const mode = this._probeResult.mode === "none" ? "auto" : this._probeResult.mode;
|
|
714
|
+
const escapedBinary = binary.includes(" ") ? `"${binary}"` : binary;
|
|
715
|
+
const escapedPolicyFile = policyFile.includes(" ") ? `"${policyFile}"` : policyFile;
|
|
716
|
+
const escapedCommand = command.replace(/"/g, "\\\"");
|
|
717
|
+
return `cmd /c "type ${escapedPolicyFile} | ${escapedBinary} --policy-stdin --mode ${mode} -- ${escapedCommand} & del ${escapedPolicyFile} 2>nul"`;
|
|
718
|
+
}
|
|
719
|
+
getEnvOverrides() {
|
|
720
|
+
if (this._strength === "strong") {
|
|
721
|
+
return {
|
|
722
|
+
PATH: null,
|
|
723
|
+
TEMP: null,
|
|
724
|
+
TMP: null,
|
|
725
|
+
LD_PRELOAD: null,
|
|
726
|
+
DYLD_INSERT_LIBRARIES: null,
|
|
727
|
+
DYLD_LIBRARY_PATH: null,
|
|
728
|
+
DYLD_FRAMEWORK_PATH: null,
|
|
729
|
+
DYLD_ROOT_PATH: null,
|
|
730
|
+
DYLD_FORCE_FLAT_NAMESPACE: null
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
return this._fallbackExecutor.getEnvOverrides();
|
|
734
|
+
}
|
|
735
|
+
async executeNative(command, workspaceRoot, runId) {
|
|
736
|
+
if (this._strength !== "strong") {
|
|
737
|
+
throw new SandboxError("Native execution requires strong sandbox (runner binary)", "NATIVE_UNAVAILABLE");
|
|
738
|
+
}
|
|
739
|
+
const policy = buildDefaultPolicy(workspaceRoot, runId);
|
|
740
|
+
return execute(command, policy);
|
|
741
|
+
}
|
|
742
|
+
get hasNativeRunner() {
|
|
743
|
+
return this._strength === "strong";
|
|
744
|
+
}
|
|
745
|
+
get disabledReason() {
|
|
746
|
+
return this._disabledReason;
|
|
747
|
+
}
|
|
748
|
+
get probeResult() {
|
|
749
|
+
return this._probeResult;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
var init_native_sandbox_executor = __esm(() => {
|
|
753
|
+
init_logger();
|
|
754
|
+
init_executor();
|
|
755
|
+
init_restricted_environment_executor();
|
|
756
|
+
init_runner_client();
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// src/sandbox/executors/windows.ts
|
|
760
|
+
var exports_windows = {};
|
|
761
|
+
__export(exports_windows, {
|
|
762
|
+
WindowsSandboxExecutor: () => NativeWindowsSandboxExecutor,
|
|
763
|
+
LegacyWindowsSandboxExecutor: () => WindowsSandboxExecutor
|
|
764
|
+
});
|
|
765
|
+
var init_windows = __esm(() => {
|
|
766
|
+
init_native_sandbox_executor();
|
|
767
|
+
init_restricted_environment_executor();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// src/sandbox/executor.ts
|
|
771
|
+
function isValidEnvKey(key) {
|
|
772
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key);
|
|
773
|
+
}
|
|
774
|
+
async function getExecutor() {
|
|
775
|
+
if (_cachedExecutorPromise !== undefined) {
|
|
776
|
+
return _cachedExecutorPromise;
|
|
777
|
+
}
|
|
778
|
+
_cachedExecutorPromise = _createExecutor();
|
|
779
|
+
return _cachedExecutorPromise;
|
|
780
|
+
}
|
|
781
|
+
async function _createExecutor() {
|
|
782
|
+
const platform = process.platform;
|
|
783
|
+
if (platform === "linux") {
|
|
784
|
+
return _createLinuxExecutor();
|
|
785
|
+
}
|
|
786
|
+
if (platform === "darwin") {
|
|
787
|
+
return _createMacOSExecutor();
|
|
788
|
+
}
|
|
789
|
+
if (platform === "win32") {
|
|
790
|
+
return _createWindowsExecutor();
|
|
791
|
+
}
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
async function _createLinuxExecutor() {
|
|
795
|
+
const { SandboxCapabilityProbe, isBubblewrapAvailable } = await import("./capability-probe-sh9bwh39.js");
|
|
796
|
+
await new SandboxCapabilityProbe().detect();
|
|
797
|
+
if (!isBubblewrapAvailable()) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const { BubblewrapSandboxExecutor: BubblewrapSandboxExecutor2 } = (init_bubblewrap(), __toCommonJS(exports_bubblewrap));
|
|
802
|
+
return new BubblewrapSandboxExecutor2([]);
|
|
803
|
+
} catch {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
async function _createMacOSExecutor() {
|
|
808
|
+
const { SandboxCapabilityProbe, isSandboxExecAvailable } = await import("./capability-probe-sh9bwh39.js");
|
|
809
|
+
await new SandboxCapabilityProbe().detect();
|
|
810
|
+
if (!isSandboxExecAvailable()) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
const { MacOSSandboxExecutor: MacOSSandboxExecutor2 } = (init_macos(), __toCommonJS(exports_macos));
|
|
815
|
+
return new MacOSSandboxExecutor2([]);
|
|
816
|
+
} catch {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function _createWindowsExecutor() {
|
|
821
|
+
const { SandboxCapabilityProbe, isWindowsSandboxAvailable } = await import("./capability-probe-sh9bwh39.js");
|
|
822
|
+
await new SandboxCapabilityProbe().detect();
|
|
823
|
+
if (!isWindowsSandboxAvailable()) {
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
const { WindowsSandboxExecutor: WindowsSandboxExecutor2 } = (init_windows(), __toCommonJS(exports_windows));
|
|
828
|
+
return new WindowsSandboxExecutor2([]);
|
|
829
|
+
} catch {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
var SandboxError, _cachedExecutorPromise;
|
|
834
|
+
var init_executor = __esm(() => {
|
|
835
|
+
SandboxError = class SandboxError extends Error {
|
|
836
|
+
code;
|
|
837
|
+
constructor(message, code) {
|
|
838
|
+
super(message);
|
|
839
|
+
this.code = code;
|
|
840
|
+
this.name = "SandboxError";
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
});
|
|
38
844
|
|
|
39
845
|
// src/plan/manager.ts
|
|
40
846
|
import {
|
|
@@ -45,7 +851,7 @@ import {
|
|
|
45
851
|
unlinkSync as unlinkSync3
|
|
46
852
|
} from "fs";
|
|
47
853
|
import * as fsPromises from "fs/promises";
|
|
48
|
-
import * as
|
|
854
|
+
import * as path9 from "path";
|
|
49
855
|
|
|
50
856
|
// src/config/plan-schema.ts
|
|
51
857
|
var ExecutionProfileSchema = exports_external.object({
|
|
@@ -109,8 +915,10 @@ var PlanSchema = exports_external.object({
|
|
|
109
915
|
});
|
|
110
916
|
|
|
111
917
|
// src/git/branch.ts
|
|
918
|
+
init_executor();
|
|
112
919
|
import * as child_process from "child_process";
|
|
113
|
-
import
|
|
920
|
+
import * as fsSync from "fs";
|
|
921
|
+
import path4 from "path";
|
|
114
922
|
|
|
115
923
|
// src/utils/git-binary-missing-error.ts
|
|
116
924
|
class GitBinaryMissingError extends Error {
|
|
@@ -157,11 +965,11 @@ function windowsGitCandidates() {
|
|
|
157
965
|
const roots = unique([
|
|
158
966
|
process.env.ProgramFiles ?? "",
|
|
159
967
|
process.env["ProgramFiles(x86)"] ?? "",
|
|
160
|
-
process.env.LOCALAPPDATA ?
|
|
968
|
+
process.env.LOCALAPPDATA ? path4.join(process.env.LOCALAPPDATA, "Programs") : ""
|
|
161
969
|
]);
|
|
162
970
|
const installed = roots.flatMap((root) => [
|
|
163
|
-
|
|
164
|
-
|
|
971
|
+
path4.join(root, "Git", "cmd", "git.exe"),
|
|
972
|
+
path4.join(root, "Git", "bin", "git.exe")
|
|
165
973
|
]);
|
|
166
974
|
return unique(["git", ...installed]);
|
|
167
975
|
}
|
|
@@ -172,20 +980,59 @@ function isNotGitRepositoryMessage(message) {
|
|
|
172
980
|
const lower = message.toLowerCase();
|
|
173
981
|
return lower.includes("not a git repository") || lower.includes("not a git repo");
|
|
174
982
|
}
|
|
175
|
-
function
|
|
983
|
+
function getChildProcessSpawnSync() {
|
|
984
|
+
return child_process.spawnSync;
|
|
985
|
+
}
|
|
986
|
+
var __spawnSyncSeam = {
|
|
987
|
+
spawnSync: (cmd, args, options) => {
|
|
988
|
+
const mergedEnv = mergeEnvForChild(options?.env, options?.envOverrides);
|
|
989
|
+
return getChildProcessSpawnSync()(cmd, args, {
|
|
990
|
+
...options,
|
|
991
|
+
env: mergedEnv
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
function readLaneEnvFileFromDiskSync(worktreePath, laneIndex) {
|
|
996
|
+
const envPath = path4.join(worktreePath, ".swarm", "lanes", `${laneIndex}.env`);
|
|
997
|
+
let content;
|
|
998
|
+
try {
|
|
999
|
+
content = fsSync.readFileSync(envPath, "utf-8");
|
|
1000
|
+
} catch {
|
|
1001
|
+
return {};
|
|
1002
|
+
}
|
|
1003
|
+
const result = {};
|
|
1004
|
+
for (const rawLine of content.split(`
|
|
1005
|
+
`)) {
|
|
1006
|
+
const line = rawLine.trim();
|
|
1007
|
+
if (!line || line.startsWith("#"))
|
|
1008
|
+
continue;
|
|
1009
|
+
const eqIdx = line.indexOf("=");
|
|
1010
|
+
if (eqIdx < 0)
|
|
1011
|
+
continue;
|
|
1012
|
+
const k = line.slice(0, eqIdx);
|
|
1013
|
+
const v = line.slice(eqIdx + 1);
|
|
1014
|
+
if (!isValidEnvKey(k))
|
|
1015
|
+
continue;
|
|
1016
|
+
result[k] = v;
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
function gitExec(args, cwd, laneEnv, laneIndex) {
|
|
1021
|
+
const resolvedLaneEnv = laneEnv ?? (laneIndex !== undefined ? readLaneEnvFileFromDiskSync(cwd, laneIndex) : undefined);
|
|
176
1022
|
let missingGitError;
|
|
177
1023
|
const subcommand = args[0];
|
|
178
1024
|
const hardenedArgs = subcommand === "commit" || subcommand === "tag" ? ["-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", ...args] : args;
|
|
179
1025
|
for (const command of windowsGitCandidates()) {
|
|
180
1026
|
for (let attempt = 0;attempt < MAX_TRANSIENT_RETRIES; attempt++) {
|
|
181
|
-
const result =
|
|
1027
|
+
const result = _internals5.spawnSync(command, hardenedArgs, {
|
|
182
1028
|
cwd,
|
|
183
1029
|
encoding: "utf-8",
|
|
184
1030
|
timeout: GIT_TIMEOUT_MS,
|
|
185
1031
|
windowsHide: true,
|
|
186
1032
|
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
187
1033
|
stdio: ["ignore", "pipe", "pipe"],
|
|
188
|
-
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
1034
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
1035
|
+
envOverrides: resolvedLaneEnv
|
|
189
1036
|
});
|
|
190
1037
|
if (!result.error && result.status === 0) {
|
|
191
1038
|
return result.stdout;
|
|
@@ -231,8 +1078,8 @@ function getGitRepositoryStatus(cwd) {
|
|
|
231
1078
|
function isGitRepo(cwd) {
|
|
232
1079
|
return getGitRepositoryStatus(cwd).isRepo;
|
|
233
1080
|
}
|
|
234
|
-
function getCurrentBranch(cwd) {
|
|
235
|
-
const output = gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
1081
|
+
function getCurrentBranch(cwd, laneEnv, laneIndex) {
|
|
1082
|
+
const output = gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd, laneEnv, laneIndex);
|
|
236
1083
|
return output.trim();
|
|
237
1084
|
}
|
|
238
1085
|
function getDefaultBaseBranch(cwd) {
|
|
@@ -248,8 +1095,8 @@ function getDefaultBaseBranch(cwd) {
|
|
|
248
1095
|
}
|
|
249
1096
|
}
|
|
250
1097
|
}
|
|
251
|
-
function hasUncommittedChanges(cwd) {
|
|
252
|
-
const status = gitExec(["status", "--porcelain"], cwd);
|
|
1098
|
+
function hasUncommittedChanges(cwd, laneEnv, laneIndex) {
|
|
1099
|
+
const status = gitExec(["status", "--porcelain"], cwd, laneEnv, laneIndex);
|
|
253
1100
|
return status.trim().length > 0;
|
|
254
1101
|
}
|
|
255
1102
|
function detectDefaultRemoteBranch(cwd) {
|
|
@@ -283,7 +1130,7 @@ async function resetToRemoteBranch(cwd, options) {
|
|
|
283
1130
|
const prunedBranches = [];
|
|
284
1131
|
try {
|
|
285
1132
|
const currentBranch = getCurrentBranch(cwd);
|
|
286
|
-
const defaultRemoteBranch =
|
|
1133
|
+
const defaultRemoteBranch = _internals5.detectDefaultRemoteBranch(cwd);
|
|
287
1134
|
if (!defaultRemoteBranch) {
|
|
288
1135
|
return {
|
|
289
1136
|
success: false,
|
|
@@ -465,7 +1312,7 @@ var GITIGNORED_BUILD_ARTIFACTS = ["dist"];
|
|
|
465
1312
|
async function resetToMainAfterMerge(cwd, options) {
|
|
466
1313
|
const warnings = [];
|
|
467
1314
|
try {
|
|
468
|
-
const defaultBranch =
|
|
1315
|
+
const defaultBranch = _internals5.detectDefaultRemoteBranch(cwd);
|
|
469
1316
|
if (!defaultBranch) {
|
|
470
1317
|
return {
|
|
471
1318
|
success: false,
|
|
@@ -492,7 +1339,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
492
1339
|
}
|
|
493
1340
|
if (currentBranch === defaultBranch) {
|
|
494
1341
|
try {
|
|
495
|
-
const logOutput =
|
|
1342
|
+
const logOutput = _internals5.gitExec(["log", `${targetBranch}..HEAD`, "--oneline"], cwd);
|
|
496
1343
|
if (logOutput.trim().length > 0) {
|
|
497
1344
|
return {
|
|
498
1345
|
success: false,
|
|
@@ -507,11 +1354,11 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
507
1354
|
} catch {}
|
|
508
1355
|
} else {
|
|
509
1356
|
try {
|
|
510
|
-
|
|
1357
|
+
_internals5.gitExec(["rev-parse", "--abbrev-ref", `${currentBranch}@{upstream}`], cwd);
|
|
511
1358
|
} catch {
|
|
512
1359
|
try {
|
|
513
|
-
const localSha =
|
|
514
|
-
const remoteSha =
|
|
1360
|
+
const localSha = _internals5.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
1361
|
+
const remoteSha = _internals5.gitExec(["rev-parse", targetBranch], cwd).trim();
|
|
515
1362
|
if (localSha !== remoteSha) {
|
|
516
1363
|
return {
|
|
517
1364
|
success: false,
|
|
@@ -537,7 +1384,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
537
1384
|
}
|
|
538
1385
|
}
|
|
539
1386
|
try {
|
|
540
|
-
|
|
1387
|
+
_internals5.gitExec(["fetch", "--prune", "origin"], cwd);
|
|
541
1388
|
} catch (err) {
|
|
542
1389
|
return {
|
|
543
1390
|
success: false,
|
|
@@ -553,7 +1400,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
553
1400
|
let switchedBranch = false;
|
|
554
1401
|
if (currentBranch !== defaultBranch) {
|
|
555
1402
|
try {
|
|
556
|
-
|
|
1403
|
+
_internals5.gitExec(["checkout", defaultBranch], cwd);
|
|
557
1404
|
switchedBranch = true;
|
|
558
1405
|
} catch (err) {
|
|
559
1406
|
return {
|
|
@@ -568,7 +1415,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
568
1415
|
}
|
|
569
1416
|
}
|
|
570
1417
|
try {
|
|
571
|
-
|
|
1418
|
+
_internals5.gitExec(["reset", "--hard", targetBranch], cwd);
|
|
572
1419
|
} catch (err) {
|
|
573
1420
|
return {
|
|
574
1421
|
success: false,
|
|
@@ -588,7 +1435,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
588
1435
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
589
1436
|
}
|
|
590
1437
|
try {
|
|
591
|
-
|
|
1438
|
+
_internals5.gitExec(["checkout", "--", "."], cwd);
|
|
592
1439
|
discardSucceeded = true;
|
|
593
1440
|
break;
|
|
594
1441
|
} catch {}
|
|
@@ -599,18 +1446,18 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
599
1446
|
changesDiscarded = discardSucceeded;
|
|
600
1447
|
}
|
|
601
1448
|
try {
|
|
602
|
-
|
|
1449
|
+
_internals5.gitExec(["clean", "-fdX", "--", ...GITIGNORED_BUILD_ARTIFACTS], cwd);
|
|
603
1450
|
} catch {
|
|
604
1451
|
warnings.push("Could not clean build artifacts");
|
|
605
1452
|
}
|
|
606
1453
|
let branchDeleted = false;
|
|
607
1454
|
if (switchedBranch && previousBranch !== defaultBranch) {
|
|
608
1455
|
try {
|
|
609
|
-
const mergedOutput =
|
|
1456
|
+
const mergedOutput = _internals5.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
610
1457
|
const isMerged = mergedOutput.split(`
|
|
611
1458
|
`).some((line) => line.trim() === previousBranch || line.trim() === `* ${previousBranch}`);
|
|
612
1459
|
if (isMerged) {
|
|
613
|
-
|
|
1460
|
+
_internals5.gitExec(["branch", "-d", previousBranch], cwd);
|
|
614
1461
|
branchDeleted = true;
|
|
615
1462
|
} else {
|
|
616
1463
|
warnings.push(`Branch ${previousBranch} is not merged into ${defaultBranch} \u2014 keeping it`);
|
|
@@ -621,7 +1468,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
621
1468
|
}
|
|
622
1469
|
if (options?.pruneBranches) {
|
|
623
1470
|
try {
|
|
624
|
-
const mergedOutput =
|
|
1471
|
+
const mergedOutput = _internals5.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
625
1472
|
const mergedLines = mergedOutput.split(`
|
|
626
1473
|
`);
|
|
627
1474
|
for (const line of mergedLines) {
|
|
@@ -630,7 +1477,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
630
1477
|
continue;
|
|
631
1478
|
}
|
|
632
1479
|
try {
|
|
633
|
-
|
|
1480
|
+
_internals5.gitExec(["branch", "-d", trimmedLine], cwd);
|
|
634
1481
|
} catch {
|
|
635
1482
|
warnings.push(`Could not prune branch: ${trimmedLine}`);
|
|
636
1483
|
}
|
|
@@ -660,28 +1507,31 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
660
1507
|
};
|
|
661
1508
|
}
|
|
662
1509
|
}
|
|
663
|
-
var
|
|
1510
|
+
var _internals5 = {
|
|
664
1511
|
gitExec,
|
|
665
1512
|
detectDefaultRemoteBranch,
|
|
666
1513
|
getDefaultBaseBranch,
|
|
667
1514
|
getGitRepositoryStatus,
|
|
668
1515
|
resetToRemoteBranch,
|
|
669
|
-
resetToMainAfterMerge
|
|
1516
|
+
resetToMainAfterMerge,
|
|
1517
|
+
spawnSync: __spawnSyncSeam.spawnSync,
|
|
1518
|
+
readLaneEnvFileFromDiskSync
|
|
670
1519
|
};
|
|
671
1520
|
|
|
672
1521
|
// src/hooks/delegation-gate/worktree-merge-status.ts
|
|
673
|
-
import * as
|
|
1522
|
+
import * as fs2 from "fs";
|
|
1523
|
+
import * as path5 from "path";
|
|
674
1524
|
var failuresByTask = new Map;
|
|
675
1525
|
var durableStatusPath;
|
|
676
1526
|
var hasLoaded = false;
|
|
677
1527
|
function loadDurableStatus(statusPath) {
|
|
678
1528
|
try {
|
|
679
1529
|
hasLoaded = true;
|
|
680
|
-
if (!
|
|
1530
|
+
if (!fs2.existsSync(statusPath)) {
|
|
681
1531
|
failuresByTask.clear();
|
|
682
1532
|
return;
|
|
683
1533
|
}
|
|
684
|
-
const data = JSON.parse(
|
|
1534
|
+
const data = JSON.parse(fs2.readFileSync(statusPath, "utf-8"));
|
|
685
1535
|
if (data && typeof data === "object") {
|
|
686
1536
|
failuresByTask.clear();
|
|
687
1537
|
for (const [taskId, failure] of Object.entries(data)) {
|
|
@@ -692,25 +1542,39 @@ function loadDurableStatus(statusPath) {
|
|
|
692
1542
|
failuresByTask.clear();
|
|
693
1543
|
}
|
|
694
1544
|
}
|
|
1545
|
+
function initDurableStatusPath(projectDir) {
|
|
1546
|
+
const nextPath = path5.join(projectDir, ".swarm", "worktree-merge-status.json");
|
|
1547
|
+
if (durableStatusPath === nextPath && hasLoaded)
|
|
1548
|
+
return;
|
|
1549
|
+
durableStatusPath = nextPath;
|
|
1550
|
+
hasLoaded = false;
|
|
1551
|
+
loadDurableStatus(durableStatusPath);
|
|
1552
|
+
}
|
|
695
1553
|
function getWorktreeMergeFailure(taskId) {
|
|
696
1554
|
if (!hasLoaded && durableStatusPath) {
|
|
697
1555
|
loadDurableStatus(durableStatusPath);
|
|
698
1556
|
}
|
|
699
1557
|
return failuresByTask.get(taskId);
|
|
700
1558
|
}
|
|
1559
|
+
function getAllWorktreeMergeFailures() {
|
|
1560
|
+
if (!hasLoaded && durableStatusPath) {
|
|
1561
|
+
loadDurableStatus(durableStatusPath);
|
|
1562
|
+
}
|
|
1563
|
+
return failuresByTask.entries();
|
|
1564
|
+
}
|
|
701
1565
|
|
|
702
1566
|
// src/turbo/epic/state.ts
|
|
703
1567
|
init_logger();
|
|
704
|
-
import * as
|
|
705
|
-
import * as
|
|
1568
|
+
import * as fs3 from "fs";
|
|
1569
|
+
import * as path6 from "path";
|
|
706
1570
|
var STATE_FILE = "epic-state.json";
|
|
707
1571
|
function nowISO() {
|
|
708
1572
|
return new Date().toISOString();
|
|
709
1573
|
}
|
|
710
1574
|
function ensureSwarmDir(directory) {
|
|
711
|
-
const swarmDir =
|
|
712
|
-
if (!
|
|
713
|
-
|
|
1575
|
+
const swarmDir = path6.resolve(directory, ".swarm");
|
|
1576
|
+
if (!fs3.existsSync(swarmDir)) {
|
|
1577
|
+
fs3.mkdirSync(swarmDir, { recursive: true });
|
|
714
1578
|
}
|
|
715
1579
|
return swarmDir;
|
|
716
1580
|
}
|
|
@@ -730,17 +1594,17 @@ function markStateUnreadable(directory, reason) {
|
|
|
730
1594
|
}
|
|
731
1595
|
function readPersisted(directory) {
|
|
732
1596
|
try {
|
|
733
|
-
const filePath =
|
|
734
|
-
if (!
|
|
1597
|
+
const filePath = path6.join(directory, ".swarm", STATE_FILE);
|
|
1598
|
+
if (!fs3.existsSync(filePath)) {
|
|
735
1599
|
const seed = emptyPersisted();
|
|
736
1600
|
try {
|
|
737
1601
|
ensureSwarmDir(directory);
|
|
738
|
-
|
|
1602
|
+
fs3.writeFileSync(filePath, `${JSON.stringify(seed, null, 2)}
|
|
739
1603
|
`, "utf-8");
|
|
740
1604
|
} catch {}
|
|
741
1605
|
return seed;
|
|
742
1606
|
}
|
|
743
|
-
const raw =
|
|
1607
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
744
1608
|
const parsed = JSON.parse(raw);
|
|
745
1609
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 1 || !parsed.sessions || typeof parsed.sessions !== "object" || Array.isArray(parsed.sessions)) {
|
|
746
1610
|
markStateUnreadable(directory, `malformed shape (version=${parsed?.version}, sessions type=${Array.isArray(parsed?.sessions) ? "array" : typeof parsed?.sessions})`);
|
|
@@ -766,7 +1630,7 @@ function writePersisted(directory, persisted) {
|
|
|
766
1630
|
let payload;
|
|
767
1631
|
try {
|
|
768
1632
|
ensureSwarmDir(directory);
|
|
769
|
-
filePath =
|
|
1633
|
+
filePath = path6.join(directory, ".swarm", STATE_FILE);
|
|
770
1634
|
tmpPath = `${filePath}.tmp.${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
771
1635
|
persisted.updatedAt = nowISO();
|
|
772
1636
|
payload = `${JSON.stringify(persisted, null, 2)}
|
|
@@ -777,14 +1641,14 @@ function writePersisted(directory, persisted) {
|
|
|
777
1641
|
throw new Error(`Epic state persistence prepare failed: ${msg}`);
|
|
778
1642
|
}
|
|
779
1643
|
try {
|
|
780
|
-
|
|
781
|
-
|
|
1644
|
+
fs3.writeFileSync(tmpPath, payload, "utf-8");
|
|
1645
|
+
fs3.renameSync(tmpPath, filePath);
|
|
782
1646
|
} catch (error2) {
|
|
783
1647
|
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
784
1648
|
error(`[turbo/epic/state] Failed to persist ${STATE_FILE} atomically: ${msg}`);
|
|
785
1649
|
try {
|
|
786
|
-
if (
|
|
787
|
-
|
|
1650
|
+
if (fs3.existsSync(tmpPath))
|
|
1651
|
+
fs3.unlinkSync(tmpPath);
|
|
788
1652
|
} catch {}
|
|
789
1653
|
throw new Error(`Epic state persistence failed: ${msg}`);
|
|
790
1654
|
}
|
|
@@ -815,7 +1679,7 @@ function isEpicModeActive(directory, sessionID) {
|
|
|
815
1679
|
function isEpicModeActiveForProject(directory) {
|
|
816
1680
|
if (stateUnreadableMap.get(directory))
|
|
817
1681
|
return false;
|
|
818
|
-
if (!
|
|
1682
|
+
if (!fs3.existsSync(path6.join(directory, ".swarm", STATE_FILE))) {
|
|
819
1683
|
return false;
|
|
820
1684
|
}
|
|
821
1685
|
const persisted = readPersisted(directory);
|
|
@@ -866,11 +1730,11 @@ function isLockContentionError(err) {
|
|
|
866
1730
|
return INDEX_LOCK_ERROR_RE.test(msg);
|
|
867
1731
|
}
|
|
868
1732
|
async function commitTaskCompletion(directory, taskId, description, scopePaths) {
|
|
869
|
-
if (!
|
|
1733
|
+
if (!_internals6.isGitRepo(directory)) {
|
|
870
1734
|
return { committed: false, reason: "no-git" };
|
|
871
1735
|
}
|
|
872
1736
|
try {
|
|
873
|
-
if (
|
|
1737
|
+
if (_internals6.hasExistingTaskCommit(directory, taskId)) {
|
|
874
1738
|
return { committed: true, reason: "idempotent-skip" };
|
|
875
1739
|
}
|
|
876
1740
|
} catch {}
|
|
@@ -891,15 +1755,15 @@ async function commitTaskCompletion(directory, taskId, description, scopePaths)
|
|
|
891
1755
|
for (let attempt = 0;attempt <= INDEX_LOCK_BACKOFF_MS.length; attempt++) {
|
|
892
1756
|
try {
|
|
893
1757
|
if (paths.length > 0) {
|
|
894
|
-
|
|
1758
|
+
_internals6.stageScopedPaths(directory, paths);
|
|
895
1759
|
}
|
|
896
|
-
|
|
897
|
-
const sha =
|
|
1760
|
+
_internals6.commitAllowEmpty(directory, message);
|
|
1761
|
+
const sha = _internals6.gitHeadSha(directory);
|
|
898
1762
|
return { committed: true, reason: "success", sha };
|
|
899
1763
|
} catch (err) {
|
|
900
1764
|
lastError = err;
|
|
901
1765
|
if (attempt < INDEX_LOCK_BACKOFF_MS.length && isLockContentionError(err)) {
|
|
902
|
-
await
|
|
1766
|
+
await _internals6.sleep(INDEX_LOCK_BACKOFF_MS[attempt]);
|
|
903
1767
|
continue;
|
|
904
1768
|
}
|
|
905
1769
|
break;
|
|
@@ -909,26 +1773,26 @@ async function commitTaskCompletion(directory, taskId, description, scopePaths)
|
|
|
909
1773
|
criticalWarn(`[epic:task-commit] commit for task ${taskId} failed (non-fatal): ${msg}`);
|
|
910
1774
|
return { committed: false, reason: "commit-failed", error: msg };
|
|
911
1775
|
}
|
|
912
|
-
var
|
|
1776
|
+
var _internals6 = {
|
|
913
1777
|
isGitRepo: (cwd) => isGitRepo(cwd),
|
|
914
1778
|
stageScopedPaths: (cwd, paths) => {
|
|
915
1779
|
const CHUNK = 200;
|
|
916
1780
|
for (let i = 0;i < paths.length; i += CHUNK) {
|
|
917
1781
|
const chunk = paths.slice(i, i + CHUNK);
|
|
918
|
-
|
|
1782
|
+
_internals5.gitExec(["add", "--", ...chunk, ":(exclude,glob)**/.swarm/**"], cwd);
|
|
919
1783
|
}
|
|
920
1784
|
},
|
|
921
1785
|
commitAllowEmpty: (cwd, message) => {
|
|
922
|
-
|
|
1786
|
+
_internals5.gitExec(["commit", "--allow-empty", "--no-verify", "-m", message], cwd);
|
|
923
1787
|
},
|
|
924
1788
|
gitHeadSha: (cwd) => {
|
|
925
|
-
return
|
|
1789
|
+
return _internals5.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
926
1790
|
},
|
|
927
1791
|
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
|
|
928
1792
|
hasExistingTaskCommit: (cwd, taskId) => {
|
|
929
1793
|
const safeId = scrubTaskIdForGitSubject(taskId);
|
|
930
1794
|
const escaped = safeId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
931
|
-
const output =
|
|
1795
|
+
const output = _internals5.gitExec([
|
|
932
1796
|
"log",
|
|
933
1797
|
"--extended-regexp",
|
|
934
1798
|
`--grep=^swarm\\(task ${escaped}\\):`,
|
|
@@ -941,8 +1805,8 @@ var _internals2 = {
|
|
|
941
1805
|
};
|
|
942
1806
|
|
|
943
1807
|
// src/turbo/lean/conflicts.ts
|
|
944
|
-
import * as
|
|
945
|
-
import * as
|
|
1808
|
+
import * as fs4 from "fs";
|
|
1809
|
+
import * as path7 from "path";
|
|
946
1810
|
|
|
947
1811
|
// src/utils/path.ts
|
|
948
1812
|
function normalizePath(p) {
|
|
@@ -1070,12 +1934,12 @@ function isProtectedPath(normalizedPath) {
|
|
|
1070
1934
|
return false;
|
|
1071
1935
|
}
|
|
1072
1936
|
function readTaskScopes(directory, taskId) {
|
|
1073
|
-
const scopePath =
|
|
1937
|
+
const scopePath = path7.join(directory, ".swarm", "scopes", `scope-${taskId}.json`);
|
|
1074
1938
|
try {
|
|
1075
|
-
if (!
|
|
1939
|
+
if (!fs4.existsSync(scopePath)) {
|
|
1076
1940
|
return null;
|
|
1077
1941
|
}
|
|
1078
|
-
const raw =
|
|
1942
|
+
const raw = fs4.readFileSync(scopePath, "utf-8");
|
|
1079
1943
|
const parsed = JSON.parse(raw);
|
|
1080
1944
|
if (!parsed || !Array.isArray(parsed.files)) {
|
|
1081
1945
|
return null;
|
|
@@ -1088,16 +1952,16 @@ function readTaskScopes(directory, taskId) {
|
|
|
1088
1952
|
|
|
1089
1953
|
// src/utils/spec-hash.ts
|
|
1090
1954
|
import { createHash } from "crypto";
|
|
1091
|
-
import * as
|
|
1092
|
-
import { join as
|
|
1955
|
+
import * as fsSync2 from "fs";
|
|
1956
|
+
import { join as join6 } from "path";
|
|
1093
1957
|
async function computeSpecHash(directory) {
|
|
1094
|
-
const spec =
|
|
1958
|
+
const spec = _internals7.readEffectiveSpecSync(directory);
|
|
1095
1959
|
if (!spec)
|
|
1096
1960
|
return null;
|
|
1097
1961
|
return createHash("sha256").update(spec.content, "utf-8").digest("hex");
|
|
1098
1962
|
}
|
|
1099
1963
|
async function isSpecStale(directory, plan) {
|
|
1100
|
-
const currentHash = await
|
|
1964
|
+
const currentHash = await _internals7.computeSpecHash(directory);
|
|
1101
1965
|
if (!plan.specHash) {
|
|
1102
1966
|
return { stale: false };
|
|
1103
1967
|
}
|
|
@@ -1118,15 +1982,15 @@ async function isSpecStale(directory, plan) {
|
|
|
1118
1982
|
return { stale: false };
|
|
1119
1983
|
}
|
|
1120
1984
|
function isObligationPreserving(directory) {
|
|
1121
|
-
const snapshotPath =
|
|
1985
|
+
const snapshotPath = join6(directory, ".swarm", "spec-snapshot.md");
|
|
1122
1986
|
try {
|
|
1123
1987
|
let recordedContent;
|
|
1124
1988
|
try {
|
|
1125
|
-
recordedContent =
|
|
1989
|
+
recordedContent = fsSync2.readFileSync(snapshotPath, "utf-8");
|
|
1126
1990
|
} catch {
|
|
1127
1991
|
return false;
|
|
1128
1992
|
}
|
|
1129
|
-
const currentResult =
|
|
1993
|
+
const currentResult = _internals7.readEffectiveSpecSync(directory);
|
|
1130
1994
|
if (!currentResult || !currentResult.content) {
|
|
1131
1995
|
return false;
|
|
1132
1996
|
}
|
|
@@ -1156,7 +2020,7 @@ function isObligationPreserving(directory) {
|
|
|
1156
2020
|
return false;
|
|
1157
2021
|
}
|
|
1158
2022
|
}
|
|
1159
|
-
var
|
|
2023
|
+
var _internals7 = {
|
|
1160
2024
|
computeSpecDiff,
|
|
1161
2025
|
computeSpecHash,
|
|
1162
2026
|
isObligationPreserving,
|
|
@@ -1164,19 +2028,19 @@ var _internals3 = {
|
|
|
1164
2028
|
readEffectiveSpecSync
|
|
1165
2029
|
};
|
|
1166
2030
|
function computeSpecDiff(directory) {
|
|
1167
|
-
const snapshotPath =
|
|
2031
|
+
const snapshotPath = join6(directory, ".swarm", "spec-snapshot.md");
|
|
1168
2032
|
let recordedContent;
|
|
1169
2033
|
try {
|
|
1170
|
-
const snapshotSize =
|
|
2034
|
+
const snapshotSize = fsSync2.statSync(snapshotPath).size;
|
|
1171
2035
|
const MAX_SNAPSHOT_BYTES = 512 * 1024;
|
|
1172
2036
|
if (snapshotSize > MAX_SNAPSHOT_BYTES) {
|
|
1173
2037
|
return null;
|
|
1174
2038
|
}
|
|
1175
|
-
recordedContent =
|
|
2039
|
+
recordedContent = fsSync2.readFileSync(snapshotPath, "utf-8");
|
|
1176
2040
|
} catch {
|
|
1177
2041
|
return null;
|
|
1178
2042
|
}
|
|
1179
|
-
const spec =
|
|
2043
|
+
const spec = _internals7.readEffectiveSpecSync(directory);
|
|
1180
2044
|
if (!spec) {
|
|
1181
2045
|
return null;
|
|
1182
2046
|
}
|
|
@@ -1250,9 +2114,9 @@ function computeLcsDiff(oldLines, newLines) {
|
|
|
1250
2114
|
}
|
|
1251
2115
|
|
|
1252
2116
|
// src/plan/ledger.ts
|
|
1253
|
-
import * as
|
|
1254
|
-
import * as
|
|
1255
|
-
import * as
|
|
2117
|
+
import * as crypto2 from "crypto";
|
|
2118
|
+
import * as fs5 from "fs";
|
|
2119
|
+
import * as path8 from "path";
|
|
1256
2120
|
|
|
1257
2121
|
// src/plan/utils.ts
|
|
1258
2122
|
function derivePlanId(plan) {
|
|
@@ -1262,7 +2126,7 @@ function derivePlanId(plan) {
|
|
|
1262
2126
|
// src/plan/ledger.ts
|
|
1263
2127
|
var LEDGER_SCHEMA_VERSION = "1.1.0";
|
|
1264
2128
|
var LEDGER_FILENAME = "plan-ledger.jsonl";
|
|
1265
|
-
var LEDGER_LOCK_PATH =
|
|
2129
|
+
var LEDGER_LOCK_PATH = path8.join(".swarm", LEDGER_FILENAME);
|
|
1266
2130
|
var PLAN_JSON_FILENAME = "plan.json";
|
|
1267
2131
|
|
|
1268
2132
|
class LedgerStaleWriterError extends Error {
|
|
@@ -1272,10 +2136,10 @@ class LedgerStaleWriterError extends Error {
|
|
|
1272
2136
|
}
|
|
1273
2137
|
}
|
|
1274
2138
|
function getLedgerPath(directory) {
|
|
1275
|
-
return
|
|
2139
|
+
return path8.join(directory, ".swarm", LEDGER_FILENAME);
|
|
1276
2140
|
}
|
|
1277
2141
|
function getPlanJsonPath(directory) {
|
|
1278
|
-
return
|
|
2142
|
+
return path8.join(directory, ".swarm", PLAN_JSON_FILENAME);
|
|
1279
2143
|
}
|
|
1280
2144
|
function computePlanHash(plan) {
|
|
1281
2145
|
const normalized = {
|
|
@@ -1305,12 +2169,12 @@ function computePlanHash(plan) {
|
|
|
1305
2169
|
}))
|
|
1306
2170
|
};
|
|
1307
2171
|
const jsonString = JSON.stringify(normalized);
|
|
1308
|
-
return
|
|
2172
|
+
return crypto2.createHash("sha256").update(jsonString, "utf8").digest("hex");
|
|
1309
2173
|
}
|
|
1310
2174
|
function computeCurrentPlanHash(directory) {
|
|
1311
2175
|
const planPath = getPlanJsonPath(directory);
|
|
1312
2176
|
try {
|
|
1313
|
-
const content =
|
|
2177
|
+
const content = fs5.readFileSync(planPath, "utf8");
|
|
1314
2178
|
const plan = JSON.parse(content);
|
|
1315
2179
|
return computePlanHash(plan);
|
|
1316
2180
|
} catch {
|
|
@@ -1319,15 +2183,15 @@ function computeCurrentPlanHash(directory) {
|
|
|
1319
2183
|
}
|
|
1320
2184
|
async function ledgerExists(directory) {
|
|
1321
2185
|
const ledgerPath = getLedgerPath(directory);
|
|
1322
|
-
return
|
|
2186
|
+
return fs5.existsSync(ledgerPath);
|
|
1323
2187
|
}
|
|
1324
2188
|
async function getLatestLedgerSeq(directory) {
|
|
1325
2189
|
const ledgerPath = getLedgerPath(directory);
|
|
1326
|
-
if (!
|
|
2190
|
+
if (!fs5.existsSync(ledgerPath)) {
|
|
1327
2191
|
return 0;
|
|
1328
2192
|
}
|
|
1329
2193
|
try {
|
|
1330
|
-
const content =
|
|
2194
|
+
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
1331
2195
|
const lines = content.trim().split(`
|
|
1332
2196
|
`).filter((line) => line.trim() !== "");
|
|
1333
2197
|
if (lines.length === 0) {
|
|
@@ -1349,11 +2213,11 @@ async function getLatestLedgerSeq(directory) {
|
|
|
1349
2213
|
}
|
|
1350
2214
|
async function readLedgerEvents(directory) {
|
|
1351
2215
|
const ledgerPath = getLedgerPath(directory);
|
|
1352
|
-
if (!
|
|
2216
|
+
if (!fs5.existsSync(ledgerPath)) {
|
|
1353
2217
|
return [];
|
|
1354
2218
|
}
|
|
1355
2219
|
try {
|
|
1356
|
-
const content =
|
|
2220
|
+
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
1357
2221
|
const lines = content.trim().split(`
|
|
1358
2222
|
`).filter((line) => line.trim() !== "");
|
|
1359
2223
|
const events = [];
|
|
@@ -1378,15 +2242,15 @@ async function readLedgerEvents(directory) {
|
|
|
1378
2242
|
async function initLedger(directory, planId, initialPlanHash, initialPlan) {
|
|
1379
2243
|
const ledgerPath = getLedgerPath(directory);
|
|
1380
2244
|
const planJsonPath = getPlanJsonPath(directory);
|
|
1381
|
-
if (
|
|
2245
|
+
if (fs5.existsSync(ledgerPath)) {
|
|
1382
2246
|
throw new Error("Ledger already initialized. Use appendLedgerEvent to add events.");
|
|
1383
2247
|
}
|
|
1384
2248
|
let planHashAfter = initialPlanHash ?? "";
|
|
1385
2249
|
let embeddedPlan = initialPlan;
|
|
1386
2250
|
if (!initialPlanHash) {
|
|
1387
2251
|
try {
|
|
1388
|
-
if (
|
|
1389
|
-
const content =
|
|
2252
|
+
if (fs5.existsSync(planJsonPath)) {
|
|
2253
|
+
const content = fs5.readFileSync(planJsonPath, "utf8");
|
|
1390
2254
|
const plan = JSON.parse(content);
|
|
1391
2255
|
planHashAfter = computePlanHash(plan);
|
|
1392
2256
|
if (!embeddedPlan)
|
|
@@ -1406,12 +2270,12 @@ async function initLedger(directory, planId, initialPlanHash, initialPlan) {
|
|
|
1406
2270
|
schema_version: LEDGER_SCHEMA_VERSION,
|
|
1407
2271
|
...payload ? { payload } : {}
|
|
1408
2272
|
};
|
|
1409
|
-
|
|
2273
|
+
fs5.mkdirSync(path8.join(directory, ".swarm"), { recursive: true });
|
|
1410
2274
|
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
1411
2275
|
const line = `${JSON.stringify(event)}
|
|
1412
2276
|
`;
|
|
1413
|
-
|
|
1414
|
-
|
|
2277
|
+
fs5.writeFileSync(tempPath, line, "utf8");
|
|
2278
|
+
fs5.renameSync(tempPath, ledgerPath);
|
|
1415
2279
|
}
|
|
1416
2280
|
async function appendLedgerEvent(directory, eventInput, options) {
|
|
1417
2281
|
return withEvidenceLock(directory, LEDGER_LOCK_PATH, "plan-ledger", "append-ledger-event", async () => {
|
|
@@ -1434,17 +2298,17 @@ async function appendLedgerEvent(directory, eventInput, options) {
|
|
|
1434
2298
|
plan_hash_after: planHashAfter,
|
|
1435
2299
|
schema_version: LEDGER_SCHEMA_VERSION
|
|
1436
2300
|
};
|
|
1437
|
-
|
|
2301
|
+
fs5.mkdirSync(path8.join(directory, ".swarm"), { recursive: true });
|
|
1438
2302
|
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
1439
2303
|
const line = `${JSON.stringify(event)}
|
|
1440
2304
|
`;
|
|
1441
|
-
if (
|
|
1442
|
-
const existingContent =
|
|
1443
|
-
|
|
2305
|
+
if (fs5.existsSync(ledgerPath)) {
|
|
2306
|
+
const existingContent = fs5.readFileSync(ledgerPath, "utf8");
|
|
2307
|
+
fs5.writeFileSync(tempPath, existingContent + line, "utf8");
|
|
1444
2308
|
} else {
|
|
1445
2309
|
throw new Error("Ledger not initialized. Call initLedger() first.");
|
|
1446
2310
|
}
|
|
1447
|
-
|
|
2311
|
+
fs5.renameSync(tempPath, ledgerPath);
|
|
1448
2312
|
return event;
|
|
1449
2313
|
});
|
|
1450
2314
|
}
|
|
@@ -1532,12 +2396,12 @@ async function replayFromLedger(directory, _options) {
|
|
|
1532
2396
|
}
|
|
1533
2397
|
}
|
|
1534
2398
|
const planJsonPath = getPlanJsonPath(directory);
|
|
1535
|
-
if (!
|
|
2399
|
+
if (!fs5.existsSync(planJsonPath)) {
|
|
1536
2400
|
return null;
|
|
1537
2401
|
}
|
|
1538
2402
|
let plan;
|
|
1539
2403
|
try {
|
|
1540
|
-
const content =
|
|
2404
|
+
const content = fs5.readFileSync(planJsonPath, "utf8");
|
|
1541
2405
|
plan = JSON.parse(content);
|
|
1542
2406
|
} catch {
|
|
1543
2407
|
return null;
|
|
@@ -1632,11 +2496,11 @@ function applyEventToPlan(plan, event) {
|
|
|
1632
2496
|
}
|
|
1633
2497
|
async function readLedgerEventsWithIntegrity(directory) {
|
|
1634
2498
|
const ledgerPath = getLedgerPath(directory);
|
|
1635
|
-
if (!
|
|
2499
|
+
if (!fs5.existsSync(ledgerPath)) {
|
|
1636
2500
|
return { events: [], truncated: false, badSuffix: null };
|
|
1637
2501
|
}
|
|
1638
2502
|
try {
|
|
1639
|
-
const content =
|
|
2503
|
+
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
1640
2504
|
const lines = content.split(`
|
|
1641
2505
|
`);
|
|
1642
2506
|
const events = [];
|
|
@@ -1665,9 +2529,9 @@ async function readLedgerEventsWithIntegrity(directory) {
|
|
|
1665
2529
|
}
|
|
1666
2530
|
async function quarantineLedgerSuffix(directory, badSuffix) {
|
|
1667
2531
|
try {
|
|
1668
|
-
const quarantinePath =
|
|
1669
|
-
|
|
1670
|
-
console.warn(`[ledger] Corrupted suffix quarantined to ${
|
|
2532
|
+
const quarantinePath = path8.join(directory, ".swarm", "plan-ledger.quarantine");
|
|
2533
|
+
fs5.writeFileSync(quarantinePath, badSuffix, "utf8");
|
|
2534
|
+
console.warn(`[ledger] Corrupted suffix quarantined to ${path8.relative(directory, quarantinePath)}`);
|
|
1671
2535
|
} catch {}
|
|
1672
2536
|
}
|
|
1673
2537
|
async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
@@ -1726,7 +2590,7 @@ var startupLedgerCheckedWorkspaces = new Set;
|
|
|
1726
2590
|
var ledgerStaleWorkspaces = new Set;
|
|
1727
2591
|
var recoveryMutexes = new Map;
|
|
1728
2592
|
var PLAN_JSON_CACHE_NAMESPACE = "plan-json:validated:v1";
|
|
1729
|
-
var
|
|
2593
|
+
var _internals8 = {
|
|
1730
2594
|
loadPlan,
|
|
1731
2595
|
loadPlanJsonOnly,
|
|
1732
2596
|
regeneratePlanMarkdown,
|
|
@@ -1866,7 +2730,7 @@ async function getLatestLedgerHash(directory) {
|
|
|
1866
2730
|
}
|
|
1867
2731
|
}
|
|
1868
2732
|
async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
1869
|
-
const resolvedWorkspace =
|
|
2733
|
+
const resolvedWorkspace = path9.resolve(directory);
|
|
1870
2734
|
if (!ledgerStaleWorkspaces.has(resolvedWorkspace)) {
|
|
1871
2735
|
return plan;
|
|
1872
2736
|
}
|
|
@@ -1885,7 +2749,7 @@ async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
|
1885
2749
|
return plan;
|
|
1886
2750
|
}
|
|
1887
2751
|
async function parsePlanJsonCached(directory) {
|
|
1888
|
-
const planJsonPath =
|
|
2752
|
+
const planJsonPath = path9.resolve(directory, ".swarm", "plan.json");
|
|
1889
2753
|
return readCachedParsedFile(planJsonPath, PLAN_JSON_CACHE_NAMESPACE, () => readSwarmFileAsync(directory, "plan.json"), (planJsonContent) => {
|
|
1890
2754
|
if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
|
|
1891
2755
|
throw new Error("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
|
|
@@ -1945,13 +2809,13 @@ async function isPlanMdInSync(directory, plan, cache) {
|
|
|
1945
2809
|
return false;
|
|
1946
2810
|
}
|
|
1947
2811
|
async function regeneratePlanMarkdown(directory, plan) {
|
|
1948
|
-
const swarmDir =
|
|
2812
|
+
const swarmDir = path9.resolve(directory, ".swarm");
|
|
1949
2813
|
const contentHash = computePlanContentHash(plan);
|
|
1950
2814
|
const markdown = derivePlanMarkdown(plan);
|
|
1951
2815
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
1952
2816
|
${markdown}`;
|
|
1953
|
-
const mdPath =
|
|
1954
|
-
const mdTempPath =
|
|
2817
|
+
const mdPath = path9.join(swarmDir, "plan.md");
|
|
2818
|
+
const mdTempPath = path9.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1955
2819
|
try {
|
|
1956
2820
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
1957
2821
|
renameSync4(mdTempPath, mdPath);
|
|
@@ -1975,7 +2839,7 @@ async function loadPlan(directory, cache) {
|
|
|
1975
2839
|
const inSync = await isPlanMdInSync(directory, validated, cache);
|
|
1976
2840
|
if (!inSync) {
|
|
1977
2841
|
try {
|
|
1978
|
-
await
|
|
2842
|
+
await _internals8.regeneratePlanMarkdown(directory, validated);
|
|
1979
2843
|
} catch (regenError) {
|
|
1980
2844
|
warn(`Failed to regenerate plan.md: ${regenError instanceof Error ? regenError.message : String(regenError)}. Proceeding with plan.json only.`);
|
|
1981
2845
|
}
|
|
@@ -1983,7 +2847,7 @@ async function loadPlan(directory, cache) {
|
|
|
1983
2847
|
if (await ledgerExists(directory)) {
|
|
1984
2848
|
const planHash = computePlanHash(validated);
|
|
1985
2849
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
1986
|
-
const resolvedWorkspace =
|
|
2850
|
+
const resolvedWorkspace = path9.resolve(directory);
|
|
1987
2851
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
1988
2852
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
1989
2853
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
@@ -2049,7 +2913,7 @@ async function loadPlan(directory, cache) {
|
|
|
2049
2913
|
if (!preserving) {
|
|
2050
2914
|
try {
|
|
2051
2915
|
const diffInfo = computeSpecDiff(directory);
|
|
2052
|
-
const specStalenessPath =
|
|
2916
|
+
const specStalenessPath = path9.join(directory, ".swarm", "spec-staleness.json");
|
|
2053
2917
|
await fsPromises.writeFile(specStalenessPath, JSON.stringify({
|
|
2054
2918
|
type: "spec_stale_detected",
|
|
2055
2919
|
timestamp: new Date().toISOString(),
|
|
@@ -2063,7 +2927,7 @@ async function loadPlan(directory, cache) {
|
|
|
2063
2927
|
}, null, 2), "utf-8");
|
|
2064
2928
|
} catch {}
|
|
2065
2929
|
try {
|
|
2066
|
-
const eventsPath =
|
|
2930
|
+
const eventsPath = path9.join(directory, ".swarm", "events.jsonl");
|
|
2067
2931
|
const event = {
|
|
2068
2932
|
type: "spec_stale_detected",
|
|
2069
2933
|
timestamp: new Date().toISOString(),
|
|
@@ -2129,11 +2993,11 @@ async function loadPlan(directory, cache) {
|
|
|
2129
2993
|
return migrated;
|
|
2130
2994
|
}
|
|
2131
2995
|
if (await ledgerExists(directory)) {
|
|
2132
|
-
const resolvedDir =
|
|
2996
|
+
const resolvedDir = path9.resolve(directory);
|
|
2133
2997
|
const existingMutex = recoveryMutexes.get(resolvedDir);
|
|
2134
2998
|
if (existingMutex) {
|
|
2135
2999
|
await existingMutex;
|
|
2136
|
-
const postRecoveryPlan = await
|
|
3000
|
+
const postRecoveryPlan = await _internals8.loadPlanJsonOnly(directory);
|
|
2137
3001
|
if (postRecoveryPlan)
|
|
2138
3002
|
return postRecoveryPlan;
|
|
2139
3003
|
}
|
|
@@ -2193,7 +3057,7 @@ async function loadPlan(directory, cache) {
|
|
|
2193
3057
|
return null;
|
|
2194
3058
|
}
|
|
2195
3059
|
async function savePlanWithAutoAcknowledgedRemovals(directory, plan, source, reason, options) {
|
|
2196
|
-
const existing = await
|
|
3060
|
+
const existing = await _internals8.loadPlanJsonOnly(directory);
|
|
2197
3061
|
const newIds = new Set;
|
|
2198
3062
|
for (const phase of plan.phases) {
|
|
2199
3063
|
for (const task of phase.tasks)
|
|
@@ -2221,7 +3085,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2221
3085
|
const validated = PlanSchema.parse(plan);
|
|
2222
3086
|
if (options?.preserveCompletedStatuses !== false) {
|
|
2223
3087
|
try {
|
|
2224
|
-
const currentPlan2 = await
|
|
3088
|
+
const currentPlan2 = await _internals8.loadPlanJsonOnly(directory);
|
|
2225
3089
|
if (currentPlan2) {
|
|
2226
3090
|
const completedTaskIds = new Set;
|
|
2227
3091
|
for (const phase of currentPlan2.phases) {
|
|
@@ -2243,7 +3107,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2243
3107
|
} catch {}
|
|
2244
3108
|
}
|
|
2245
3109
|
derivePhaseStatusesInPlace(validated);
|
|
2246
|
-
const currentPlan = await
|
|
3110
|
+
const currentPlan = await _internals8.loadPlanJsonOnly(directory);
|
|
2247
3111
|
const planId = derivePlanId(validated);
|
|
2248
3112
|
const planHashForInit = computePlanHash(validated);
|
|
2249
3113
|
if (!await ledgerExists(directory)) {
|
|
@@ -2258,9 +3122,9 @@ async function savePlan(directory, plan, options) {
|
|
|
2258
3122
|
} else {
|
|
2259
3123
|
const existingEvents = await readLedgerEvents(directory);
|
|
2260
3124
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
2261
|
-
const swarmDir2 =
|
|
2262
|
-
const oldLedgerPath =
|
|
2263
|
-
const oldLedgerBackupPath =
|
|
3125
|
+
const swarmDir2 = path9.resolve(directory, ".swarm");
|
|
3126
|
+
const oldLedgerPath = path9.join(swarmDir2, "plan-ledger.jsonl");
|
|
3127
|
+
const oldLedgerBackupPath = path9.join(swarmDir2, `plan-ledger.backup-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
2264
3128
|
let backupExists = false;
|
|
2265
3129
|
if (existsSync5(oldLedgerPath)) {
|
|
2266
3130
|
try {
|
|
@@ -2298,7 +3162,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2298
3162
|
}
|
|
2299
3163
|
}
|
|
2300
3164
|
if (initSucceeded && backupExists) {
|
|
2301
|
-
const archivePath =
|
|
3165
|
+
const archivePath = path9.join(swarmDir2, `plan-ledger.archived-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
2302
3166
|
try {
|
|
2303
3167
|
renameSync4(oldLedgerBackupPath, archivePath);
|
|
2304
3168
|
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived old ledger to ${archivePath} and reinitializing.`);
|
|
@@ -2323,7 +3187,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2323
3187
|
const toRemove = archivedSiblings.slice(0, archivedSiblings.length - MAX_ARCHIVED_SIBLINGS);
|
|
2324
3188
|
for (const file of toRemove) {
|
|
2325
3189
|
try {
|
|
2326
|
-
unlinkSync3(
|
|
3190
|
+
unlinkSync3(path9.join(swarmDir2, file));
|
|
2327
3191
|
} catch {}
|
|
2328
3192
|
}
|
|
2329
3193
|
}
|
|
@@ -2388,7 +3252,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2388
3252
|
expectedHash: currentHash,
|
|
2389
3253
|
planHashAfter: hashAfter,
|
|
2390
3254
|
verifyValid: async () => {
|
|
2391
|
-
const onDisk = await
|
|
3255
|
+
const onDisk = await _internals8.loadPlanJsonOnly(directory);
|
|
2392
3256
|
if (!onDisk)
|
|
2393
3257
|
return true;
|
|
2394
3258
|
for (const p of onDisk.phases) {
|
|
@@ -2426,7 +3290,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2426
3290
|
expectedHash: currentHash,
|
|
2427
3291
|
planHashAfter: hashAfter,
|
|
2428
3292
|
verifyValid: async () => {
|
|
2429
|
-
const onDisk = await
|
|
3293
|
+
const onDisk = await _internals8.loadPlanJsonOnly(directory);
|
|
2430
3294
|
if (!onDisk)
|
|
2431
3295
|
return true;
|
|
2432
3296
|
for (const p of onDisk.phases) {
|
|
@@ -2466,9 +3330,9 @@ async function savePlan(directory, plan, options) {
|
|
|
2466
3330
|
source: "savePlan_manager"
|
|
2467
3331
|
});
|
|
2468
3332
|
}
|
|
2469
|
-
const swarmDir =
|
|
2470
|
-
const planPath =
|
|
2471
|
-
const tempPath =
|
|
3333
|
+
const swarmDir = path9.resolve(directory, ".swarm");
|
|
3334
|
+
const planPath = path9.join(swarmDir, "plan.json");
|
|
3335
|
+
const tempPath = path9.join(swarmDir, `plan.json.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2472
3336
|
try {
|
|
2473
3337
|
await bunWrite(tempPath, JSON.stringify(projectedPlan, null, 2));
|
|
2474
3338
|
renameSync4(tempPath, planPath);
|
|
@@ -2478,7 +3342,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2478
3342
|
} catch {}
|
|
2479
3343
|
}
|
|
2480
3344
|
try {
|
|
2481
|
-
const markerPath =
|
|
3345
|
+
const markerPath = path9.join(swarmDir, ".plan-write-marker");
|
|
2482
3346
|
const inProgressMarker = JSON.stringify({
|
|
2483
3347
|
source: "plan_manager",
|
|
2484
3348
|
timestamp: new Date().toISOString(),
|
|
@@ -2493,8 +3357,8 @@ async function savePlan(directory, plan, options) {
|
|
|
2493
3357
|
const markdown = derivePlanMarkdown(projectedPlan);
|
|
2494
3358
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
2495
3359
|
${markdown}`;
|
|
2496
|
-
const mdPath =
|
|
2497
|
-
const mdTempPath =
|
|
3360
|
+
const mdPath = path9.join(swarmDir, "plan.md");
|
|
3361
|
+
const mdTempPath = path9.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2498
3362
|
try {
|
|
2499
3363
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
2500
3364
|
renameSync4(mdTempPath, mdPath);
|
|
@@ -2515,7 +3379,7 @@ ${markdown}`;
|
|
|
2515
3379
|
} catch {}
|
|
2516
3380
|
}
|
|
2517
3381
|
try {
|
|
2518
|
-
const markerPath =
|
|
3382
|
+
const markerPath = path9.join(swarmDir, ".plan-write-marker");
|
|
2519
3383
|
const tasksCount = projectedPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
2520
3384
|
const marker = JSON.stringify({
|
|
2521
3385
|
source: "plan_manager",
|
|
@@ -2531,14 +3395,14 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
2531
3395
|
const targetPlan = plan ?? await replayFromLedger(directory);
|
|
2532
3396
|
if (!targetPlan)
|
|
2533
3397
|
return null;
|
|
2534
|
-
const swarmDir =
|
|
2535
|
-
const planPath =
|
|
2536
|
-
const mdPath =
|
|
2537
|
-
const tempPlanPath =
|
|
3398
|
+
const swarmDir = path9.join(directory, ".swarm");
|
|
3399
|
+
const planPath = path9.join(swarmDir, "plan.json");
|
|
3400
|
+
const mdPath = path9.join(swarmDir, "plan.md");
|
|
3401
|
+
const tempPlanPath = path9.join(swarmDir, `plan.json.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2538
3402
|
await bunWrite(tempPlanPath, JSON.stringify(targetPlan, null, 2));
|
|
2539
3403
|
renameSync4(tempPlanPath, planPath);
|
|
2540
3404
|
try {
|
|
2541
|
-
const markerPath =
|
|
3405
|
+
const markerPath = path9.join(swarmDir, ".plan-write-marker");
|
|
2542
3406
|
const inProgressMarker = JSON.stringify({
|
|
2543
3407
|
source: "plan_manager",
|
|
2544
3408
|
timestamp: new Date().toISOString(),
|
|
@@ -2553,12 +3417,12 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
2553
3417
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
2554
3418
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
2555
3419
|
${markdown}`;
|
|
2556
|
-
const tempMdPath =
|
|
3420
|
+
const tempMdPath = path9.join(swarmDir, `plan.md.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2557
3421
|
await bunWrite(tempMdPath, markdownWithHash);
|
|
2558
3422
|
renameSync4(tempMdPath, mdPath);
|
|
2559
3423
|
} finally {
|
|
2560
3424
|
try {
|
|
2561
|
-
const markerPath =
|
|
3425
|
+
const markerPath = path9.join(swarmDir, ".plan-write-marker");
|
|
2562
3426
|
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
2563
3427
|
const marker = JSON.stringify({
|
|
2564
3428
|
source: "plan_manager",
|
|
@@ -2621,13 +3485,13 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
2621
3485
|
planHashAfter: hashAfter,
|
|
2622
3486
|
source: "close_terminal"
|
|
2623
3487
|
});
|
|
2624
|
-
const swarmDir =
|
|
2625
|
-
const planPath =
|
|
2626
|
-
const tempPlanPath =
|
|
3488
|
+
const swarmDir = path9.join(directory, ".swarm");
|
|
3489
|
+
const planPath = path9.join(swarmDir, "plan.json");
|
|
3490
|
+
const tempPlanPath = path9.join(swarmDir, `plan.json.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2627
3491
|
await bunWrite(tempPlanPath, JSON.stringify(validated, null, 2));
|
|
2628
3492
|
renameSync4(tempPlanPath, planPath);
|
|
2629
3493
|
try {
|
|
2630
|
-
const markerPath =
|
|
3494
|
+
const markerPath = path9.join(swarmDir, ".plan-write-marker");
|
|
2631
3495
|
const inProgressMarker = JSON.stringify({
|
|
2632
3496
|
source: "plan_manager_close",
|
|
2633
3497
|
timestamp: new Date().toISOString(),
|
|
@@ -2638,17 +3502,17 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
2638
3502
|
await bunWrite(markerPath, inProgressMarker);
|
|
2639
3503
|
} catch {}
|
|
2640
3504
|
try {
|
|
2641
|
-
const mdPath =
|
|
3505
|
+
const mdPath = path9.join(swarmDir, "plan.md");
|
|
2642
3506
|
const contentHash = computePlanContentHash(validated);
|
|
2643
3507
|
const markdown = derivePlanMarkdown(validated);
|
|
2644
3508
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
2645
3509
|
${markdown}`;
|
|
2646
|
-
const mdTempPath =
|
|
3510
|
+
const mdTempPath = path9.join(swarmDir, `plan.md.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2647
3511
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
2648
3512
|
renameSync4(mdTempPath, mdPath);
|
|
2649
3513
|
} finally {
|
|
2650
3514
|
try {
|
|
2651
|
-
const markerPath =
|
|
3515
|
+
const markerPath = path9.join(swarmDir, ".plan-write-marker");
|
|
2652
3516
|
const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
2653
3517
|
const marker = JSON.stringify({
|
|
2654
3518
|
source: "plan_manager_close",
|
|
@@ -2932,15 +3796,15 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
2932
3796
|
|
|
2933
3797
|
// src/evidence/manager.ts
|
|
2934
3798
|
import {
|
|
2935
|
-
mkdirSync as
|
|
3799
|
+
mkdirSync as mkdirSync5,
|
|
2936
3800
|
readdirSync as readdirSync2,
|
|
2937
3801
|
realpathSync,
|
|
2938
3802
|
rmSync,
|
|
2939
3803
|
statSync as statSync2
|
|
2940
3804
|
} from "fs";
|
|
2941
|
-
import * as
|
|
2942
|
-
import * as
|
|
2943
|
-
import * as
|
|
3805
|
+
import * as fs6 from "fs/promises";
|
|
3806
|
+
import * as os4 from "os";
|
|
3807
|
+
import * as path10 from "path";
|
|
2944
3808
|
|
|
2945
3809
|
// src/config/evidence-schema.ts
|
|
2946
3810
|
var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
|
|
@@ -3261,10 +4125,10 @@ var PROJECT_INDICATORS = [
|
|
|
3261
4125
|
function isWeakConfigContainerRoot(directory) {
|
|
3262
4126
|
try {
|
|
3263
4127
|
const resolved = realpathSync(directory);
|
|
3264
|
-
return resolved === realpathSync(
|
|
4128
|
+
return resolved === realpathSync(os4.tmpdir()) || resolved === realpathSync(os4.homedir());
|
|
3265
4129
|
} catch {
|
|
3266
|
-
const resolved =
|
|
3267
|
-
return resolved ===
|
|
4130
|
+
const resolved = path10.resolve(directory);
|
|
4131
|
+
return resolved === path10.resolve(os4.tmpdir()) || resolved === path10.resolve(os4.homedir());
|
|
3268
4132
|
}
|
|
3269
4133
|
}
|
|
3270
4134
|
function hasAccessibleProjectIndicator(directory, options = {}) {
|
|
@@ -3274,7 +4138,7 @@ function hasAccessibleProjectIndicator(directory, options = {}) {
|
|
|
3274
4138
|
continue;
|
|
3275
4139
|
}
|
|
3276
4140
|
try {
|
|
3277
|
-
const indicatorStat = statSync2(
|
|
4141
|
+
const indicatorStat = statSync2(path10.join(directory, indicator));
|
|
3278
4142
|
if (indicatorStat.isFile() || indicatorStat.isDirectory()) {
|
|
3279
4143
|
return true;
|
|
3280
4144
|
}
|
|
@@ -3296,14 +4160,14 @@ function validateProjectRoot(directory) {
|
|
|
3296
4160
|
if (depth >= MAX_DEPTH)
|
|
3297
4161
|
break;
|
|
3298
4162
|
depth++;
|
|
3299
|
-
const parent =
|
|
4163
|
+
const parent = path10.dirname(current);
|
|
3300
4164
|
if (parent === current)
|
|
3301
4165
|
break;
|
|
3302
|
-
if (
|
|
4166
|
+
if (path10.dirname(parent) === parent) {
|
|
3303
4167
|
current = parent;
|
|
3304
4168
|
continue;
|
|
3305
4169
|
}
|
|
3306
|
-
const parentSwarm =
|
|
4170
|
+
const parentSwarm = path10.join(parent, ".swarm");
|
|
3307
4171
|
try {
|
|
3308
4172
|
if (statSync2(parentSwarm).isDirectory()) {
|
|
3309
4173
|
if (hasAccessibleProjectIndicator(parent, {
|
|
@@ -3322,14 +4186,14 @@ function validateProjectRoot(directory) {
|
|
|
3322
4186
|
}
|
|
3323
4187
|
}
|
|
3324
4188
|
async function saveEvidence(directory, taskId, evidence) {
|
|
3325
|
-
|
|
4189
|
+
_internals9.validateProjectRoot(directory);
|
|
3326
4190
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
3327
|
-
const relativePath =
|
|
4191
|
+
const relativePath = path10.join("evidence", sanitizedTaskId, "evidence.json");
|
|
3328
4192
|
validateSwarmPath(directory, relativePath);
|
|
3329
|
-
|
|
4193
|
+
_internals9.validateEvidence(evidence);
|
|
3330
4194
|
return withEvidenceLock(directory, relativePath, "evidence-manager", sanitizedTaskId, async () => {
|
|
3331
4195
|
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
3332
|
-
const evidenceDir =
|
|
4196
|
+
const evidenceDir = path10.dirname(evidencePath);
|
|
3333
4197
|
let bundle;
|
|
3334
4198
|
const existingContent = await readSwarmFileAsync(directory, relativePath);
|
|
3335
4199
|
if (existingContent !== null) {
|
|
@@ -3371,11 +4235,11 @@ async function saveEvidence(directory, taskId, evidence) {
|
|
|
3371
4235
|
if (bundleJson.length > EVIDENCE_MAX_JSON_BYTES) {
|
|
3372
4236
|
throw new Error(`Evidence bundle size (${bundleJson.length} bytes) exceeds maximum (${EVIDENCE_MAX_JSON_BYTES} bytes)`);
|
|
3373
4237
|
}
|
|
3374
|
-
|
|
3375
|
-
const tempPath =
|
|
4238
|
+
mkdirSync5(evidenceDir, { recursive: true });
|
|
4239
|
+
const tempPath = path10.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
3376
4240
|
try {
|
|
3377
4241
|
await bunWrite(tempPath, bundleJson);
|
|
3378
|
-
await
|
|
4242
|
+
await fs6.rename(tempPath, evidencePath);
|
|
3379
4243
|
} catch (error2) {
|
|
3380
4244
|
try {
|
|
3381
4245
|
rmSync(tempPath, { force: true });
|
|
@@ -3416,7 +4280,7 @@ function wrapFlatRetrospective(flatEntry, taskId) {
|
|
|
3416
4280
|
}
|
|
3417
4281
|
async function loadEvidence(directory, taskId) {
|
|
3418
4282
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
3419
|
-
const relativePath =
|
|
4283
|
+
const relativePath = path10.join("evidence", sanitizedTaskId, "evidence.json");
|
|
3420
4284
|
if (relativePath.length > 4096) {
|
|
3421
4285
|
return { status: "not_found" };
|
|
3422
4286
|
}
|
|
@@ -3432,17 +4296,17 @@ async function loadEvidence(directory, taskId) {
|
|
|
3432
4296
|
return { status: "invalid_schema", errors: ["Invalid JSON"] };
|
|
3433
4297
|
}
|
|
3434
4298
|
if (isFlatRetrospective(parsed)) {
|
|
3435
|
-
const wrappedBundle =
|
|
4299
|
+
const wrappedBundle = _internals9.wrapFlatRetrospective(parsed, sanitizedTaskId);
|
|
3436
4300
|
try {
|
|
3437
4301
|
const validated = EvidenceBundleSchema.parse(wrappedBundle);
|
|
3438
4302
|
try {
|
|
3439
4303
|
await withEvidenceLock(directory, relativePath, "evidence-loader", sanitizedTaskId, async () => {
|
|
3440
|
-
const evidenceDir =
|
|
4304
|
+
const evidenceDir = path10.dirname(evidencePath);
|
|
3441
4305
|
const bundleJson = JSON.stringify(validated);
|
|
3442
|
-
const tempPath =
|
|
4306
|
+
const tempPath = path10.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
3443
4307
|
try {
|
|
3444
4308
|
await bunWrite(tempPath, bundleJson);
|
|
3445
|
-
await
|
|
4309
|
+
await fs6.rename(tempPath, evidencePath);
|
|
3446
4310
|
} catch (writeError) {
|
|
3447
4311
|
try {
|
|
3448
4312
|
rmSync(tempPath, { force: true });
|
|
@@ -3484,7 +4348,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
3484
4348
|
}
|
|
3485
4349
|
const taskIds = [];
|
|
3486
4350
|
for (const entry of entries) {
|
|
3487
|
-
const entryPath =
|
|
4351
|
+
const entryPath = path10.join(evidenceBasePath, entry);
|
|
3488
4352
|
try {
|
|
3489
4353
|
const stats = statSync2(entryPath);
|
|
3490
4354
|
if (!stats.isDirectory()) {
|
|
@@ -3502,7 +4366,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
3502
4366
|
}
|
|
3503
4367
|
async function deleteEvidence(directory, taskId) {
|
|
3504
4368
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
3505
|
-
const relativePath =
|
|
4369
|
+
const relativePath = path10.join("evidence", sanitizedTaskId);
|
|
3506
4370
|
const evidenceDir = validateSwarmPath(directory, relativePath);
|
|
3507
4371
|
try {
|
|
3508
4372
|
statSync2(evidenceDir);
|
|
@@ -3518,24 +4382,24 @@ async function deleteEvidence(directory, taskId) {
|
|
|
3518
4382
|
}
|
|
3519
4383
|
}
|
|
3520
4384
|
async function checkRequirementCoverage(phase, directory) {
|
|
3521
|
-
const relativePath =
|
|
3522
|
-
const absolutePath =
|
|
4385
|
+
const relativePath = path10.join("evidence", `req-coverage-phase-${phase}.json`);
|
|
4386
|
+
const absolutePath = path10.resolve(directory, ".swarm", relativePath);
|
|
3523
4387
|
try {
|
|
3524
|
-
await
|
|
4388
|
+
await fs6.access(absolutePath);
|
|
3525
4389
|
return { exists: true, path: absolutePath };
|
|
3526
4390
|
} catch {
|
|
3527
4391
|
return { exists: false, path: absolutePath };
|
|
3528
4392
|
}
|
|
3529
4393
|
}
|
|
3530
4394
|
async function archiveEvidence(directory, maxAgeDays, maxBundles) {
|
|
3531
|
-
const taskIds = await
|
|
4395
|
+
const taskIds = await _internals9.listEvidenceTaskIds(directory);
|
|
3532
4396
|
const cutoffDate = new Date;
|
|
3533
4397
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
3534
4398
|
const cutoffIso = cutoffDate.toISOString();
|
|
3535
4399
|
const archived = [];
|
|
3536
4400
|
const remainingBundles = [];
|
|
3537
4401
|
for (const taskId of taskIds) {
|
|
3538
|
-
const result = await
|
|
4402
|
+
const result = await _internals9.loadEvidence(directory, taskId);
|
|
3539
4403
|
if (result.status !== "found") {
|
|
3540
4404
|
continue;
|
|
3541
4405
|
}
|
|
@@ -3566,7 +4430,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
|
|
|
3566
4430
|
function validateEvidence(evidence) {
|
|
3567
4431
|
return EvidenceSchema.parse(evidence);
|
|
3568
4432
|
}
|
|
3569
|
-
var
|
|
4433
|
+
var _internals9 = {
|
|
3570
4434
|
wrapFlatRetrospective,
|
|
3571
4435
|
loadEvidence,
|
|
3572
4436
|
listEvidenceTaskIds,
|
|
@@ -3685,4 +4549,4 @@ function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
|
|
|
3685
4549
|
return merged;
|
|
3686
4550
|
}
|
|
3687
4551
|
|
|
3688
|
-
export { PlanSchema, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, resetToRemoteBranch, resetToMainAfterMerge, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, computeSpecHash, derivePlanId, computePlanHash, initLedger, appendLedgerEvent, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, RetrospectiveEvidenceSchema, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|
|
4552
|
+
export { PlanSchema, getExecutor, init_executor, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, resetToRemoteBranch, resetToMainAfterMerge, initDurableStatusPath, getAllWorktreeMergeFailures, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, computeSpecHash, derivePlanId, computePlanHash, initLedger, appendLedgerEvent, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, RetrospectiveEvidenceSchema, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|