opencode-swarm 7.144.4 → 7.144.5
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/swarm-pr-review/references/prompt-templates.md +2 -2
- package/dist/background/workspace-snapshot.d.ts +7 -0
- package/dist/cli/{capability-probe-f1paszww.js → capability-probe-976tt4ba.js} +6 -2
- package/dist/cli/{config-doctor-rw9zrjk6.js → config-doctor-m005xfdt.js} +2 -2
- package/dist/cli/{core-zjx9aqaf.js → core-6vdtfb8s.js} +4 -3
- package/dist/cli/{curation-policy-0wbrh11c.js → curation-policy-a511r0qz.js} +4 -4
- package/dist/cli/{curator-g54a7wv0.js → curator-0t87anp5.js} +33 -32
- package/dist/cli/{curator-drift-2jbjv03a.js → curator-drift-ckjm272w.js} +2 -2
- package/dist/cli/curator-llm-factory-j8tk9rxm.js +63 -0
- package/dist/cli/{dispatch-1y1rnv1h.js → dispatch-ndxbsk98.js} +2 -2
- package/dist/cli/{evidence-summary-service-fnr5y54e.js → evidence-summary-service-c22gahjc.js} +13 -12
- package/dist/cli/{gate-evidence-2xt1x07r.js → gate-evidence-hh8kc9wt.js} +7 -6
- package/dist/cli/guardrail-explain-9gjw2dsd.js +64 -0
- package/dist/cli/{guardrail-log-5w4b9zrw.js → guardrail-log-ejf5zjac.js} +4 -4
- package/dist/cli/{hive-promoter-x63ehffd.js → hive-promoter-gkbfpdtv.js} +33 -32
- package/dist/cli/{index-psvxq93b.js → index-1pxqkvvq.js} +2 -2
- package/dist/cli/{index-q67jb94n.js → index-3qdzpv91.js} +1 -1
- package/dist/cli/{index-9qq7c18c.js → index-58cvm3dw.js} +36 -1
- package/dist/cli/{index-p7cm3gtj.js → index-5wenr2vs.js} +35 -34
- package/dist/cli/{index-nckg4gxb.js → index-63tgh0cn.js} +15 -4
- package/dist/cli/{index-03pv47h2.js → index-641yjpk8.js} +6 -6
- package/dist/cli/{index-1csyxv0f.js → index-6z5jtf7d.js} +2 -2
- package/dist/cli/{index-z6v7kwpa.js → index-7e4q41nb.js} +1 -1
- package/dist/cli/{index-8j0va7jp.js → index-855hseq4.js} +43 -22
- package/dist/cli/index-9e6fdz2h.js +282 -0
- package/dist/cli/{index-mazgg5s2.js → index-9s8drsf9.js} +1 -1
- package/dist/cli/{index-cz25e8xp.js → index-a1hxejxc.js} +166 -34
- package/dist/cli/{index-b6e016tv.js → index-abtrcs16.js} +1 -1
- package/dist/cli/{index-8fwhhayc.js → index-b44qqqt2.js} +27 -18
- package/dist/cli/{index-k3cxyk66.js → index-cwtpqm5f.js} +1 -1
- package/dist/cli/{index-h98t1m88.js → index-dc04v786.js} +29 -1
- package/dist/cli/{index-d12jd5mn.js → index-dtbzyr3s.js} +1 -1
- package/dist/cli/{index-x6tp0hfr.js → index-ea09xax6.js} +5 -5
- package/dist/cli/{index-74hpm6sh.js → index-f36ybbsv.js} +12 -6
- package/dist/cli/{index-rn0eq85g.js → index-fv0s19g9.js} +1 -1
- package/dist/cli/{index-btmsqv7p.js → index-fv2pzwnp.js} +5 -4
- package/dist/cli/{index-ywn7vtxp.js → index-h1p08s3n.js} +1 -1
- package/dist/cli/{index-bt9aa01f.js → index-hkdgkkhb.js} +1 -1
- package/dist/cli/{index-y8j8ehc8.js → index-kdak6s63.js} +11 -5
- package/dist/cli/{index-az8etqpp.js → index-kh0jt9pg.js} +298 -1179
- package/dist/cli/{index-pw83zgny.js → index-mkmcz6x8.js} +3 -3
- package/dist/cli/{index-jabkn643.js → index-n4hwcsbd.js} +2 -2
- package/dist/cli/{index-2m7mavaz.js → index-rdgtfj74.js} +2 -2
- package/dist/cli/{index-yts89pqg.js → index-rnyt30tm.js} +4 -4
- package/dist/cli/{index-dty5qgk2.js → index-vdha477y.js} +1 -1
- package/dist/cli/{index-4w5qdbk7.js → index-wbrqvm2g.js} +6 -6
- package/dist/cli/{index-fqcjragy.js → index-wknz5c8q.js} +1 -1
- package/dist/cli/{index-zb0xcz9t.js → index-wm6f7t7w.js} +3 -3
- package/dist/cli/{index-q4ed1jk7.js → index-wmsehjq6.js} +1994 -1333
- package/dist/cli/{index-4nt52yr9.js → index-zj6t9zgx.js} +173 -25
- package/dist/cli/index-zy7f84cs.js +1210 -0
- package/dist/cli/index.d.ts +11 -1
- package/dist/cli/index.js +61 -39
- package/dist/cli/{knowledge-escalator-ypg14d5r.js → knowledge-escalator-1d79m1tr.js} +12 -11
- package/dist/cli/{knowledge-events-nkrym4wb.js → knowledge-events-ces29ncg.js} +10 -9
- package/dist/cli/{knowledge-link-e4qgqmpx.js → knowledge-link-4cg4776a.js} +3 -3
- package/dist/cli/{knowledge-store-vh3nhtg2.js → knowledge-store-pe8webc2.js} +4 -4
- package/dist/cli/{knowledge-validator-jrxvexv6.js → knowledge-validator-4yjf1f7k.js} +6 -6
- package/dist/cli/{pending-delegations-pw79c8ds.js → pending-delegations-9h1jmtw1.js} +3 -3
- package/dist/cli/{pr-subscriptions-fk628bhc.js → pr-subscriptions-8pg34pev.js} +3 -3
- package/dist/cli/{runner-b02sz2vx.js → runner-cqecp1na.js} +4 -4
- package/dist/cli/{scan-cursor-fp7w5jmc.js → scan-cursor-d6exjmqj.js} +7 -5
- package/dist/cli/{schema-rptwqbm4.js → schema-2esybebd.js} +3 -1
- package/dist/cli/{scope-persistence-0bkaxawa.js → scope-persistence-ej59x0yc.js} +7 -6
- package/dist/cli/{skill-generator-nh8b4gjv.js → skill-generator-yqta3yrg.js} +14 -13
- package/dist/cli/{workspace-snapshot-w3tg4ne5.js → workspace-snapshot-d89zm6wm.js} +5 -2
- package/dist/cli/{worktree-collision-ownership-hqd9ypsz.js → worktree-collision-ownership-vy0kxa98.js} +26 -19
- package/dist/cli/{worktree-isolation-canf3rj8.js → worktree-isolation-rwq0kke7.js} +33 -32
- package/dist/cli/{worktree-provisioning-owner-g24xd417.js → worktree-provisioning-owner-zzkjycp1.js} +3 -3
- package/dist/commands/ci-simulate.d.ts +2 -0
- package/dist/config/cache-paths.d.ts +57 -0
- package/dist/config/evidence-schema.d.ts +3 -3
- package/dist/config/schema.d.ts +9 -0
- package/dist/git/branch.d.ts +2 -0
- package/dist/git/pr.d.ts +2 -0
- package/dist/hooks/delegation-gate/worktree-isolation.d.ts +7 -0
- package/dist/hooks/diff-scope.d.ts +7 -0
- package/dist/hooks/review-receipt-scope.d.ts +7 -0
- package/dist/hooks/reviewer-scope-lifecycle.d.ts +9 -0
- package/dist/hooks/semantic-diff-injection.d.ts +8 -0
- package/dist/index.js +599 -589
- package/dist/mutation/engine.d.ts +11 -0
- package/dist/review/diff-source.d.ts +2 -0
- package/dist/sandbox/capability-probe.d.ts +22 -0
- package/dist/sandbox/executor.d.ts +15 -0
- package/dist/sandbox/linux/bubblewrap-executor.d.ts +6 -0
- package/dist/sandbox/macos/sandbox-exec-executor.d.ts +41 -0
- package/dist/tools/checkpoint.d.ts +11 -0
- package/dist/tools/complexity-hotspots.d.ts +15 -0
- package/dist/tools/diff-summary.d.ts +12 -0
- package/dist/tools/dispatch-lanes.d.ts +8 -0
- package/dist/tools/gh-evidence.d.ts +17 -1
- package/dist/tools/knowledge-receipt.d.ts +1 -1
- package/dist/tools/pkg-audit.d.ts +13 -0
- package/dist/tools/prepare-pr-workflow-checkout.d.ts +1 -0
- package/dist/utils/bun-compat.d.ts +80 -3
- package/dist/utils/git-binary-missing-error.d.ts +80 -2
- package/dist/utils/git-executable.d.ts +179 -0
- package/dist/utils/gitignore-warning.d.ts +10 -0
- package/dist/workflow/coder-settlement.d.ts +18 -0
- package/dist/worktree/core.d.ts +8 -0
- package/dist/worktree/merge.d.ts +33 -0
- package/package.json +3 -2
- package/dist/cli/curator-llm-factory-xgb4pxmm.js +0 -62
- package/dist/cli/guardrail-explain-6wcgr9tz.js +0 -63
- package/dist/cli/index-q1exe2b3.js +0 -256
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
EvaluationRunV1Schema,
|
|
7
7
|
GateAuditResultV1Schema,
|
|
8
8
|
getProtectedEvaluationRunIds
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-wknz5c8q.js";
|
|
10
10
|
import {
|
|
11
11
|
readEffectiveSpecSync
|
|
12
12
|
} from "./index-9ss2m4rs.js";
|
|
@@ -14,11 +14,16 @@ import {
|
|
|
14
14
|
getWorktreeMergeFailure
|
|
15
15
|
} from "./index-zsrtt42s.js";
|
|
16
16
|
import {
|
|
17
|
+
GitBinaryMissingError,
|
|
18
|
+
GitSpawnCwdError,
|
|
19
|
+
isGitBinaryMissing,
|
|
20
|
+
isSpawnCwdMissing,
|
|
21
|
+
isSpawnCwdUnreadable,
|
|
17
22
|
isValidTaskId,
|
|
18
23
|
readTaskEvidence,
|
|
19
24
|
readTaskEvidenceRaw,
|
|
20
25
|
sanitizeTaskId
|
|
21
|
-
} from "./index-
|
|
26
|
+
} from "./index-a1hxejxc.js";
|
|
22
27
|
import {
|
|
23
28
|
LedgerStaleWriterError,
|
|
24
29
|
appendLedgerEvent,
|
|
@@ -38,14 +43,11 @@ import {
|
|
|
38
43
|
replayFromLedgerWithStatus,
|
|
39
44
|
takeSnapshotEvent,
|
|
40
45
|
takeSnapshotWithRetry
|
|
41
|
-
} from "./index-
|
|
46
|
+
} from "./index-7e4q41nb.js";
|
|
42
47
|
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
init_runner_client,
|
|
47
|
-
probe
|
|
48
|
-
} from "./index-q1exe2b3.js";
|
|
48
|
+
init_executor,
|
|
49
|
+
isValidEnvKey
|
|
50
|
+
} from "./index-zy7f84cs.js";
|
|
49
51
|
import {
|
|
50
52
|
EVIDENCE_MAX_JSON_BYTES,
|
|
51
53
|
EvidenceBundleSchema,
|
|
@@ -54,10 +56,16 @@ import {
|
|
|
54
56
|
import {
|
|
55
57
|
readSwarmFileAsync,
|
|
56
58
|
validateSwarmPath
|
|
57
|
-
} from "./index-
|
|
59
|
+
} from "./index-fv0s19g9.js";
|
|
58
60
|
import {
|
|
59
61
|
PlanSchema
|
|
60
|
-
} from "./index-
|
|
62
|
+
} from "./index-58cvm3dw.js";
|
|
63
|
+
import {
|
|
64
|
+
GIT_BINARY_ENV_VAR,
|
|
65
|
+
describeGitResolution,
|
|
66
|
+
init_git_executable,
|
|
67
|
+
resolveGitExecutable
|
|
68
|
+
} from "./index-9e6fdz2h.js";
|
|
61
69
|
import {
|
|
62
70
|
invalidateCachedArtifact,
|
|
63
71
|
readCachedParsedFile
|
|
@@ -78,903 +86,13 @@ import {
|
|
|
78
86
|
bunHash,
|
|
79
87
|
bunWrite,
|
|
80
88
|
mergeEnvForChild
|
|
81
|
-
} from "./index-
|
|
89
|
+
} from "./index-zj6t9zgx.js";
|
|
82
90
|
import {
|
|
83
91
|
criticalWarn,
|
|
84
92
|
error,
|
|
85
93
|
init_logger,
|
|
86
94
|
warn
|
|
87
95
|
} from "./index-zgwm4ryv.js";
|
|
88
|
-
import {
|
|
89
|
-
__esm,
|
|
90
|
-
__export,
|
|
91
|
-
__require,
|
|
92
|
-
__toCommonJS
|
|
93
|
-
} from "./index-a76rekgs.js";
|
|
94
|
-
|
|
95
|
-
// src/sandbox/linux/bubblewrap-executor.ts
|
|
96
|
-
import { spawnSync } from "child_process";
|
|
97
|
-
function probeBwrap() {
|
|
98
|
-
try {
|
|
99
|
-
const result = spawnSync("bwrap", ["--version"], {
|
|
100
|
-
windowsHide: true,
|
|
101
|
-
encoding: "utf-8",
|
|
102
|
-
timeout: 5000,
|
|
103
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
104
|
-
});
|
|
105
|
-
if (result.error) {
|
|
106
|
-
const code = result.error.code;
|
|
107
|
-
if (code && BWRAP_UNAVAILABLE_CODES.has(code)) {
|
|
108
|
-
warn(`Sandbox disabled: bwrap error (${code}). Falling through to tool-layer enforcement.`);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
warn(`Sandbox disabled: bwrap spawn error (${result.error.message}). Falling through to tool-layer enforcement.`);
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
return result.status === BWRAP_VERSION_EXIT && result.stdout.trim().length > 0;
|
|
115
|
-
} catch (err) {
|
|
116
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
-
warn(`Sandbox disabled: probe threw (${message}). Falling through to tool-layer enforcement.`);
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function shellEscape(s) {
|
|
122
|
-
return s.replace(/'/g, "'\\''");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
class BubblewrapSandboxExecutor {
|
|
126
|
-
mechanism = "Bubblewrap";
|
|
127
|
-
_scopePaths;
|
|
128
|
-
_tempDir;
|
|
129
|
-
_available;
|
|
130
|
-
_disabledReason;
|
|
131
|
-
constructor(scopePaths = [], tempDir) {
|
|
132
|
-
this._scopePaths = scopePaths;
|
|
133
|
-
this._tempDir = tempDir;
|
|
134
|
-
this._available = false;
|
|
135
|
-
this._disabledReason = null;
|
|
136
|
-
try {
|
|
137
|
-
if (!_internals2.probeBwrap()) {
|
|
138
|
-
this._disabledReason = "bwrap not available or not functional";
|
|
139
|
-
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
140
|
-
} else {
|
|
141
|
-
this._available = true;
|
|
142
|
-
}
|
|
143
|
-
} catch (err) {
|
|
144
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
-
this._disabledReason = `constructor threw: ${message}`;
|
|
146
|
-
this._available = false;
|
|
147
|
-
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
isAvailable() {
|
|
151
|
-
return this._available;
|
|
152
|
-
}
|
|
153
|
-
disable(reason) {
|
|
154
|
-
this._available = false;
|
|
155
|
-
this._disabledReason = reason;
|
|
156
|
-
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
157
|
-
}
|
|
158
|
-
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
159
|
-
if (!this._available) {
|
|
160
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
161
|
-
}
|
|
162
|
-
const temp = tempDir ?? this._tempDir ?? "/tmp";
|
|
163
|
-
const allScopes = [...this._scopePaths, ...scopePaths];
|
|
164
|
-
const bindArgs = allScopes.flatMap((p) => [
|
|
165
|
-
"--bind",
|
|
166
|
-
`'${shellEscape(p)}'`,
|
|
167
|
-
`'${shellEscape(p)}'`
|
|
168
|
-
]);
|
|
169
|
-
const envArgs = [];
|
|
170
|
-
if (envOverrides) {
|
|
171
|
-
for (const [key, value] of Object.entries(envOverrides)) {
|
|
172
|
-
if (!isValidEnvKey(key)) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (value === null) {
|
|
176
|
-
envArgs.push("--unsetenv", key);
|
|
177
|
-
} else {
|
|
178
|
-
envArgs.push("--setenv", key, value);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
const args = [
|
|
183
|
-
"--unshare-user",
|
|
184
|
-
"--unshare-net",
|
|
185
|
-
"--unshare-ipc",
|
|
186
|
-
"--die-with-parent",
|
|
187
|
-
"--new-session",
|
|
188
|
-
"--cap-drop",
|
|
189
|
-
"ALL",
|
|
190
|
-
...bindArgs,
|
|
191
|
-
"--dev",
|
|
192
|
-
"/dev",
|
|
193
|
-
"--size",
|
|
194
|
-
String(TMPFS_SIZE_BYTES),
|
|
195
|
-
"--tmpfs",
|
|
196
|
-
`'${shellEscape(temp)}'`,
|
|
197
|
-
"--ro-bind",
|
|
198
|
-
"/etc",
|
|
199
|
-
"/etc",
|
|
200
|
-
"--ro-bind",
|
|
201
|
-
"/usr",
|
|
202
|
-
"/usr",
|
|
203
|
-
"--ro-bind",
|
|
204
|
-
"/lib",
|
|
205
|
-
"/lib",
|
|
206
|
-
"--ro-bind",
|
|
207
|
-
"/lib64",
|
|
208
|
-
"/lib64",
|
|
209
|
-
"--proc",
|
|
210
|
-
"/proc",
|
|
211
|
-
"--unshare-pid",
|
|
212
|
-
...envArgs,
|
|
213
|
-
"--",
|
|
214
|
-
"bash",
|
|
215
|
-
"-c",
|
|
216
|
-
`'${shellEscape(command)}'`
|
|
217
|
-
];
|
|
218
|
-
return `bwrap ${args.join(" ")}`;
|
|
219
|
-
}
|
|
220
|
-
getEnvOverrides() {
|
|
221
|
-
return {};
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
var BWRAP_VERSION_EXIT = 0, BWRAP_UNAVAILABLE_CODES, TMPFS_SIZE_BYTES = 524288000, _internals2;
|
|
225
|
-
var init_bubblewrap_executor = __esm(() => {
|
|
226
|
-
init_logger();
|
|
227
|
-
init_executor();
|
|
228
|
-
BWRAP_UNAVAILABLE_CODES = new Set(["ENOENT", "EACCES", "ENOSPC"]);
|
|
229
|
-
_internals2 = {
|
|
230
|
-
probeBwrap
|
|
231
|
-
};
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// src/sandbox/executors/bubblewrap.ts
|
|
235
|
-
var exports_bubblewrap = {};
|
|
236
|
-
__export(exports_bubblewrap, {
|
|
237
|
-
BubblewrapSandboxExecutor: () => BubblewrapSandboxExecutor
|
|
238
|
-
});
|
|
239
|
-
var init_bubblewrap = __esm(() => {
|
|
240
|
-
init_bubblewrap_executor();
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// src/sandbox/macos/sandbox-exec-executor.ts
|
|
244
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
245
|
-
import { mkdtempSync, writeFileSync } from "fs";
|
|
246
|
-
import * as os from "os";
|
|
247
|
-
import * as path from "path";
|
|
248
|
-
function probeSandboxExec() {
|
|
249
|
-
try {
|
|
250
|
-
const result = spawnSync2("sandbox-exec", ["--version"], {
|
|
251
|
-
windowsHide: true,
|
|
252
|
-
encoding: "utf-8",
|
|
253
|
-
timeout: 5000,
|
|
254
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
255
|
-
});
|
|
256
|
-
if (result.error) {
|
|
257
|
-
const code = result.error.code;
|
|
258
|
-
if (code && SANDBOX_UNAVAILABLE_CODES.has(code)) {
|
|
259
|
-
warn(`Sandbox disabled: sandbox-exec error (${code}). Falling through to tool-layer enforcement.`);
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
warn(`Sandbox disabled: spawn error (${result.error.message}). Falling through to tool-layer enforcement.`);
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
return result.status === 0;
|
|
266
|
-
} catch (err) {
|
|
267
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
268
|
-
warn(`Sandbox disabled: probe threw (${message}). Falling through to tool-layer enforcement.`);
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function shellEscape2(s) {
|
|
273
|
-
return s.replace(/'/g, "'\\''");
|
|
274
|
-
}
|
|
275
|
-
function sbplEscapePath(path2) {
|
|
276
|
-
const withoutControlChars = path2.split("").filter((ch) => {
|
|
277
|
-
const cp = ch.codePointAt(0);
|
|
278
|
-
return cp >= 32 && cp !== 127;
|
|
279
|
-
}).join("");
|
|
280
|
-
return withoutControlChars.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
281
|
-
}
|
|
282
|
-
function buildSandboxProfile(scopePaths, tempDir, envOverrides) {
|
|
283
|
-
const rwPaths = [...scopePaths];
|
|
284
|
-
if (tempDir) {
|
|
285
|
-
rwPaths.push(tempDir);
|
|
286
|
-
}
|
|
287
|
-
const rwAllowLines = rwPaths.map((p) => `(allow file-write* (subpath "${sbplEscapePath(p)}"))`).join(`
|
|
288
|
-
`);
|
|
289
|
-
const envLines = [];
|
|
290
|
-
if (envOverrides) {
|
|
291
|
-
for (const [key, value] of Object.entries(envOverrides)) {
|
|
292
|
-
if (!isValidEnvKey(key)) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
if (value === null) {
|
|
296
|
-
envLines.push(`(unsetenv ${key})`);
|
|
297
|
-
} else {
|
|
298
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
299
|
-
envLines.push(`(setenv ${key} "${escaped}")`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
const profile = `(version 1)
|
|
304
|
-
(allow default)
|
|
305
|
-
(allow file-read* (subpath "/usr"))
|
|
306
|
-
(allow file-read* (subpath "/bin"))
|
|
307
|
-
(allow file-read* (subpath "/sbin"))
|
|
308
|
-
(allow file-read* (subpath "/lib"))
|
|
309
|
-
(allow file-read* (subpath "/lib64"))
|
|
310
|
-
(deny file-write*)
|
|
311
|
-
${rwAllowLines}
|
|
312
|
-
${envLines.join(`
|
|
313
|
-
`)}`;
|
|
314
|
-
return profile;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
class MacOSSandboxExecutor {
|
|
318
|
-
mechanism = "sandbox-exec";
|
|
319
|
-
_scopePaths;
|
|
320
|
-
_tempDir;
|
|
321
|
-
_available;
|
|
322
|
-
_disabledReason;
|
|
323
|
-
constructor(scopePaths = [], tempDir) {
|
|
324
|
-
if (process.platform !== "darwin") {
|
|
325
|
-
throw new Error("MacOSSandboxExecutor not yet implemented");
|
|
326
|
-
}
|
|
327
|
-
this._scopePaths = scopePaths;
|
|
328
|
-
this._tempDir = tempDir;
|
|
329
|
-
this._available = false;
|
|
330
|
-
this._disabledReason = null;
|
|
331
|
-
try {
|
|
332
|
-
if (!_internals3.probeSandboxExec()) {
|
|
333
|
-
this._disabledReason = "sandbox-exec not available or not functional";
|
|
334
|
-
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
335
|
-
} else {
|
|
336
|
-
this._available = true;
|
|
337
|
-
}
|
|
338
|
-
} catch (err) {
|
|
339
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
340
|
-
this._disabledReason = `constructor threw: ${message}`;
|
|
341
|
-
this._available = false;
|
|
342
|
-
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
isAvailable() {
|
|
346
|
-
return this._available;
|
|
347
|
-
}
|
|
348
|
-
disable(reason) {
|
|
349
|
-
this._disabledReason = reason;
|
|
350
|
-
this._available = false;
|
|
351
|
-
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
352
|
-
}
|
|
353
|
-
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
354
|
-
if (!this._available) {
|
|
355
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
356
|
-
}
|
|
357
|
-
if (!_internals3.probeSandboxExec()) {
|
|
358
|
-
this._available = false;
|
|
359
|
-
this._disabledReason = "sandbox-exec became unavailable between calls";
|
|
360
|
-
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
361
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
362
|
-
}
|
|
363
|
-
const temp = tempDir ?? this._tempDir ?? os.tmpdir();
|
|
364
|
-
const allScopes = [...this._scopePaths, ...scopePaths];
|
|
365
|
-
const profile = buildSandboxProfile(allScopes, temp, envOverrides);
|
|
366
|
-
let profilePath;
|
|
367
|
-
try {
|
|
368
|
-
const profileDir = mkdtempSync(path.join(os.tmpdir(), "sandbox-"));
|
|
369
|
-
profilePath = path.join(profileDir, `profile-${process.pid}-${Date.now()}.sb`);
|
|
370
|
-
writeFileSync(profilePath, profile, { mode: 384 });
|
|
371
|
-
} catch (err) {
|
|
372
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
373
|
-
warn(`Sandbox disabled: failed to write profile (${message}). Falling through to tool-layer enforcement.`);
|
|
374
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
375
|
-
}
|
|
376
|
-
const escapedCommand = shellEscape2(command);
|
|
377
|
-
const escapedProfilePath = shellEscape2(profilePath);
|
|
378
|
-
return `sandbox-exec -f '${escapedProfilePath}' bash -c '${escapedCommand}'`;
|
|
379
|
-
}
|
|
380
|
-
getEnvOverrides() {
|
|
381
|
-
return {
|
|
382
|
-
DYLD_INSERT_LIBRARIES: null,
|
|
383
|
-
DYLD_LIBRARY_PATH: null,
|
|
384
|
-
DYLD_FRAMEWORK_PATH: null,
|
|
385
|
-
DYLD_ROOT_PATH: null,
|
|
386
|
-
PATH: "/usr/bin:/bin:/usr/sbin:/sbin"
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
var SANDBOX_UNAVAILABLE_CODES, _internals3;
|
|
391
|
-
var init_sandbox_exec_executor = __esm(() => {
|
|
392
|
-
init_logger();
|
|
393
|
-
init_executor();
|
|
394
|
-
SANDBOX_UNAVAILABLE_CODES = new Set(["ENOENT", "EACCES", "ENOSPC"]);
|
|
395
|
-
_internals3 = {
|
|
396
|
-
probeSandboxExec,
|
|
397
|
-
buildSandboxProfile
|
|
398
|
-
};
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// src/sandbox/executors/macos.ts
|
|
402
|
-
var exports_macos = {};
|
|
403
|
-
__export(exports_macos, {
|
|
404
|
-
MacOSSandboxExecutor: () => MacOSSandboxExecutor
|
|
405
|
-
});
|
|
406
|
-
var init_macos = __esm(() => {
|
|
407
|
-
init_sandbox_exec_executor();
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// src/sandbox/win32/edge-cases.ts
|
|
411
|
-
function detectPowerShellEscape(command) {
|
|
412
|
-
if (process.platform !== "win32")
|
|
413
|
-
return false;
|
|
414
|
-
const encodedCommandPattern = /-EncodedCommand/i;
|
|
415
|
-
if (encodedCommandPattern.test(command)) {
|
|
416
|
-
return true;
|
|
417
|
-
}
|
|
418
|
-
const encodedBase64Pattern = /-Enc(odedCommand)?\s+[A-Za-z0-9+/=]+/i;
|
|
419
|
-
if (encodedBase64Pattern.test(command)) {
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
const psExePattern = /powershell\.exe?\s+.*-(?:e(?:n(?:c(?:odedCommand)?)?)?|wmi|remote)/i;
|
|
423
|
-
if (psExePattern.test(command)) {
|
|
424
|
-
return true;
|
|
425
|
-
}
|
|
426
|
-
const iexPattern = /\biex\b/i;
|
|
427
|
-
if (iexPattern.test(command)) {
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
const invokeExpressionPattern = /Invoke-Expression/i;
|
|
431
|
-
if (invokeExpressionPattern.test(command)) {
|
|
432
|
-
return true;
|
|
433
|
-
}
|
|
434
|
-
const invokeCommandRemotePattern = /Invoke-Command\s+.*-ComputerName/i;
|
|
435
|
-
if (invokeCommandRemotePattern.test(command)) {
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
const psCommandPattern = /powershell.*-Command\s+["'].*\$/i;
|
|
439
|
-
if (psCommandPattern.test(command)) {
|
|
440
|
-
return true;
|
|
441
|
-
}
|
|
442
|
-
const execPolicyPattern = /-ExecutionPolicy\s+(?:Bypass|RemoteSigned|Unrestricted)/i;
|
|
443
|
-
if (execPolicyPattern.test(command)) {
|
|
444
|
-
return true;
|
|
445
|
-
}
|
|
446
|
-
const noProfilePattern = /-NoProfile\s+-/i;
|
|
447
|
-
if (noProfilePattern.test(command)) {
|
|
448
|
-
return true;
|
|
449
|
-
}
|
|
450
|
-
const automationPattern = /New-Object\s+.*Management\.Automation/i;
|
|
451
|
-
if (automationPattern.test(command)) {
|
|
452
|
-
return true;
|
|
453
|
-
}
|
|
454
|
-
const downloadPattern = /\[System\.Net\.(?:WebClient|Http).*\]\.(?:DownloadString|DownloadFile)/i;
|
|
455
|
-
if (downloadPattern.test(command)) {
|
|
456
|
-
return true;
|
|
457
|
-
}
|
|
458
|
-
const hiddenWindowPattern = /-WindowStyle\s+Hidden/i;
|
|
459
|
-
if (hiddenWindowPattern.test(command)) {
|
|
460
|
-
return true;
|
|
461
|
-
}
|
|
462
|
-
const scriptInjectionPattern = /\$(?:env|input|host)\b.*\|/i;
|
|
463
|
-
if (scriptInjectionPattern.test(command)) {
|
|
464
|
-
return true;
|
|
465
|
-
}
|
|
466
|
-
const obfuscatedIexPattern = /IEX|i:eX/i;
|
|
467
|
-
if (obfuscatedIexPattern.test(command)) {
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
var init_edge_cases = () => {};
|
|
473
|
-
|
|
474
|
-
// src/sandbox/win32/restricted-environment-executor.ts
|
|
475
|
-
import { Buffer as Buffer2 } from "buffer";
|
|
476
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
477
|
-
import * as os2 from "os";
|
|
478
|
-
import * as path2 from "path";
|
|
479
|
-
function containsUnsafePathSyntax(value) {
|
|
480
|
-
return [...value].some((character) => character.charCodeAt(0) < 32 || UNSAFE_PATH_CHARACTERS.has(character));
|
|
481
|
-
}
|
|
482
|
-
function getSafeSystemRoot() {
|
|
483
|
-
const raw = process.env.SystemRoot;
|
|
484
|
-
if (typeof raw !== "string" || !/^[A-Za-z]:[\\/]/.test(raw) || containsUnsafePathSyntax(raw.slice(2)) || raw.slice(3).split(/[\\/]+/).some((segment) => segment === "." || segment === "..")) {
|
|
485
|
-
return DEFAULT_SYSTEM_ROOT;
|
|
486
|
-
}
|
|
487
|
-
const normalized = path2.win32.normalize(raw);
|
|
488
|
-
return normalized.length > 3 ? normalized.replace(/\\+$/, "") : normalized;
|
|
489
|
-
}
|
|
490
|
-
function getWindowsExecutablePaths() {
|
|
491
|
-
const systemRoot = getSafeSystemRoot();
|
|
492
|
-
return {
|
|
493
|
-
systemRoot,
|
|
494
|
-
cmdExe: path2.win32.join(systemRoot, "System32", "cmd.exe"),
|
|
495
|
-
powerShellExe: path2.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
function probeWindowsSandbox() {
|
|
499
|
-
try {
|
|
500
|
-
const result = spawnSync3(getWindowsExecutablePaths().cmdExe, ["/d", "/s", "/c", "echo ok"], {
|
|
501
|
-
cwd: os2.tmpdir(),
|
|
502
|
-
windowsHide: true,
|
|
503
|
-
encoding: "utf-8",
|
|
504
|
-
timeout: 5000,
|
|
505
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
506
|
-
});
|
|
507
|
-
if (result.error) {
|
|
508
|
-
const code = result.error.code;
|
|
509
|
-
if (code && SANDBOX_UNAVAILABLE_CODES2.has(code)) {
|
|
510
|
-
warn(`Sandbox disabled: spawn error (${code}). Falling through to tool-layer enforcement.`);
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
warn(`Sandbox disabled: spawn error (${result.error.message}). Falling through to tool-layer enforcement.`);
|
|
514
|
-
return false;
|
|
515
|
-
}
|
|
516
|
-
return result.status === 0;
|
|
517
|
-
} catch (err) {
|
|
518
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
519
|
-
warn(`Sandbox disabled: probe threw (${message}). Falling through to tool-layer enforcement.`);
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
function psSingleQuoteEscape(s) {
|
|
524
|
-
return s.replace(/'/g, "''");
|
|
525
|
-
}
|
|
526
|
-
function normalizeInheritedPathEntry(rawEntry) {
|
|
527
|
-
const entry = rawEntry.trim();
|
|
528
|
-
if (!/^[A-Za-z]:[\\/]/.test(entry) || containsUnsafePathSyntax(entry.slice(2)) || entry.slice(3).split(/[\\/]+/).some((segment) => segment === "." || segment === "..")) {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
const normalized = path2.win32.normalize(entry);
|
|
532
|
-
return normalized.length > 3 ? normalized.replace(/\\+$/, "") : normalized;
|
|
533
|
-
}
|
|
534
|
-
function getSafeWindowsPath() {
|
|
535
|
-
const { systemRoot } = getWindowsExecutablePaths();
|
|
536
|
-
const candidates = [
|
|
537
|
-
path2.win32.join(systemRoot, "System32"),
|
|
538
|
-
systemRoot,
|
|
539
|
-
...(process.env.PATH ?? "").split(";")
|
|
540
|
-
];
|
|
541
|
-
const seen = new Set;
|
|
542
|
-
const safeEntries = [];
|
|
543
|
-
for (const candidate of candidates) {
|
|
544
|
-
const normalized = normalizeInheritedPathEntry(candidate);
|
|
545
|
-
if (!normalized)
|
|
546
|
-
continue;
|
|
547
|
-
const identity = normalized.toLowerCase();
|
|
548
|
-
if (seen.has(identity))
|
|
549
|
-
continue;
|
|
550
|
-
seen.add(identity);
|
|
551
|
-
safeEntries.push(normalized);
|
|
552
|
-
}
|
|
553
|
-
return safeEntries.join(";");
|
|
554
|
-
}
|
|
555
|
-
function isPowerShellNativeCommand(command) {
|
|
556
|
-
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());
|
|
557
|
-
}
|
|
558
|
-
function isSafePsCommandBody(command) {
|
|
559
|
-
return !/[;&|`$()\r\n]/.test(command);
|
|
560
|
-
}
|
|
561
|
-
function isPathInScopes(command, scopes) {
|
|
562
|
-
if (scopes.length === 0)
|
|
563
|
-
return true;
|
|
564
|
-
const pathPattern = /[A-Za-z]:(?:[^\\/:*?"<>|\r\n]+(?:\\[^\\/:*?"<>|\r\n]+)*)/g;
|
|
565
|
-
const paths = command.match(pathPattern) || [];
|
|
566
|
-
if (paths.length === 0)
|
|
567
|
-
return true;
|
|
568
|
-
const normalizedPaths = paths.map((p) => path2.win32.resolve(p));
|
|
569
|
-
const normalizedScopes = scopes.map((s) => s.toLowerCase().replace(/\\+$/, ""));
|
|
570
|
-
return normalizedPaths.every((p) => {
|
|
571
|
-
const lower = p.toLowerCase();
|
|
572
|
-
return normalizedScopes.some((scope) => lower.startsWith(scope));
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
class WindowsSandboxExecutor {
|
|
577
|
-
mechanism = "powershell-wrapper";
|
|
578
|
-
_scopePaths;
|
|
579
|
-
_tempDir;
|
|
580
|
-
_available;
|
|
581
|
-
_disabled;
|
|
582
|
-
_disabledReason;
|
|
583
|
-
constructor(scopePaths = [], tempDir) {
|
|
584
|
-
this._scopePaths = scopePaths;
|
|
585
|
-
this._tempDir = tempDir;
|
|
586
|
-
this._available = false;
|
|
587
|
-
this._disabled = false;
|
|
588
|
-
this._disabledReason = null;
|
|
589
|
-
try {
|
|
590
|
-
if (!_internals4.probeWindowsSandbox()) {
|
|
591
|
-
this._available = false;
|
|
592
|
-
this._disabledReason = "Windows sandbox not available or not functional";
|
|
593
|
-
warn(`Sandbox unavailable: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
594
|
-
} else {
|
|
595
|
-
this._available = true;
|
|
596
|
-
}
|
|
597
|
-
} catch (err) {
|
|
598
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
599
|
-
this._available = false;
|
|
600
|
-
this._disabledReason = `constructor probe threw: ${message}`;
|
|
601
|
-
warn(`Sandbox unavailable: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
isAvailable() {
|
|
605
|
-
return this._available && !this._disabled;
|
|
606
|
-
}
|
|
607
|
-
disable(reason) {
|
|
608
|
-
this._disabled = true;
|
|
609
|
-
this._disabledReason = reason;
|
|
610
|
-
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
611
|
-
}
|
|
612
|
-
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
613
|
-
if (!this.isAvailable()) {
|
|
614
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
615
|
-
}
|
|
616
|
-
if (!_internals4.probeWindowsSandbox()) {
|
|
617
|
-
this._available = false;
|
|
618
|
-
this._disabledReason = "Windows sandbox became unavailable between calls";
|
|
619
|
-
warn(`Sandbox disabled: ${this._disabledReason}. Falling through to tool-layer enforcement.`);
|
|
620
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
621
|
-
}
|
|
622
|
-
const temp = tempDir ?? this._tempDir ?? os2.tmpdir();
|
|
623
|
-
const _allScopes = [...this._scopePaths, ...scopePaths];
|
|
624
|
-
if (detectPowerShellEscape(command)) {
|
|
625
|
-
throw new SandboxError("Command contains PowerShell escape patterns", "DETECT_POWERSHELL_ESCAPE");
|
|
626
|
-
}
|
|
627
|
-
if (!isPathInScopes(command, _allScopes)) {
|
|
628
|
-
throw new SandboxError("Command targets paths outside authorized scopes", "PATH_ESCAPE_SCOPE");
|
|
629
|
-
}
|
|
630
|
-
if (isPowerShellNativeCommand(command) && !isSafePsCommandBody(command)) {
|
|
631
|
-
throw new SandboxError("PowerShell-native command body contains unsafe characters", "UNSAFE_PS_COMMAND");
|
|
632
|
-
}
|
|
633
|
-
const safePath = getSafeWindowsPath();
|
|
634
|
-
const { cmdExe, powerShellExe } = getWindowsExecutablePaths();
|
|
635
|
-
const commandBase64 = Buffer2.from(command, "utf8").toString("base64");
|
|
636
|
-
const escapedTemp = psSingleQuoteEscape(temp);
|
|
637
|
-
const escapedPath = psSingleQuoteEscape(safePath);
|
|
638
|
-
const escapedCmdExe = psSingleQuoteEscape(cmdExe);
|
|
639
|
-
const envOverrideLines = [];
|
|
640
|
-
if (envOverrides) {
|
|
641
|
-
for (const [key, value] of Object.entries(envOverrides)) {
|
|
642
|
-
if (!isValidEnvKey(key)) {
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (value === null) {
|
|
646
|
-
envOverrideLines.push(`Remove-Item Env:${key} -Force -ErrorAction SilentlyContinue;`);
|
|
647
|
-
} else {
|
|
648
|
-
const psEscaped = psSingleQuoteEscape(value);
|
|
649
|
-
envOverrideLines.push(`$env:${key} = '${psEscaped}';`);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
const commandExec = isPowerShellNativeCommand(command) ? `Invoke-Expression $command;
|
|
654
|
-
exit 0;` : `$batchPath = $null;
|
|
655
|
-
$batchLocationPushed = $false;
|
|
656
|
-
try {
|
|
657
|
-
if ($command.Contains([char]13) -or $command.Contains([char]10)) {
|
|
658
|
-
$batchName = 'opencode-swarm-' + [Guid]::NewGuid().ToString('N') + '.cmd';
|
|
659
|
-
$batchPath = Join-Path $env:TEMP $batchName;
|
|
660
|
-
[IO.File]::WriteAllText($batchPath, $command, [Text.UTF8Encoding]::new($false));
|
|
661
|
-
Push-Location -LiteralPath $env:TEMP;
|
|
662
|
-
$batchLocationPushed = $true;
|
|
663
|
-
& '${escapedCmdExe}' /d /v:off /s /c ('call "' + $batchName + '"');
|
|
664
|
-
} else {
|
|
665
|
-
& '${escapedCmdExe}' /d /v:off /s /c $command;
|
|
666
|
-
}
|
|
667
|
-
$childExitCode = $LASTEXITCODE;
|
|
668
|
-
} finally {
|
|
669
|
-
if ($batchLocationPushed) {
|
|
670
|
-
Pop-Location;
|
|
671
|
-
}
|
|
672
|
-
if ($null -ne $batchPath) {
|
|
673
|
-
Remove-Item -LiteralPath $batchPath -Force -ErrorAction SilentlyContinue;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
if ($null -eq $childExitCode) {
|
|
677
|
-
throw 'cmd.exe did not report an exit code';
|
|
678
|
-
}
|
|
679
|
-
exit [int]$childExitCode;`;
|
|
680
|
-
const envOverrideBlock = envOverrideLines.length > 0 ? `
|
|
681
|
-
${envOverrideLines.join(`
|
|
682
|
-
`)}` : "";
|
|
683
|
-
const psScript = `
|
|
684
|
-
$ErrorActionPreference = 'Stop';
|
|
685
|
-
$ProgressPreference = 'SilentlyContinue';
|
|
686
|
-
try {
|
|
687
|
-
$env:TEMP = '${escapedTemp}';
|
|
688
|
-
$env:TMP = '${escapedTemp}';
|
|
689
|
-
|
|
690
|
-
$env:PATH = '${escapedPath}';
|
|
691
|
-
|
|
692
|
-
$dangerousVars = @(
|
|
693
|
-
'LD_PRELOAD',
|
|
694
|
-
'DYLD_INSERT_LIBRARIES',
|
|
695
|
-
'DYLD_LIBRARY_PATH',
|
|
696
|
-
'DYLD_FRAMEWORK_PATH',
|
|
697
|
-
'DYLD_ROOT_PATH',
|
|
698
|
-
'DYLD_FORCE_FLAT_NAMESPACE'
|
|
699
|
-
);
|
|
700
|
-
foreach ($v in $dangerousVars) {
|
|
701
|
-
if (Test-Path Env:$v) {
|
|
702
|
-
Remove-Item Env:$v -Force -ErrorAction SilentlyContinue;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
${envOverrideBlock}
|
|
706
|
-
|
|
707
|
-
# Execute the command \u2014 PS-native cmdlets via Invoke-Expression, others via the standard command interpreter
|
|
708
|
-
$commandBytes = [Convert]::FromBase64String('${commandBase64}');
|
|
709
|
-
$command = [Text.Encoding]::UTF8.GetString($commandBytes);
|
|
710
|
-
${commandExec};
|
|
711
|
-
} catch {
|
|
712
|
-
[Console]::Error.WriteLine($_.Exception.Message);
|
|
713
|
-
exit 1;
|
|
714
|
-
}`;
|
|
715
|
-
const encodedScript = Buffer2.from(psScript, "utf16le").toString("base64");
|
|
716
|
-
const escapedPowerShellExe = psSingleQuoteEscape(powerShellExe);
|
|
717
|
-
return `& '${escapedPowerShellExe}' -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand ${encodedScript}; exit $LASTEXITCODE`;
|
|
718
|
-
}
|
|
719
|
-
getEnvOverrides() {
|
|
720
|
-
return {
|
|
721
|
-
PATH: getSafeWindowsPath(),
|
|
722
|
-
TEMP: null,
|
|
723
|
-
TMP: null,
|
|
724
|
-
LD_PRELOAD: null,
|
|
725
|
-
DYLD_INSERT_LIBRARIES: null,
|
|
726
|
-
DYLD_LIBRARY_PATH: null,
|
|
727
|
-
DYLD_FRAMEWORK_PATH: null,
|
|
728
|
-
DYLD_ROOT_PATH: null,
|
|
729
|
-
DYLD_FORCE_FLAT_NAMESPACE: null
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
var SANDBOX_UNAVAILABLE_CODES2, DEFAULT_SYSTEM_ROOT = "C:\\Windows", UNSAFE_PATH_CHARACTERS, _internals4;
|
|
734
|
-
var init_restricted_environment_executor = __esm(() => {
|
|
735
|
-
init_logger();
|
|
736
|
-
init_executor();
|
|
737
|
-
init_edge_cases();
|
|
738
|
-
SANDBOX_UNAVAILABLE_CODES2 = new Set([
|
|
739
|
-
"ENOENT",
|
|
740
|
-
"EACCES",
|
|
741
|
-
"EPERM",
|
|
742
|
-
"ENOSPC"
|
|
743
|
-
]);
|
|
744
|
-
UNSAFE_PATH_CHARACTERS = new Set([
|
|
745
|
-
":",
|
|
746
|
-
'"',
|
|
747
|
-
"'",
|
|
748
|
-
";",
|
|
749
|
-
"&",
|
|
750
|
-
"|",
|
|
751
|
-
"<",
|
|
752
|
-
">",
|
|
753
|
-
"^",
|
|
754
|
-
"%",
|
|
755
|
-
"!",
|
|
756
|
-
"?",
|
|
757
|
-
"*",
|
|
758
|
-
"`",
|
|
759
|
-
"$"
|
|
760
|
-
]);
|
|
761
|
-
_internals4 = {
|
|
762
|
-
probeWindowsSandbox
|
|
763
|
-
};
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// src/sandbox/win32/native-sandbox-executor.ts
|
|
767
|
-
import * as crypto from "crypto";
|
|
768
|
-
import * as fs from "fs";
|
|
769
|
-
import * as os3 from "os";
|
|
770
|
-
import * as path3 from "path";
|
|
771
|
-
|
|
772
|
-
class NativeWindowsSandboxExecutor {
|
|
773
|
-
mechanism;
|
|
774
|
-
_probeResult;
|
|
775
|
-
_fallbackExecutor;
|
|
776
|
-
_strength;
|
|
777
|
-
_disabled;
|
|
778
|
-
_disabledReason;
|
|
779
|
-
constructor(scopePaths = [], tempDir) {
|
|
780
|
-
this._disabled = false;
|
|
781
|
-
this._disabledReason = null;
|
|
782
|
-
this._fallbackExecutor = new WindowsSandboxExecutor(scopePaths, tempDir);
|
|
783
|
-
this._probeResult = probe();
|
|
784
|
-
if (this._probeResult.available) {
|
|
785
|
-
this._strength = "strong";
|
|
786
|
-
this.mechanism = `native-runner/${this._probeResult.mode}`;
|
|
787
|
-
} else if (this._fallbackExecutor.isAvailable()) {
|
|
788
|
-
this._strength = "weak";
|
|
789
|
-
this.mechanism = "powershell-wrapper";
|
|
790
|
-
warn(`Native sandbox runner unavailable (${this._probeResult.error ?? "unknown"}); ` + "falling back to PowerShell environment restrictions (weak sandbox)");
|
|
791
|
-
} else {
|
|
792
|
-
this._strength = "none";
|
|
793
|
-
this.mechanism = "none";
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
get strength() {
|
|
797
|
-
if (this._disabled)
|
|
798
|
-
return "none";
|
|
799
|
-
return this._strength;
|
|
800
|
-
}
|
|
801
|
-
isAvailable() {
|
|
802
|
-
return !this._disabled && this._strength !== "none";
|
|
803
|
-
}
|
|
804
|
-
disable(reason) {
|
|
805
|
-
this._disabled = true;
|
|
806
|
-
this._disabledReason = reason;
|
|
807
|
-
warn(`Sandbox disabled: ${reason}. Falling through to tool-layer enforcement.`);
|
|
808
|
-
}
|
|
809
|
-
wrapCommand(command, scopePaths, tempDir, envOverrides) {
|
|
810
|
-
if (!this.isAvailable()) {
|
|
811
|
-
throw new SandboxError("Sandbox not available", "SANDBOX_UNAVAILABLE");
|
|
812
|
-
}
|
|
813
|
-
if (this._strength === "strong") {
|
|
814
|
-
return this._wrapWithRunner(command, scopePaths, tempDir, envOverrides);
|
|
815
|
-
}
|
|
816
|
-
return this._fallbackExecutor.wrapCommand(command, scopePaths, tempDir, envOverrides);
|
|
817
|
-
}
|
|
818
|
-
_wrapWithRunner(command, scopePaths, tempDir, _envOverrides) {
|
|
819
|
-
const binary = _internals.findRunnerBinary();
|
|
820
|
-
if (!binary) {
|
|
821
|
-
throw new SandboxError("Runner binary not found", "RUNNER_NOT_FOUND");
|
|
822
|
-
}
|
|
823
|
-
const workspaceRoot = scopePaths[0] ?? process.cwd();
|
|
824
|
-
const runId = `swarm-${crypto.randomUUID?.() ?? Date.now()}`;
|
|
825
|
-
const policy = buildDefaultPolicy(workspaceRoot, runId);
|
|
826
|
-
if (tempDir) {
|
|
827
|
-
policy.temp_root = tempDir;
|
|
828
|
-
}
|
|
829
|
-
policy.workspace_roots = scopePaths.length > 0 ? scopePaths : [workspaceRoot];
|
|
830
|
-
policy.writable_roots = policy.workspace_roots;
|
|
831
|
-
if (_envOverrides) {
|
|
832
|
-
for (const [key, value] of Object.entries(_envOverrides)) {
|
|
833
|
-
if (!isValidEnvKey(key))
|
|
834
|
-
continue;
|
|
835
|
-
if (typeof value === "string") {
|
|
836
|
-
policy.env_overrides[key] = value;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
const policyJson = JSON.stringify(policy);
|
|
841
|
-
const policyDir = path3.join(os3.tmpdir(), "swarm-sandbox-policies");
|
|
842
|
-
try {
|
|
843
|
-
fs.mkdirSync(policyDir, { recursive: true });
|
|
844
|
-
} catch {}
|
|
845
|
-
const policyFile = path3.join(policyDir, `${runId}.json`);
|
|
846
|
-
fs.writeFileSync(policyFile, policyJson, "utf-8");
|
|
847
|
-
const mode = this._probeResult.mode === "none" ? "auto" : this._probeResult.mode;
|
|
848
|
-
const escapedBinary = binary.includes(" ") ? `"${binary}"` : binary;
|
|
849
|
-
const escapedPolicyFile = policyFile.includes(" ") ? `"${policyFile}"` : policyFile;
|
|
850
|
-
const escapedCommand = command.replace(/"/g, "\\\"");
|
|
851
|
-
return `cmd /c "type ${escapedPolicyFile} | ${escapedBinary} --policy-stdin --mode ${mode} -- ${escapedCommand} & del ${escapedPolicyFile} 2>nul"`;
|
|
852
|
-
}
|
|
853
|
-
getEnvOverrides() {
|
|
854
|
-
if (this._strength === "strong") {
|
|
855
|
-
return {
|
|
856
|
-
PATH: null,
|
|
857
|
-
TEMP: null,
|
|
858
|
-
TMP: null,
|
|
859
|
-
LD_PRELOAD: null,
|
|
860
|
-
DYLD_INSERT_LIBRARIES: null,
|
|
861
|
-
DYLD_LIBRARY_PATH: null,
|
|
862
|
-
DYLD_FRAMEWORK_PATH: null,
|
|
863
|
-
DYLD_ROOT_PATH: null,
|
|
864
|
-
DYLD_FORCE_FLAT_NAMESPACE: null
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
return this._fallbackExecutor.getEnvOverrides();
|
|
868
|
-
}
|
|
869
|
-
async executeNative(command, workspaceRoot, runId) {
|
|
870
|
-
if (this._strength !== "strong") {
|
|
871
|
-
throw new SandboxError("Native execution requires strong sandbox (runner binary)", "NATIVE_UNAVAILABLE");
|
|
872
|
-
}
|
|
873
|
-
const policy = buildDefaultPolicy(workspaceRoot, runId);
|
|
874
|
-
return execute(command, policy);
|
|
875
|
-
}
|
|
876
|
-
get hasNativeRunner() {
|
|
877
|
-
return this._strength === "strong";
|
|
878
|
-
}
|
|
879
|
-
get disabledReason() {
|
|
880
|
-
return this._disabledReason;
|
|
881
|
-
}
|
|
882
|
-
get probeResult() {
|
|
883
|
-
return this._probeResult;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
var init_native_sandbox_executor = __esm(() => {
|
|
887
|
-
init_logger();
|
|
888
|
-
init_executor();
|
|
889
|
-
init_restricted_environment_executor();
|
|
890
|
-
init_runner_client();
|
|
891
|
-
});
|
|
892
|
-
|
|
893
|
-
// src/sandbox/executors/windows.ts
|
|
894
|
-
var exports_windows = {};
|
|
895
|
-
__export(exports_windows, {
|
|
896
|
-
WindowsSandboxExecutor: () => NativeWindowsSandboxExecutor,
|
|
897
|
-
LegacyWindowsSandboxExecutor: () => WindowsSandboxExecutor
|
|
898
|
-
});
|
|
899
|
-
var init_windows = __esm(() => {
|
|
900
|
-
init_native_sandbox_executor();
|
|
901
|
-
init_restricted_environment_executor();
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
// src/sandbox/executor.ts
|
|
905
|
-
function isValidEnvKey(key) {
|
|
906
|
-
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key);
|
|
907
|
-
}
|
|
908
|
-
async function getExecutor() {
|
|
909
|
-
if (_cachedExecutorPromise !== undefined) {
|
|
910
|
-
return _cachedExecutorPromise;
|
|
911
|
-
}
|
|
912
|
-
_cachedExecutorPromise = _createExecutor();
|
|
913
|
-
return _cachedExecutorPromise;
|
|
914
|
-
}
|
|
915
|
-
async function _createExecutor() {
|
|
916
|
-
const platform = process.platform;
|
|
917
|
-
if (platform === "linux") {
|
|
918
|
-
return _createLinuxExecutor();
|
|
919
|
-
}
|
|
920
|
-
if (platform === "darwin") {
|
|
921
|
-
return _createMacOSExecutor();
|
|
922
|
-
}
|
|
923
|
-
if (platform === "win32") {
|
|
924
|
-
return _createWindowsExecutor();
|
|
925
|
-
}
|
|
926
|
-
return null;
|
|
927
|
-
}
|
|
928
|
-
async function _createLinuxExecutor() {
|
|
929
|
-
const { SandboxCapabilityProbe, isBubblewrapAvailable } = await import("./capability-probe-f1paszww.js");
|
|
930
|
-
await new SandboxCapabilityProbe().detect();
|
|
931
|
-
if (!isBubblewrapAvailable()) {
|
|
932
|
-
return null;
|
|
933
|
-
}
|
|
934
|
-
try {
|
|
935
|
-
const { BubblewrapSandboxExecutor: BubblewrapSandboxExecutor2 } = (init_bubblewrap(), __toCommonJS(exports_bubblewrap));
|
|
936
|
-
return new BubblewrapSandboxExecutor2([]);
|
|
937
|
-
} catch {
|
|
938
|
-
return null;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
async function _createMacOSExecutor() {
|
|
942
|
-
const { SandboxCapabilityProbe, isSandboxExecAvailable } = await import("./capability-probe-f1paszww.js");
|
|
943
|
-
await new SandboxCapabilityProbe().detect();
|
|
944
|
-
if (!isSandboxExecAvailable()) {
|
|
945
|
-
return null;
|
|
946
|
-
}
|
|
947
|
-
try {
|
|
948
|
-
const { MacOSSandboxExecutor: MacOSSandboxExecutor2 } = (init_macos(), __toCommonJS(exports_macos));
|
|
949
|
-
return new MacOSSandboxExecutor2([]);
|
|
950
|
-
} catch {
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
async function _createWindowsExecutor() {
|
|
955
|
-
const { SandboxCapabilityProbe, isWindowsSandboxAvailable } = await import("./capability-probe-f1paszww.js");
|
|
956
|
-
await new SandboxCapabilityProbe().detect();
|
|
957
|
-
if (!isWindowsSandboxAvailable()) {
|
|
958
|
-
return null;
|
|
959
|
-
}
|
|
960
|
-
try {
|
|
961
|
-
const { WindowsSandboxExecutor: WindowsSandboxExecutor2 } = (init_windows(), __toCommonJS(exports_windows));
|
|
962
|
-
return new WindowsSandboxExecutor2([]);
|
|
963
|
-
} catch {
|
|
964
|
-
return null;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
var SandboxError, _cachedExecutorPromise;
|
|
968
|
-
var init_executor = __esm(() => {
|
|
969
|
-
SandboxError = class SandboxError extends Error {
|
|
970
|
-
code;
|
|
971
|
-
constructor(message, code) {
|
|
972
|
-
super(message);
|
|
973
|
-
this.code = code;
|
|
974
|
-
this.name = "SandboxError";
|
|
975
|
-
}
|
|
976
|
-
};
|
|
977
|
-
});
|
|
978
96
|
|
|
979
97
|
// src/plan/manager.ts
|
|
980
98
|
import {
|
|
@@ -982,22 +100,22 @@ import {
|
|
|
982
100
|
copyFileSync,
|
|
983
101
|
existsSync as existsSync4,
|
|
984
102
|
fsyncSync,
|
|
985
|
-
mkdirSync as
|
|
103
|
+
mkdirSync as mkdirSync3,
|
|
986
104
|
openSync,
|
|
987
105
|
readdirSync,
|
|
988
106
|
renameSync as renameSync2,
|
|
989
107
|
unlinkSync as unlinkSync2,
|
|
990
|
-
writeFileSync as
|
|
108
|
+
writeFileSync as writeFileSync2
|
|
991
109
|
} from "fs";
|
|
992
110
|
import * as fsPromises from "fs/promises";
|
|
993
|
-
import * as
|
|
111
|
+
import * as path5 from "path";
|
|
994
112
|
|
|
995
113
|
// src/db/task-checkpoint-receipt.ts
|
|
996
114
|
import { createHash } from "crypto";
|
|
997
115
|
|
|
998
116
|
// src/db/project-db.ts
|
|
999
|
-
import { existsSync, mkdirSync
|
|
1000
|
-
import { join
|
|
117
|
+
import { existsSync, mkdirSync } from "fs";
|
|
118
|
+
import { join, resolve } from "path";
|
|
1001
119
|
var MIGRATIONS = [
|
|
1002
120
|
{
|
|
1003
121
|
version: 1,
|
|
@@ -1151,7 +269,7 @@ function runProjectMigrations(db) {
|
|
|
1151
269
|
}
|
|
1152
270
|
}
|
|
1153
271
|
function projectDbPath(directory) {
|
|
1154
|
-
return
|
|
272
|
+
return join(resolve(directory), ".swarm", "swarm.db");
|
|
1155
273
|
}
|
|
1156
274
|
function projectDbExists(directory) {
|
|
1157
275
|
return existsSync(projectDbPath(directory));
|
|
@@ -1161,10 +279,10 @@ function getProjectDb(directory) {
|
|
|
1161
279
|
const existing = _projectDbs.get(key);
|
|
1162
280
|
if (existing)
|
|
1163
281
|
return existing;
|
|
1164
|
-
const swarmDir =
|
|
1165
|
-
|
|
282
|
+
const swarmDir = join(key, ".swarm");
|
|
283
|
+
mkdirSync(swarmDir, { recursive: true });
|
|
1166
284
|
const Db = loadDatabaseCtor();
|
|
1167
|
-
const db = new Db(
|
|
285
|
+
const db = new Db(join(swarmDir, "swarm.db"));
|
|
1168
286
|
db.run("PRAGMA journal_mode = WAL;");
|
|
1169
287
|
db.run("PRAGMA synchronous = NORMAL;");
|
|
1170
288
|
db.run("PRAGMA busy_timeout = 5000;");
|
|
@@ -1332,20 +450,8 @@ function updateTaskCheckpointReceipt(directory, descriptor, state, sha) {
|
|
|
1332
450
|
init_executor();
|
|
1333
451
|
import * as child_process from "child_process";
|
|
1334
452
|
import * as fsSync from "fs";
|
|
1335
|
-
import
|
|
1336
|
-
|
|
1337
|
-
// src/utils/git-binary-missing-error.ts
|
|
1338
|
-
class GitBinaryMissingError extends Error {
|
|
1339
|
-
name = "GitBinaryMissingError";
|
|
1340
|
-
constructor(message = "git binary is not available", options) {
|
|
1341
|
-
super(message, options);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
function isGitBinaryMissing(err) {
|
|
1345
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// src/git/branch.ts
|
|
453
|
+
import path from "path";
|
|
454
|
+
init_git_executable();
|
|
1349
455
|
init_logger();
|
|
1350
456
|
|
|
1351
457
|
// src/utils/transient-retry.ts
|
|
@@ -1370,23 +476,7 @@ function transientBackoff(attempt) {
|
|
|
1370
476
|
// src/git/branch.ts
|
|
1371
477
|
var GIT_TIMEOUT_MS = 30000;
|
|
1372
478
|
var GIT_MAX_BUFFER_BYTES = 5 * 1024 * 1024;
|
|
1373
|
-
|
|
1374
|
-
return [...new Set(values.filter(Boolean))];
|
|
1375
|
-
}
|
|
1376
|
-
function windowsGitCandidates() {
|
|
1377
|
-
if (process.platform !== "win32")
|
|
1378
|
-
return ["git"];
|
|
1379
|
-
const roots = unique([
|
|
1380
|
-
process.env.ProgramFiles ?? "",
|
|
1381
|
-
process.env["ProgramFiles(x86)"] ?? "",
|
|
1382
|
-
process.env.LOCALAPPDATA ? path4.join(process.env.LOCALAPPDATA, "Programs") : ""
|
|
1383
|
-
]);
|
|
1384
|
-
const installed = roots.flatMap((root) => [
|
|
1385
|
-
path4.join(root, "Git", "cmd", "git.exe"),
|
|
1386
|
-
path4.join(root, "Git", "bin", "git.exe")
|
|
1387
|
-
]);
|
|
1388
|
-
return unique(["git", ...installed]);
|
|
1389
|
-
}
|
|
479
|
+
var MAX_RENDERED_GIT_CANDIDATES = 10;
|
|
1390
480
|
function errorMessage(err) {
|
|
1391
481
|
return err instanceof Error ? err.message : String(err);
|
|
1392
482
|
}
|
|
@@ -1407,7 +497,7 @@ var __spawnSyncSeam = {
|
|
|
1407
497
|
}
|
|
1408
498
|
};
|
|
1409
499
|
function readLaneEnvFileFromDiskSync(worktreePath, laneIndex) {
|
|
1410
|
-
const envPath =
|
|
500
|
+
const envPath = path.join(worktreePath, ".swarm", "lanes", `${laneIndex}.env`);
|
|
1411
501
|
let content;
|
|
1412
502
|
try {
|
|
1413
503
|
content = fsSync.readFileSync(envPath, "utf-8");
|
|
@@ -1431,43 +521,71 @@ function readLaneEnvFileFromDiskSync(worktreePath, laneIndex) {
|
|
|
1431
521
|
}
|
|
1432
522
|
return result;
|
|
1433
523
|
}
|
|
524
|
+
function gitBinaryMissingMessage(cwd) {
|
|
525
|
+
const resolution = describeGitResolution();
|
|
526
|
+
const lines = [
|
|
527
|
+
`git executable is not available: spawning "${resolution.resolvedPath ?? "git"}" failed, and the working directory ${cwd} exists and is a directory.`
|
|
528
|
+
];
|
|
529
|
+
if (resolution.attempts.length > 0) {
|
|
530
|
+
lines.push("Candidates tried:");
|
|
531
|
+
const shown = resolution.attempts.slice(0, MAX_RENDERED_GIT_CANDIDATES);
|
|
532
|
+
for (const attempt of shown) {
|
|
533
|
+
lines.push(` - [${attempt.source}] ${attempt.candidate}: ${attempt.accepted ? "accepted" : attempt.reason ?? "rejected"}`);
|
|
534
|
+
}
|
|
535
|
+
const omitted = resolution.attempts.length - shown.length;
|
|
536
|
+
if (omitted > 0)
|
|
537
|
+
lines.push(` ... and ${omitted} more`);
|
|
538
|
+
} else {
|
|
539
|
+
lines.push("No candidate probe results are recorded for this process (the resolver was pre-seeded or has not probed yet).");
|
|
540
|
+
}
|
|
541
|
+
lines.push(resolution.overrideValue ? `The configured override (${resolution.overrideSource ?? "unknown"}) "${resolution.overrideValue}" did not work \u2014 point the ${GIT_BINARY_ENV_VAR} environment variable, or the "git.binary" value in your USER config (a project .opencode config is ignored for this key), at a working git executable.` : `No override is configured. Set the ${GIT_BINARY_ENV_VAR} environment variable, or the "git.binary" value in your USER config (a project .opencode config is ignored for this key), to point at a working git executable.`);
|
|
542
|
+
return lines.join(`
|
|
543
|
+
`);
|
|
544
|
+
}
|
|
1434
545
|
function gitExec(args, cwd, laneEnv, laneIndex) {
|
|
1435
546
|
const resolvedLaneEnv = laneEnv ?? (laneIndex !== undefined ? readLaneEnvFileFromDiskSync(cwd, laneIndex) : undefined);
|
|
1436
|
-
let missingGitError;
|
|
1437
547
|
const subcommand = args[0];
|
|
1438
548
|
const hardenedArgs = subcommand === "commit" || subcommand === "tag" ? ["-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", ...args] : args;
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
549
|
+
const command = _internals.resolveGitExecutable();
|
|
550
|
+
for (let attempt = 0;attempt < MAX_TRANSIENT_RETRIES; attempt++) {
|
|
551
|
+
const result = _internals.spawnSync(command, hardenedArgs, {
|
|
552
|
+
cwd,
|
|
553
|
+
encoding: "utf-8",
|
|
554
|
+
timeout: GIT_TIMEOUT_MS,
|
|
555
|
+
windowsHide: true,
|
|
556
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
557
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
558
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
559
|
+
envOverrides: resolvedLaneEnv
|
|
560
|
+
});
|
|
561
|
+
if (!result.error && result.status === 0) {
|
|
562
|
+
return result.stdout;
|
|
563
|
+
}
|
|
564
|
+
if (result.error) {
|
|
565
|
+
if (isSpawnCwdMissing(result.error, cwd)) {
|
|
566
|
+
throw new GitSpawnCwdError(cwd, "missing", { cause: result.error });
|
|
1453
567
|
}
|
|
1454
|
-
if (result.error) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
}
|
|
1459
|
-
if (isTransientSpawnError(result.error) && attempt < MAX_TRANSIENT_RETRIES - 1) {
|
|
1460
|
-
transientBackoff(attempt);
|
|
1461
|
-
continue;
|
|
1462
|
-
}
|
|
1463
|
-
throw new Error(errorMessage(result.error));
|
|
568
|
+
if (isSpawnCwdUnreadable(result.error, cwd)) {
|
|
569
|
+
throw new GitSpawnCwdError(cwd, "unreadable", {
|
|
570
|
+
cause: result.error
|
|
571
|
+
});
|
|
1464
572
|
}
|
|
1465
|
-
if (result.
|
|
1466
|
-
throw new
|
|
573
|
+
if (isGitBinaryMissing(result.error, cwd)) {
|
|
574
|
+
throw new GitBinaryMissingError(gitBinaryMissingMessage(cwd), {
|
|
575
|
+
cause: result.error
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
if (isTransientSpawnError(result.error) && attempt < MAX_TRANSIENT_RETRIES - 1) {
|
|
579
|
+
transientBackoff(attempt);
|
|
580
|
+
continue;
|
|
1467
581
|
}
|
|
582
|
+
throw new Error(errorMessage(result.error));
|
|
583
|
+
}
|
|
584
|
+
if (result.status !== 0) {
|
|
585
|
+
throw new Error(result.stderr || result.stdout || `git exited with ${result.status}`);
|
|
1468
586
|
}
|
|
1469
587
|
}
|
|
1470
|
-
throw new GitBinaryMissingError(process.platform === "win32" ? "git executable is not available on PATH or common Windows install locations" : "git executable is not available on PATH"
|
|
588
|
+
throw new GitBinaryMissingError(process.platform === "win32" ? "git executable is not available on PATH or common Windows install locations" : "git executable is not available on PATH");
|
|
1471
589
|
}
|
|
1472
590
|
function getGitRepositoryStatus(cwd) {
|
|
1473
591
|
try {
|
|
@@ -1544,7 +662,7 @@ async function resetToRemoteBranch(cwd, options) {
|
|
|
1544
662
|
const prunedBranches = [];
|
|
1545
663
|
try {
|
|
1546
664
|
const currentBranch = getCurrentBranch(cwd);
|
|
1547
|
-
const defaultRemoteBranch =
|
|
665
|
+
const defaultRemoteBranch = _internals.detectDefaultRemoteBranch(cwd);
|
|
1548
666
|
if (!defaultRemoteBranch) {
|
|
1549
667
|
return {
|
|
1550
668
|
success: false,
|
|
@@ -1726,7 +844,7 @@ var GITIGNORED_BUILD_ARTIFACTS = ["dist"];
|
|
|
1726
844
|
async function resetToMainAfterMerge(cwd, options) {
|
|
1727
845
|
const warnings = [];
|
|
1728
846
|
try {
|
|
1729
|
-
const defaultBranch =
|
|
847
|
+
const defaultBranch = _internals.detectDefaultRemoteBranch(cwd);
|
|
1730
848
|
if (!defaultBranch) {
|
|
1731
849
|
return {
|
|
1732
850
|
success: false,
|
|
@@ -1753,7 +871,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1753
871
|
}
|
|
1754
872
|
if (currentBranch === defaultBranch) {
|
|
1755
873
|
try {
|
|
1756
|
-
const logOutput =
|
|
874
|
+
const logOutput = _internals.gitExec(["log", `${targetBranch}..HEAD`, "--oneline"], cwd);
|
|
1757
875
|
if (logOutput.trim().length > 0) {
|
|
1758
876
|
return {
|
|
1759
877
|
success: false,
|
|
@@ -1768,11 +886,11 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1768
886
|
} catch {}
|
|
1769
887
|
} else {
|
|
1770
888
|
try {
|
|
1771
|
-
|
|
889
|
+
_internals.gitExec(["rev-parse", "--abbrev-ref", `${currentBranch}@{upstream}`], cwd);
|
|
1772
890
|
} catch {
|
|
1773
891
|
try {
|
|
1774
|
-
const localSha =
|
|
1775
|
-
const remoteSha =
|
|
892
|
+
const localSha = _internals.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
893
|
+
const remoteSha = _internals.gitExec(["rev-parse", targetBranch], cwd).trim();
|
|
1776
894
|
if (localSha !== remoteSha) {
|
|
1777
895
|
return {
|
|
1778
896
|
success: false,
|
|
@@ -1798,7 +916,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1798
916
|
}
|
|
1799
917
|
}
|
|
1800
918
|
try {
|
|
1801
|
-
|
|
919
|
+
_internals.gitExec(["fetch", "--prune", "origin"], cwd);
|
|
1802
920
|
} catch (err) {
|
|
1803
921
|
return {
|
|
1804
922
|
success: false,
|
|
@@ -1814,7 +932,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1814
932
|
let switchedBranch = false;
|
|
1815
933
|
if (currentBranch !== defaultBranch) {
|
|
1816
934
|
try {
|
|
1817
|
-
|
|
935
|
+
_internals.gitExec(["checkout", defaultBranch], cwd);
|
|
1818
936
|
switchedBranch = true;
|
|
1819
937
|
} catch (err) {
|
|
1820
938
|
return {
|
|
@@ -1829,7 +947,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1829
947
|
}
|
|
1830
948
|
}
|
|
1831
949
|
try {
|
|
1832
|
-
|
|
950
|
+
_internals.gitExec(["reset", "--hard", targetBranch], cwd);
|
|
1833
951
|
} catch (err) {
|
|
1834
952
|
return {
|
|
1835
953
|
success: false,
|
|
@@ -1849,7 +967,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1849
967
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
1850
968
|
}
|
|
1851
969
|
try {
|
|
1852
|
-
|
|
970
|
+
_internals.gitExec(["checkout", "--", "."], cwd);
|
|
1853
971
|
discardSucceeded = true;
|
|
1854
972
|
break;
|
|
1855
973
|
} catch {}
|
|
@@ -1860,18 +978,18 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1860
978
|
changesDiscarded = discardSucceeded;
|
|
1861
979
|
}
|
|
1862
980
|
try {
|
|
1863
|
-
|
|
981
|
+
_internals.gitExec(["clean", "-fdX", "--", ...GITIGNORED_BUILD_ARTIFACTS], cwd);
|
|
1864
982
|
} catch {
|
|
1865
983
|
warnings.push("Could not clean build artifacts");
|
|
1866
984
|
}
|
|
1867
985
|
let branchDeleted = false;
|
|
1868
986
|
if (switchedBranch && previousBranch !== defaultBranch) {
|
|
1869
987
|
try {
|
|
1870
|
-
const mergedOutput =
|
|
988
|
+
const mergedOutput = _internals.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
1871
989
|
const isMerged = mergedOutput.split(`
|
|
1872
990
|
`).some((line) => line.trim() === previousBranch || line.trim() === `* ${previousBranch}`);
|
|
1873
991
|
if (isMerged) {
|
|
1874
|
-
|
|
992
|
+
_internals.gitExec(["branch", "-d", previousBranch], cwd);
|
|
1875
993
|
branchDeleted = true;
|
|
1876
994
|
} else {
|
|
1877
995
|
warnings.push(`Branch ${previousBranch} is not merged into ${defaultBranch} \u2014 keeping it`);
|
|
@@ -1882,7 +1000,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1882
1000
|
}
|
|
1883
1001
|
if (options?.pruneBranches) {
|
|
1884
1002
|
try {
|
|
1885
|
-
const mergedOutput =
|
|
1003
|
+
const mergedOutput = _internals.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
1886
1004
|
const mergedLines = mergedOutput.split(`
|
|
1887
1005
|
`);
|
|
1888
1006
|
for (const line of mergedLines) {
|
|
@@ -1891,7 +1009,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1891
1009
|
continue;
|
|
1892
1010
|
}
|
|
1893
1011
|
try {
|
|
1894
|
-
|
|
1012
|
+
_internals.gitExec(["branch", "-d", trimmedLine], cwd);
|
|
1895
1013
|
} catch {
|
|
1896
1014
|
warnings.push(`Could not prune branch: ${trimmedLine}`);
|
|
1897
1015
|
}
|
|
@@ -1921,7 +1039,7 @@ async function resetToMainAfterMerge(cwd, options) {
|
|
|
1921
1039
|
};
|
|
1922
1040
|
}
|
|
1923
1041
|
}
|
|
1924
|
-
var
|
|
1042
|
+
var _internals = {
|
|
1925
1043
|
gitExec,
|
|
1926
1044
|
detectDefaultRemoteBranch,
|
|
1927
1045
|
getDefaultBaseBranch,
|
|
@@ -1929,12 +1047,13 @@ var _internals5 = {
|
|
|
1929
1047
|
resetToRemoteBranch,
|
|
1930
1048
|
resetToMainAfterMerge,
|
|
1931
1049
|
spawnSync: __spawnSyncSeam.spawnSync,
|
|
1932
|
-
readLaneEnvFileFromDiskSync
|
|
1050
|
+
readLaneEnvFileFromDiskSync,
|
|
1051
|
+
resolveGitExecutable
|
|
1933
1052
|
};
|
|
1934
1053
|
|
|
1935
1054
|
// src/services/run-memory.ts
|
|
1936
|
-
import * as
|
|
1937
|
-
import * as
|
|
1055
|
+
import * as crypto from "crypto";
|
|
1056
|
+
import * as fs2 from "fs/promises";
|
|
1938
1057
|
|
|
1939
1058
|
// src/hooks/context-sanitizer.ts
|
|
1940
1059
|
function sanitizeContextText(text) {
|
|
@@ -1948,8 +1067,8 @@ function sanitizeContextText(text) {
|
|
|
1948
1067
|
init_logger();
|
|
1949
1068
|
|
|
1950
1069
|
// src/utils/path-security.ts
|
|
1951
|
-
import * as
|
|
1952
|
-
import * as
|
|
1070
|
+
import * as fs from "fs";
|
|
1071
|
+
import * as path2 from "path";
|
|
1953
1072
|
function containsPathTraversal(str) {
|
|
1954
1073
|
if (/\.\.[/\\]/.test(str))
|
|
1955
1074
|
return true;
|
|
@@ -2019,7 +1138,7 @@ function validateProjectDirectory(directory) {
|
|
|
2019
1138
|
if (containsControlChars(directory)) {
|
|
2020
1139
|
throw new Error("Invalid project directory: control characters detected");
|
|
2021
1140
|
}
|
|
2022
|
-
if (!
|
|
1141
|
+
if (!path2.isAbsolute(directory) && !/^[A-Za-z]:[/\\]/.test(directory)) {
|
|
2023
1142
|
throw new Error("Invalid project directory: must be an absolute path");
|
|
2024
1143
|
}
|
|
2025
1144
|
assertNotSystemLocation(directory);
|
|
@@ -2044,7 +1163,7 @@ var DENIED_ROOT_SUBTREES = new Set([
|
|
|
2044
1163
|
]);
|
|
2045
1164
|
var DENIED_EXACT_ROOT_CHILDREN = new Set(["users", "home", "root"]);
|
|
2046
1165
|
function assertNotSystemLocation(directory) {
|
|
2047
|
-
for (const parser of [
|
|
1166
|
+
for (const parser of [path2.win32, path2.posix]) {
|
|
2048
1167
|
if (!parser.isAbsolute(directory))
|
|
2049
1168
|
continue;
|
|
2050
1169
|
const normalized = parser.normalize(directory);
|
|
@@ -2063,50 +1182,50 @@ function resolveNearestExistingCanonical(inputPath, resolver) {
|
|
|
2063
1182
|
try {
|
|
2064
1183
|
return resolver(inputPath);
|
|
2065
1184
|
} catch {}
|
|
2066
|
-
const lexicallyResolved =
|
|
2067
|
-
let
|
|
1185
|
+
const lexicallyResolved = path2.resolve(inputPath);
|
|
1186
|
+
let probe = lexicallyResolved;
|
|
2068
1187
|
const tail = [];
|
|
2069
1188
|
for (let i = 0;i < 4096; i++) {
|
|
2070
|
-
const parent =
|
|
2071
|
-
if (parent ===
|
|
1189
|
+
const parent = path2.dirname(probe);
|
|
1190
|
+
if (parent === probe) {
|
|
2072
1191
|
return lexicallyResolved;
|
|
2073
1192
|
}
|
|
2074
|
-
tail.unshift(
|
|
2075
|
-
|
|
1193
|
+
tail.unshift(path2.basename(probe));
|
|
1194
|
+
probe = parent;
|
|
2076
1195
|
try {
|
|
2077
|
-
const canonicalAncestor = resolver(
|
|
2078
|
-
return
|
|
1196
|
+
const canonicalAncestor = resolver(probe);
|
|
1197
|
+
return path2.normalize(path2.join(canonicalAncestor, ...tail));
|
|
2079
1198
|
} catch {}
|
|
2080
1199
|
}
|
|
2081
1200
|
return lexicallyResolved;
|
|
2082
1201
|
}
|
|
2083
1202
|
function validateSymlinkBoundary(targetPath, rootPath) {
|
|
2084
|
-
const realTarget = resolveNearestExistingCanonical(targetPath,
|
|
2085
|
-
const realRoot = resolveNearestExistingCanonical(rootPath,
|
|
2086
|
-
const normalizedTarget =
|
|
2087
|
-
const normalizedRoot =
|
|
2088
|
-
if (!normalizedTarget.startsWith(normalizedRoot +
|
|
1203
|
+
const realTarget = resolveNearestExistingCanonical(targetPath, _internals2.realpathSync);
|
|
1204
|
+
const realRoot = resolveNearestExistingCanonical(rootPath, _internals2.realpathSync);
|
|
1205
|
+
const normalizedTarget = path2.normalize(realTarget);
|
|
1206
|
+
const normalizedRoot = path2.normalize(realRoot);
|
|
1207
|
+
if (!normalizedTarget.startsWith(normalizedRoot + path2.sep) && normalizedTarget !== normalizedRoot) {
|
|
2089
1208
|
throw new Error(`Symlink resolution escaped boundary: ${realTarget} is not within ${realRoot}`);
|
|
2090
1209
|
}
|
|
2091
1210
|
}
|
|
2092
1211
|
function isCanonicalPathWithinRoot(targetPath, rootPath) {
|
|
2093
1212
|
let canonicalRoot;
|
|
2094
1213
|
try {
|
|
2095
|
-
canonicalRoot =
|
|
1214
|
+
canonicalRoot = fs.realpathSync(rootPath);
|
|
2096
1215
|
} catch {
|
|
2097
|
-
canonicalRoot =
|
|
1216
|
+
canonicalRoot = path2.normalize(rootPath);
|
|
2098
1217
|
}
|
|
2099
|
-
let
|
|
1218
|
+
let probe = path2.normalize(targetPath);
|
|
2100
1219
|
for (let i = 0;i < 4096; i++) {
|
|
2101
1220
|
try {
|
|
2102
|
-
const canonicalProbe =
|
|
2103
|
-
const relative2 =
|
|
2104
|
-
return !relative2.startsWith("..") && !
|
|
1221
|
+
const canonicalProbe = fs.realpathSync(probe);
|
|
1222
|
+
const relative2 = path2.relative(canonicalRoot, canonicalProbe);
|
|
1223
|
+
return !relative2.startsWith("..") && !path2.isAbsolute(relative2);
|
|
2105
1224
|
} catch {
|
|
2106
|
-
const parent =
|
|
2107
|
-
if (parent ===
|
|
1225
|
+
const parent = path2.dirname(probe);
|
|
1226
|
+
if (parent === probe)
|
|
2108
1227
|
return false;
|
|
2109
|
-
|
|
1228
|
+
probe = parent;
|
|
2110
1229
|
}
|
|
2111
1230
|
}
|
|
2112
1231
|
return false;
|
|
@@ -2115,7 +1234,7 @@ function validateTargetWithinRoot(filePath, root) {
|
|
|
2115
1234
|
if (!filePath || filePath.trim() === "") {
|
|
2116
1235
|
return "Empty path";
|
|
2117
1236
|
}
|
|
2118
|
-
if (
|
|
1237
|
+
if (path2.isAbsolute(filePath) || /^[A-Za-z]:[/\\]/.test(filePath)) {
|
|
2119
1238
|
return `Absolute path rejected: ${filePath}`;
|
|
2120
1239
|
}
|
|
2121
1240
|
if (containsPathTraversal(filePath)) {
|
|
@@ -2124,9 +1243,9 @@ function validateTargetWithinRoot(filePath, root) {
|
|
|
2124
1243
|
if (containsControlChars(filePath)) {
|
|
2125
1244
|
return `Control characters detected in path: ${filePath}`;
|
|
2126
1245
|
}
|
|
2127
|
-
const resolved =
|
|
2128
|
-
const relative2 =
|
|
2129
|
-
if (relative2.startsWith("..") ||
|
|
1246
|
+
const resolved = path2.resolve(root, filePath);
|
|
1247
|
+
const relative2 = path2.relative(root, resolved);
|
|
1248
|
+
if (relative2.startsWith("..") || path2.isAbsolute(relative2)) {
|
|
2130
1249
|
return `Path escapes root: ${filePath}`;
|
|
2131
1250
|
}
|
|
2132
1251
|
if (!isCanonicalPathWithinRoot(resolved, root)) {
|
|
@@ -2134,8 +1253,8 @@ function validateTargetWithinRoot(filePath, root) {
|
|
|
2134
1253
|
}
|
|
2135
1254
|
return null;
|
|
2136
1255
|
}
|
|
2137
|
-
var
|
|
2138
|
-
realpathSync:
|
|
1256
|
+
var _internals2 = {
|
|
1257
|
+
realpathSync: fs.realpathSync,
|
|
2139
1258
|
containsPathTraversal,
|
|
2140
1259
|
containsControlChars,
|
|
2141
1260
|
validateDirectory,
|
|
@@ -2150,7 +1269,7 @@ var RUN_MEMORY_FILENAME = "run-memory.jsonl";
|
|
|
2150
1269
|
var MAX_SUMMARY_TOKENS = 500;
|
|
2151
1270
|
function generateTaskFingerprint(taskId, fileTargets) {
|
|
2152
1271
|
const sortedFiles = [...fileTargets].sort().join(",");
|
|
2153
|
-
const hash =
|
|
1272
|
+
const hash = crypto.createHash("sha256").update(taskId + sortedFiles).digest("hex");
|
|
2154
1273
|
return hash.slice(0, 8);
|
|
2155
1274
|
}
|
|
2156
1275
|
async function recordOutcome(directory, entry) {
|
|
@@ -2158,7 +1277,7 @@ async function recordOutcome(directory, entry) {
|
|
|
2158
1277
|
const resolvedPath = validateSwarmPath(directory, RUN_MEMORY_FILENAME);
|
|
2159
1278
|
const line = `${JSON.stringify(entry)}
|
|
2160
1279
|
`;
|
|
2161
|
-
await
|
|
1280
|
+
await fs2.appendFile(resolvedPath, line, { encoding: "utf-8" });
|
|
2162
1281
|
}
|
|
2163
1282
|
async function getTaskHistory(directory, taskId) {
|
|
2164
1283
|
validateWorkspaceRoot(directory);
|
|
@@ -2185,14 +1304,14 @@ async function recordTaskAttempt(directory, input) {
|
|
|
2185
1304
|
try {
|
|
2186
1305
|
let history = [];
|
|
2187
1306
|
try {
|
|
2188
|
-
history = await
|
|
1307
|
+
history = await _internals3.getTaskHistory(directory, input.taskId);
|
|
2189
1308
|
} catch (err) {
|
|
2190
1309
|
warn(`[run-memory] history read for task ${input.taskId} failed; recording with attemptNumber 1: ${err instanceof Error ? err.message : String(err)}`);
|
|
2191
1310
|
}
|
|
2192
1311
|
const entry = {
|
|
2193
1312
|
timestamp: new Date().toISOString(),
|
|
2194
1313
|
taskId: input.taskId,
|
|
2195
|
-
taskFingerprint:
|
|
1314
|
+
taskFingerprint: _internals3.generateTaskFingerprint(input.taskId, input.fileTargets ?? []),
|
|
2196
1315
|
agent: input.agent,
|
|
2197
1316
|
outcome: input.outcome,
|
|
2198
1317
|
attemptNumber: history.length + 1
|
|
@@ -2206,7 +1325,7 @@ async function recordTaskAttempt(directory, input) {
|
|
|
2206
1325
|
if (typeof input.durationMs === "number") {
|
|
2207
1326
|
entry.durationMs = input.durationMs;
|
|
2208
1327
|
}
|
|
2209
|
-
await
|
|
1328
|
+
await _internals3.recordOutcome(directory, entry);
|
|
2210
1329
|
} catch (err) {
|
|
2211
1330
|
warn(`[run-memory] failed to record ${input.outcome} for task ${input.taskId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2212
1331
|
}
|
|
@@ -2270,10 +1389,10 @@ async function getRunMemorySummary(directory) {
|
|
|
2270
1389
|
if (entries.length === 0) {
|
|
2271
1390
|
return null;
|
|
2272
1391
|
}
|
|
2273
|
-
const groups =
|
|
1392
|
+
const groups = _internals3.groupByTaskId(entries);
|
|
2274
1393
|
const summaries = [];
|
|
2275
1394
|
for (const [taskId, taskEntries] of groups) {
|
|
2276
|
-
const summary =
|
|
1395
|
+
const summary = _internals3.summarizeTask(taskId, taskEntries);
|
|
2277
1396
|
if (summary) {
|
|
2278
1397
|
summaries.push(summary);
|
|
2279
1398
|
}
|
|
@@ -2306,7 +1425,7 @@ Use this data to avoid repeating known failure patterns.`;
|
|
|
2306
1425
|
}
|
|
2307
1426
|
return prefix + summaryText + suffix;
|
|
2308
1427
|
}
|
|
2309
|
-
var
|
|
1428
|
+
var _internals3 = {
|
|
2310
1429
|
generateTaskFingerprint,
|
|
2311
1430
|
recordOutcome,
|
|
2312
1431
|
recordTaskAttempt,
|
|
@@ -2318,16 +1437,16 @@ var _internals7 = {
|
|
|
2318
1437
|
|
|
2319
1438
|
// src/turbo/epic/state.ts
|
|
2320
1439
|
init_logger();
|
|
2321
|
-
import * as
|
|
2322
|
-
import * as
|
|
1440
|
+
import * as fs3 from "fs";
|
|
1441
|
+
import * as path3 from "path";
|
|
2323
1442
|
var STATE_FILE = "epic-state.json";
|
|
2324
1443
|
function nowISO() {
|
|
2325
1444
|
return new Date().toISOString();
|
|
2326
1445
|
}
|
|
2327
1446
|
function ensureSwarmDir(directory) {
|
|
2328
|
-
const swarmDir =
|
|
2329
|
-
if (!
|
|
2330
|
-
|
|
1447
|
+
const swarmDir = path3.resolve(directory, ".swarm");
|
|
1448
|
+
if (!fs3.existsSync(swarmDir)) {
|
|
1449
|
+
fs3.mkdirSync(swarmDir, { recursive: true });
|
|
2331
1450
|
}
|
|
2332
1451
|
return swarmDir;
|
|
2333
1452
|
}
|
|
@@ -2347,17 +1466,17 @@ function markStateUnreadable(directory, reason) {
|
|
|
2347
1466
|
}
|
|
2348
1467
|
function readPersisted(directory) {
|
|
2349
1468
|
try {
|
|
2350
|
-
const filePath =
|
|
2351
|
-
if (!
|
|
1469
|
+
const filePath = path3.join(directory, ".swarm", STATE_FILE);
|
|
1470
|
+
if (!fs3.existsSync(filePath)) {
|
|
2352
1471
|
const seed = emptyPersisted();
|
|
2353
1472
|
try {
|
|
2354
1473
|
ensureSwarmDir(directory);
|
|
2355
|
-
|
|
1474
|
+
fs3.writeFileSync(filePath, `${JSON.stringify(seed, null, 2)}
|
|
2356
1475
|
`, "utf-8");
|
|
2357
1476
|
} catch {}
|
|
2358
1477
|
return seed;
|
|
2359
1478
|
}
|
|
2360
|
-
const raw =
|
|
1479
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
2361
1480
|
const parsed = JSON.parse(raw);
|
|
2362
1481
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 1 || !parsed.sessions || typeof parsed.sessions !== "object" || Array.isArray(parsed.sessions)) {
|
|
2363
1482
|
markStateUnreadable(directory, `malformed shape (version=${parsed?.version}, sessions type=${Array.isArray(parsed?.sessions) ? "array" : typeof parsed?.sessions})`);
|
|
@@ -2383,7 +1502,7 @@ function writePersisted(directory, persisted) {
|
|
|
2383
1502
|
let payload;
|
|
2384
1503
|
try {
|
|
2385
1504
|
ensureSwarmDir(directory);
|
|
2386
|
-
filePath =
|
|
1505
|
+
filePath = path3.join(directory, ".swarm", STATE_FILE);
|
|
2387
1506
|
tmpPath = `${filePath}.tmp.${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2388
1507
|
persisted.updatedAt = nowISO();
|
|
2389
1508
|
payload = `${JSON.stringify(persisted, null, 2)}
|
|
@@ -2394,14 +1513,14 @@ function writePersisted(directory, persisted) {
|
|
|
2394
1513
|
throw new Error(`Epic state persistence prepare failed: ${msg}`);
|
|
2395
1514
|
}
|
|
2396
1515
|
try {
|
|
2397
|
-
|
|
2398
|
-
|
|
1516
|
+
fs3.writeFileSync(tmpPath, payload, "utf-8");
|
|
1517
|
+
fs3.renameSync(tmpPath, filePath);
|
|
2399
1518
|
} catch (error2) {
|
|
2400
1519
|
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
2401
1520
|
error(`[turbo/epic/state] Failed to persist ${STATE_FILE} atomically: ${msg}`);
|
|
2402
1521
|
try {
|
|
2403
|
-
if (
|
|
2404
|
-
|
|
1522
|
+
if (fs3.existsSync(tmpPath))
|
|
1523
|
+
fs3.unlinkSync(tmpPath);
|
|
2405
1524
|
} catch {}
|
|
2406
1525
|
throw new Error(`Epic state persistence failed: ${msg}`);
|
|
2407
1526
|
}
|
|
@@ -2432,7 +1551,7 @@ function isEpicModeActive(directory, sessionID) {
|
|
|
2432
1551
|
function isEpicModeActiveForProject(directory) {
|
|
2433
1552
|
if (stateUnreadableMap.get(directory))
|
|
2434
1553
|
return false;
|
|
2435
|
-
if (!
|
|
1554
|
+
if (!fs3.existsSync(path3.join(directory, ".swarm", STATE_FILE))) {
|
|
2436
1555
|
return false;
|
|
2437
1556
|
}
|
|
2438
1557
|
const persisted = readPersisted(directory);
|
|
@@ -2483,11 +1602,11 @@ function isLockContentionError(err) {
|
|
|
2483
1602
|
return INDEX_LOCK_ERROR_RE.test(msg);
|
|
2484
1603
|
}
|
|
2485
1604
|
async function commitTaskCompletion(directory, taskId, description, scopePaths) {
|
|
2486
|
-
if (!
|
|
1605
|
+
if (!_internals4.isGitRepo(directory)) {
|
|
2487
1606
|
return { committed: false, reason: "no-git" };
|
|
2488
1607
|
}
|
|
2489
1608
|
try {
|
|
2490
|
-
if (
|
|
1609
|
+
if (_internals4.hasExistingTaskCommit(directory, taskId)) {
|
|
2491
1610
|
return { committed: true, reason: "idempotent-skip" };
|
|
2492
1611
|
}
|
|
2493
1612
|
} catch {}
|
|
@@ -2508,15 +1627,15 @@ async function commitTaskCompletion(directory, taskId, description, scopePaths)
|
|
|
2508
1627
|
for (let attempt = 0;attempt <= INDEX_LOCK_BACKOFF_MS.length; attempt++) {
|
|
2509
1628
|
try {
|
|
2510
1629
|
if (paths.length > 0) {
|
|
2511
|
-
|
|
1630
|
+
_internals4.stageScopedPaths(directory, paths);
|
|
2512
1631
|
}
|
|
2513
|
-
|
|
2514
|
-
const sha =
|
|
1632
|
+
_internals4.commitAllowEmpty(directory, message);
|
|
1633
|
+
const sha = _internals4.gitHeadSha(directory);
|
|
2515
1634
|
return { committed: true, reason: "success", sha };
|
|
2516
1635
|
} catch (err) {
|
|
2517
1636
|
lastError = err;
|
|
2518
1637
|
if (attempt < INDEX_LOCK_BACKOFF_MS.length && isLockContentionError(err)) {
|
|
2519
|
-
await
|
|
1638
|
+
await _internals4.sleep(INDEX_LOCK_BACKOFF_MS[attempt]);
|
|
2520
1639
|
continue;
|
|
2521
1640
|
}
|
|
2522
1641
|
break;
|
|
@@ -2526,26 +1645,26 @@ async function commitTaskCompletion(directory, taskId, description, scopePaths)
|
|
|
2526
1645
|
criticalWarn(`[epic:task-commit] commit for task ${taskId} failed (non-fatal): ${msg}`);
|
|
2527
1646
|
return { committed: false, reason: "commit-failed", error: msg };
|
|
2528
1647
|
}
|
|
2529
|
-
var
|
|
1648
|
+
var _internals4 = {
|
|
2530
1649
|
isGitRepo: (cwd) => isGitRepo(cwd),
|
|
2531
1650
|
stageScopedPaths: (cwd, paths) => {
|
|
2532
1651
|
const CHUNK = 200;
|
|
2533
1652
|
for (let i = 0;i < paths.length; i += CHUNK) {
|
|
2534
1653
|
const chunk = paths.slice(i, i + CHUNK);
|
|
2535
|
-
|
|
1654
|
+
_internals.gitExec(["add", "--", ...chunk, ":(exclude,glob)**/.swarm/**"], cwd);
|
|
2536
1655
|
}
|
|
2537
1656
|
},
|
|
2538
1657
|
commitAllowEmpty: (cwd, message) => {
|
|
2539
|
-
|
|
1658
|
+
_internals.gitExec(["commit", "--allow-empty", "--no-verify", "-m", message], cwd);
|
|
2540
1659
|
},
|
|
2541
1660
|
gitHeadSha: (cwd) => {
|
|
2542
|
-
return
|
|
1661
|
+
return _internals.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
2543
1662
|
},
|
|
2544
1663
|
sleep: (ms) => new Promise((resolve4) => setTimeout(resolve4, ms)),
|
|
2545
1664
|
hasExistingTaskCommit: (cwd, taskId) => {
|
|
2546
1665
|
const safeId = scrubTaskIdForGitSubject(taskId);
|
|
2547
1666
|
const escaped = safeId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2548
|
-
const output =
|
|
1667
|
+
const output = _internals.gitExec([
|
|
2549
1668
|
"log",
|
|
2550
1669
|
"--extended-regexp",
|
|
2551
1670
|
`--grep=^swarm\\(task ${escaped}\\):`,
|
|
@@ -2558,8 +1677,8 @@ var _internals8 = {
|
|
|
2558
1677
|
};
|
|
2559
1678
|
|
|
2560
1679
|
// src/turbo/lean/conflicts.ts
|
|
2561
|
-
import * as
|
|
2562
|
-
import * as
|
|
1680
|
+
import * as fs4 from "fs";
|
|
1681
|
+
import * as path4 from "path";
|
|
2563
1682
|
|
|
2564
1683
|
// src/utils/path.ts
|
|
2565
1684
|
function normalizePath(p) {
|
|
@@ -2687,12 +1806,12 @@ function isProtectedPath(normalizedPath) {
|
|
|
2687
1806
|
return false;
|
|
2688
1807
|
}
|
|
2689
1808
|
function readTaskScopes(directory, taskId) {
|
|
2690
|
-
const scopePath =
|
|
1809
|
+
const scopePath = path4.join(directory, ".swarm", "scopes", `scope-${taskId}.json`);
|
|
2691
1810
|
try {
|
|
2692
|
-
if (!
|
|
1811
|
+
if (!fs4.existsSync(scopePath)) {
|
|
2693
1812
|
return null;
|
|
2694
1813
|
}
|
|
2695
|
-
const raw =
|
|
1814
|
+
const raw = fs4.readFileSync(scopePath, "utf-8");
|
|
2696
1815
|
const parsed = JSON.parse(raw);
|
|
2697
1816
|
if (!parsed || !Array.isArray(parsed.files)) {
|
|
2698
1817
|
return null;
|
|
@@ -2706,15 +1825,15 @@ function readTaskScopes(directory, taskId) {
|
|
|
2706
1825
|
// src/utils/spec-hash.ts
|
|
2707
1826
|
import { createHash as createHash3 } from "crypto";
|
|
2708
1827
|
import * as fsSync2 from "fs";
|
|
2709
|
-
import { join as
|
|
1828
|
+
import { join as join5 } from "path";
|
|
2710
1829
|
async function computeSpecHash(directory) {
|
|
2711
|
-
const spec =
|
|
1830
|
+
const spec = _internals5.readEffectiveSpecSync(directory);
|
|
2712
1831
|
if (!spec)
|
|
2713
1832
|
return null;
|
|
2714
1833
|
return createHash3("sha256").update(spec.content, "utf-8").digest("hex");
|
|
2715
1834
|
}
|
|
2716
1835
|
async function isSpecStale(directory, plan) {
|
|
2717
|
-
const currentHash = await
|
|
1836
|
+
const currentHash = await _internals5.computeSpecHash(directory);
|
|
2718
1837
|
if (!plan.specHash) {
|
|
2719
1838
|
return { stale: false };
|
|
2720
1839
|
}
|
|
@@ -2735,7 +1854,7 @@ async function isSpecStale(directory, plan) {
|
|
|
2735
1854
|
return { stale: false };
|
|
2736
1855
|
}
|
|
2737
1856
|
function isObligationPreserving(directory) {
|
|
2738
|
-
const snapshotPath =
|
|
1857
|
+
const snapshotPath = join5(directory, ".swarm", "spec-snapshot.md");
|
|
2739
1858
|
try {
|
|
2740
1859
|
let recordedContent;
|
|
2741
1860
|
try {
|
|
@@ -2743,7 +1862,7 @@ function isObligationPreserving(directory) {
|
|
|
2743
1862
|
} catch {
|
|
2744
1863
|
return false;
|
|
2745
1864
|
}
|
|
2746
|
-
const currentResult =
|
|
1865
|
+
const currentResult = _internals5.readEffectiveSpecSync(directory);
|
|
2747
1866
|
if (!currentResult || !currentResult.content) {
|
|
2748
1867
|
return false;
|
|
2749
1868
|
}
|
|
@@ -2773,7 +1892,7 @@ function isObligationPreserving(directory) {
|
|
|
2773
1892
|
return false;
|
|
2774
1893
|
}
|
|
2775
1894
|
}
|
|
2776
|
-
var
|
|
1895
|
+
var _internals5 = {
|
|
2777
1896
|
computeSpecDiff,
|
|
2778
1897
|
computeSpecHash,
|
|
2779
1898
|
isObligationPreserving,
|
|
@@ -2781,7 +1900,7 @@ var _internals9 = {
|
|
|
2781
1900
|
readEffectiveSpecSync
|
|
2782
1901
|
};
|
|
2783
1902
|
function computeSpecDiff(directory) {
|
|
2784
|
-
const snapshotPath =
|
|
1903
|
+
const snapshotPath = join5(directory, ".swarm", "spec-snapshot.md");
|
|
2785
1904
|
let recordedContent;
|
|
2786
1905
|
try {
|
|
2787
1906
|
const snapshotSize = fsSync2.statSync(snapshotPath).size;
|
|
@@ -2793,7 +1912,7 @@ function computeSpecDiff(directory) {
|
|
|
2793
1912
|
} catch {
|
|
2794
1913
|
return null;
|
|
2795
1914
|
}
|
|
2796
|
-
const spec =
|
|
1915
|
+
const spec = _internals5.readEffectiveSpecSync(directory);
|
|
2797
1916
|
if (!spec) {
|
|
2798
1917
|
return null;
|
|
2799
1918
|
}
|
|
@@ -2887,7 +2006,7 @@ var startupLedgerCheckedWorkspaces = new Set;
|
|
|
2887
2006
|
var ledgerStaleWorkspaces = new Set;
|
|
2888
2007
|
var recoveryMutexes = new Map;
|
|
2889
2008
|
var PLAN_JSON_CACHE_NAMESPACE = "plan-json:validated:v1";
|
|
2890
|
-
var
|
|
2009
|
+
var _internals6 = {
|
|
2891
2010
|
loadPlan,
|
|
2892
2011
|
loadPlanJsonOnly,
|
|
2893
2012
|
regeneratePlanMarkdown,
|
|
@@ -3028,7 +2147,7 @@ async function getLatestLedgerHash(directory) {
|
|
|
3028
2147
|
}
|
|
3029
2148
|
}
|
|
3030
2149
|
async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
3031
|
-
const resolvedWorkspace =
|
|
2150
|
+
const resolvedWorkspace = path5.resolve(directory);
|
|
3032
2151
|
if (!ledgerStaleWorkspaces.has(resolvedWorkspace)) {
|
|
3033
2152
|
return plan;
|
|
3034
2153
|
}
|
|
@@ -3047,7 +2166,7 @@ async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
|
3047
2166
|
return plan;
|
|
3048
2167
|
}
|
|
3049
2168
|
async function parsePlanJsonCached(directory) {
|
|
3050
|
-
const planJsonPath =
|
|
2169
|
+
const planJsonPath = path5.resolve(directory, ".swarm", "plan.json");
|
|
3051
2170
|
return readCachedParsedFile(planJsonPath, PLAN_JSON_CACHE_NAMESPACE, () => readSwarmFileAsync(directory, "plan.json"), (planJsonContent) => {
|
|
3052
2171
|
if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
|
|
3053
2172
|
throw new Error("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
|
|
@@ -3109,13 +2228,13 @@ async function isPlanMdInSync(directory, plan, cache) {
|
|
|
3109
2228
|
}
|
|
3110
2229
|
async function regeneratePlanMarkdown(directory, plan) {
|
|
3111
2230
|
assertProjectRoot(directory);
|
|
3112
|
-
const swarmDir =
|
|
2231
|
+
const swarmDir = path5.resolve(directory, ".swarm");
|
|
3113
2232
|
const contentHash = computePlanContentHash(plan);
|
|
3114
2233
|
const markdown = derivePlanMarkdown(plan);
|
|
3115
2234
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3116
2235
|
${markdown}`;
|
|
3117
|
-
const mdPath =
|
|
3118
|
-
const mdTempPath =
|
|
2236
|
+
const mdPath = path5.join(swarmDir, "plan.md");
|
|
2237
|
+
const mdTempPath = path5.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3119
2238
|
try {
|
|
3120
2239
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
3121
2240
|
renameSync2(mdTempPath, mdPath);
|
|
@@ -3140,7 +2259,7 @@ async function loadPlan(directory, cache) {
|
|
|
3140
2259
|
const inSync = await isPlanMdInSync(directory, validated, cache);
|
|
3141
2260
|
if (!inSync) {
|
|
3142
2261
|
try {
|
|
3143
|
-
await
|
|
2262
|
+
await _internals6.regeneratePlanMarkdown(directory, validated);
|
|
3144
2263
|
} catch (regenError) {
|
|
3145
2264
|
warn(`Failed to regenerate plan.md: ${regenError instanceof Error ? regenError.message : String(regenError)}. Proceeding with plan.json only.`);
|
|
3146
2265
|
}
|
|
@@ -3148,7 +2267,7 @@ async function loadPlan(directory, cache) {
|
|
|
3148
2267
|
if (await ledgerExists(directory)) {
|
|
3149
2268
|
const planHash = computePlanHash(validated);
|
|
3150
2269
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
3151
|
-
const resolvedWorkspace =
|
|
2270
|
+
const resolvedWorkspace = path5.resolve(directory);
|
|
3152
2271
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
3153
2272
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
3154
2273
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
@@ -3223,7 +2342,7 @@ async function loadPlan(directory, cache) {
|
|
|
3223
2342
|
try {
|
|
3224
2343
|
assertProjectRoot(directory);
|
|
3225
2344
|
const diffInfo = computeSpecDiff(directory);
|
|
3226
|
-
const specStalenessPath =
|
|
2345
|
+
const specStalenessPath = path5.join(directory, ".swarm", "spec-staleness.json");
|
|
3227
2346
|
await fsPromises.writeFile(specStalenessPath, JSON.stringify({
|
|
3228
2347
|
type: "spec_stale_detected",
|
|
3229
2348
|
timestamp: new Date().toISOString(),
|
|
@@ -3239,7 +2358,7 @@ async function loadPlan(directory, cache) {
|
|
|
3239
2358
|
} catch {}
|
|
3240
2359
|
try {
|
|
3241
2360
|
assertProjectRoot(directory);
|
|
3242
|
-
const eventsPath =
|
|
2361
|
+
const eventsPath = path5.join(directory, ".swarm", "events.jsonl");
|
|
3243
2362
|
const event = {
|
|
3244
2363
|
type: "spec_stale_detected",
|
|
3245
2364
|
timestamp: new Date().toISOString(),
|
|
@@ -3305,11 +2424,11 @@ async function loadPlan(directory, cache) {
|
|
|
3305
2424
|
return migrated;
|
|
3306
2425
|
}
|
|
3307
2426
|
if (await ledgerExists(directory)) {
|
|
3308
|
-
const resolvedDir =
|
|
2427
|
+
const resolvedDir = path5.resolve(directory);
|
|
3309
2428
|
const existingMutex = recoveryMutexes.get(resolvedDir);
|
|
3310
2429
|
if (existingMutex) {
|
|
3311
2430
|
await existingMutex;
|
|
3312
|
-
const postRecoveryPlan = await
|
|
2431
|
+
const postRecoveryPlan = await _internals6.loadPlanJsonOnly(directory);
|
|
3313
2432
|
if (postRecoveryPlan)
|
|
3314
2433
|
return postRecoveryPlan;
|
|
3315
2434
|
}
|
|
@@ -3369,7 +2488,7 @@ async function loadPlan(directory, cache) {
|
|
|
3369
2488
|
return null;
|
|
3370
2489
|
}
|
|
3371
2490
|
async function savePlanWithAutoAcknowledgedRemovals(directory, plan, source, reason, options) {
|
|
3372
|
-
const existing = await
|
|
2491
|
+
const existing = await _internals6.loadPlanJsonOnly(directory);
|
|
3373
2492
|
const newIds = new Set;
|
|
3374
2493
|
for (const phase of plan.phases) {
|
|
3375
2494
|
for (const task of phase.tasks)
|
|
@@ -3419,7 +2538,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3419
2538
|
}
|
|
3420
2539
|
if (options?.preserveCompletedStatuses !== false) {
|
|
3421
2540
|
try {
|
|
3422
|
-
const currentPlan2 = await
|
|
2541
|
+
const currentPlan2 = await _internals6.loadPlanJsonOnly(directory);
|
|
3423
2542
|
if (currentPlan2) {
|
|
3424
2543
|
const completedTaskIds = new Set;
|
|
3425
2544
|
for (const phase of currentPlan2.phases) {
|
|
@@ -3441,7 +2560,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3441
2560
|
} catch {}
|
|
3442
2561
|
}
|
|
3443
2562
|
derivePhaseStatusesInPlace(validated);
|
|
3444
|
-
const currentPlan = await
|
|
2563
|
+
const currentPlan = await _internals6.loadPlanJsonOnly(directory);
|
|
3445
2564
|
const planId = derivePlanId(validated);
|
|
3446
2565
|
const planHashForInit = computePlanHash(validated);
|
|
3447
2566
|
if (!await ledgerExists(directory)) {
|
|
@@ -3456,9 +2575,9 @@ async function savePlan(directory, plan, options) {
|
|
|
3456
2575
|
} else {
|
|
3457
2576
|
const existingEvents = await readLedgerEvents(directory);
|
|
3458
2577
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
3459
|
-
const swarmDir2 =
|
|
3460
|
-
const oldLedgerPath =
|
|
3461
|
-
const oldLedgerBackupPath =
|
|
2578
|
+
const swarmDir2 = path5.resolve(directory, ".swarm");
|
|
2579
|
+
const oldLedgerPath = path5.join(swarmDir2, "plan-ledger.jsonl");
|
|
2580
|
+
const oldLedgerBackupPath = path5.join(swarmDir2, `plan-ledger.backup-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
3462
2581
|
let backupExists = false;
|
|
3463
2582
|
if (existsSync4(oldLedgerPath)) {
|
|
3464
2583
|
try {
|
|
@@ -3496,7 +2615,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3496
2615
|
}
|
|
3497
2616
|
}
|
|
3498
2617
|
if (initSucceeded && backupExists) {
|
|
3499
|
-
const archivePath =
|
|
2618
|
+
const archivePath = path5.join(swarmDir2, `plan-ledger.archived-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
3500
2619
|
try {
|
|
3501
2620
|
renameSync2(oldLedgerBackupPath, archivePath);
|
|
3502
2621
|
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived old ledger to ${archivePath} and reinitializing.`);
|
|
@@ -3521,7 +2640,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3521
2640
|
const toRemove = archivedSiblings.slice(0, archivedSiblings.length - MAX_ARCHIVED_SIBLINGS);
|
|
3522
2641
|
for (const file of toRemove) {
|
|
3523
2642
|
try {
|
|
3524
|
-
unlinkSync2(
|
|
2643
|
+
unlinkSync2(path5.join(swarmDir2, file));
|
|
3525
2644
|
} catch {}
|
|
3526
2645
|
}
|
|
3527
2646
|
}
|
|
@@ -3623,7 +2742,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3623
2742
|
expectedHash: currentHash,
|
|
3624
2743
|
planHashAfter: hashAfter,
|
|
3625
2744
|
verifyValid: async () => {
|
|
3626
|
-
const onDisk = await
|
|
2745
|
+
const onDisk = await _internals6.loadPlanJsonOnly(directory);
|
|
3627
2746
|
if (!onDisk)
|
|
3628
2747
|
return true;
|
|
3629
2748
|
for (const p of onDisk.phases) {
|
|
@@ -3661,7 +2780,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3661
2780
|
expectedHash: currentHash,
|
|
3662
2781
|
planHashAfter: hashAfter,
|
|
3663
2782
|
verifyValid: async () => {
|
|
3664
|
-
const onDisk = await
|
|
2783
|
+
const onDisk = await _internals6.loadPlanJsonOnly(directory);
|
|
3665
2784
|
if (!onDisk)
|
|
3666
2785
|
return true;
|
|
3667
2786
|
for (const p of onDisk.phases) {
|
|
@@ -3701,9 +2820,9 @@ async function savePlan(directory, plan, options) {
|
|
|
3701
2820
|
source: "savePlan_manager"
|
|
3702
2821
|
});
|
|
3703
2822
|
}
|
|
3704
|
-
const swarmDir =
|
|
3705
|
-
const planPath =
|
|
3706
|
-
const tempPath =
|
|
2823
|
+
const swarmDir = path5.resolve(directory, ".swarm");
|
|
2824
|
+
const planPath = path5.join(swarmDir, "plan.json");
|
|
2825
|
+
const tempPath = path5.join(swarmDir, `plan.json.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3707
2826
|
try {
|
|
3708
2827
|
await bunWrite(tempPath, JSON.stringify(projectedPlan, null, 2));
|
|
3709
2828
|
renameSync2(tempPath, planPath);
|
|
@@ -3714,7 +2833,7 @@ async function savePlan(directory, plan, options) {
|
|
|
3714
2833
|
}
|
|
3715
2834
|
invalidateCachedArtifact(planPath);
|
|
3716
2835
|
try {
|
|
3717
|
-
const markerPath =
|
|
2836
|
+
const markerPath = path5.join(swarmDir, ".plan-write-marker");
|
|
3718
2837
|
const inProgressMarker = JSON.stringify({
|
|
3719
2838
|
source: "plan_manager",
|
|
3720
2839
|
timestamp: new Date().toISOString(),
|
|
@@ -3729,8 +2848,8 @@ async function savePlan(directory, plan, options) {
|
|
|
3729
2848
|
const markdown = derivePlanMarkdown(projectedPlan);
|
|
3730
2849
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3731
2850
|
${markdown}`;
|
|
3732
|
-
const mdPath =
|
|
3733
|
-
const mdTempPath =
|
|
2851
|
+
const mdPath = path5.join(swarmDir, "plan.md");
|
|
2852
|
+
const mdTempPath = path5.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3734
2853
|
try {
|
|
3735
2854
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
3736
2855
|
renameSync2(mdTempPath, mdPath);
|
|
@@ -3752,7 +2871,7 @@ ${markdown}`;
|
|
|
3752
2871
|
} catch {}
|
|
3753
2872
|
}
|
|
3754
2873
|
try {
|
|
3755
|
-
const markerPath =
|
|
2874
|
+
const markerPath = path5.join(swarmDir, ".plan-write-marker");
|
|
3756
2875
|
const tasksCount = projectedPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
3757
2876
|
const marker = JSON.stringify({
|
|
3758
2877
|
source: "plan_manager",
|
|
@@ -3800,15 +2919,15 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
3800
2919
|
const targetPlan = plan ?? await replayFromLedger(directory);
|
|
3801
2920
|
if (!targetPlan)
|
|
3802
2921
|
return null;
|
|
3803
|
-
const swarmDir =
|
|
3804
|
-
const planPath =
|
|
3805
|
-
const mdPath =
|
|
3806
|
-
|
|
3807
|
-
const tempPlanPath =
|
|
2922
|
+
const swarmDir = path5.join(directory, ".swarm");
|
|
2923
|
+
const planPath = path5.join(swarmDir, "plan.json");
|
|
2924
|
+
const mdPath = path5.join(swarmDir, "plan.md");
|
|
2925
|
+
mkdirSync3(swarmDir, { recursive: true });
|
|
2926
|
+
const tempPlanPath = path5.join(swarmDir, `plan.json.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3808
2927
|
{
|
|
3809
2928
|
const fd = openSync(tempPlanPath, "w");
|
|
3810
2929
|
try {
|
|
3811
|
-
|
|
2930
|
+
writeFileSync2(fd, JSON.stringify(targetPlan, null, 2), "utf8");
|
|
3812
2931
|
fsyncSync(fd);
|
|
3813
2932
|
} finally {
|
|
3814
2933
|
closeSync(fd);
|
|
@@ -3817,7 +2936,7 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
3817
2936
|
renameSync2(tempPlanPath, planPath);
|
|
3818
2937
|
invalidateCachedArtifact(planPath);
|
|
3819
2938
|
try {
|
|
3820
|
-
const markerPath =
|
|
2939
|
+
const markerPath = path5.join(swarmDir, ".plan-write-marker");
|
|
3821
2940
|
const inProgressMarker = JSON.stringify({
|
|
3822
2941
|
source: "plan_manager",
|
|
3823
2942
|
timestamp: new Date().toISOString(),
|
|
@@ -3832,13 +2951,13 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
3832
2951
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
3833
2952
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3834
2953
|
${markdown}`;
|
|
3835
|
-
const tempMdPath =
|
|
2954
|
+
const tempMdPath = path5.join(swarmDir, `plan.md.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3836
2955
|
await bunWrite(tempMdPath, markdownWithHash);
|
|
3837
2956
|
renameSync2(tempMdPath, mdPath);
|
|
3838
2957
|
invalidateCachedArtifact(mdPath);
|
|
3839
2958
|
} finally {
|
|
3840
2959
|
try {
|
|
3841
|
-
const markerPath =
|
|
2960
|
+
const markerPath = path5.join(swarmDir, ".plan-write-marker");
|
|
3842
2961
|
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
3843
2962
|
const marker = JSON.stringify({
|
|
3844
2963
|
source: "plan_manager",
|
|
@@ -3902,14 +3021,14 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
3902
3021
|
planHashAfter: hashAfter,
|
|
3903
3022
|
source: "close_terminal"
|
|
3904
3023
|
});
|
|
3905
|
-
const swarmDir =
|
|
3906
|
-
const planPath =
|
|
3907
|
-
const tempPlanPath =
|
|
3024
|
+
const swarmDir = path5.join(directory, ".swarm");
|
|
3025
|
+
const planPath = path5.join(swarmDir, "plan.json");
|
|
3026
|
+
const tempPlanPath = path5.join(swarmDir, `plan.json.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3908
3027
|
await bunWrite(tempPlanPath, JSON.stringify(validated, null, 2));
|
|
3909
3028
|
renameSync2(tempPlanPath, planPath);
|
|
3910
3029
|
invalidateCachedArtifact(planPath);
|
|
3911
3030
|
try {
|
|
3912
|
-
const markerPath =
|
|
3031
|
+
const markerPath = path5.join(swarmDir, ".plan-write-marker");
|
|
3913
3032
|
const inProgressMarker = JSON.stringify({
|
|
3914
3033
|
source: "plan_manager_close",
|
|
3915
3034
|
timestamp: new Date().toISOString(),
|
|
@@ -3920,18 +3039,18 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
3920
3039
|
await bunWrite(markerPath, inProgressMarker);
|
|
3921
3040
|
} catch {}
|
|
3922
3041
|
try {
|
|
3923
|
-
const mdPath =
|
|
3042
|
+
const mdPath = path5.join(swarmDir, "plan.md");
|
|
3924
3043
|
const contentHash = computePlanContentHash(validated);
|
|
3925
3044
|
const markdown = derivePlanMarkdown(validated);
|
|
3926
3045
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3927
3046
|
${markdown}`;
|
|
3928
|
-
const mdTempPath =
|
|
3047
|
+
const mdTempPath = path5.join(swarmDir, `plan.md.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3929
3048
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
3930
3049
|
renameSync2(mdTempPath, mdPath);
|
|
3931
3050
|
invalidateCachedArtifact(mdPath);
|
|
3932
3051
|
} finally {
|
|
3933
3052
|
try {
|
|
3934
|
-
const markerPath =
|
|
3053
|
+
const markerPath = path5.join(swarmDir, ".plan-write-marker");
|
|
3935
3054
|
const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
3936
3055
|
const marker = JSON.stringify({
|
|
3937
3056
|
source: "plan_manager_close",
|
|
@@ -3959,7 +3078,7 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
3959
3078
|
return "pending";
|
|
3960
3079
|
};
|
|
3961
3080
|
{
|
|
3962
|
-
const currentPlan = await
|
|
3081
|
+
const currentPlan = await _internals6.loadPlanJsonOnly(directory);
|
|
3963
3082
|
if (currentPlan) {
|
|
3964
3083
|
const currentTask = currentPlan.phases.flatMap((p) => p.tasks).find((t) => t.id === taskId);
|
|
3965
3084
|
const settled = currentTask && currentTask.status !== "pending" && currentTask.status !== "in_progress";
|
|
@@ -3972,7 +3091,7 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
3972
3091
|
}
|
|
3973
3092
|
const MAX_OUTER_RETRIES = 1;
|
|
3974
3093
|
for (let attempt = 0;attempt <= MAX_OUTER_RETRIES; attempt++) {
|
|
3975
|
-
const plan = await
|
|
3094
|
+
const plan = await _internals6.loadPlan(directory);
|
|
3976
3095
|
if (plan === null) {
|
|
3977
3096
|
throw new Error(`Plan not found in directory: ${directory}`);
|
|
3978
3097
|
}
|
|
@@ -4003,7 +3122,7 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
4003
3122
|
if (status === "completed" || status === "blocked") {
|
|
4004
3123
|
const recordedTask = updatedPlan.phases.flatMap((phase) => phase.tasks).find((candidate) => candidate.id === taskId);
|
|
4005
3124
|
try {
|
|
4006
|
-
await
|
|
3125
|
+
await _internals6.recordTaskAttempt(directory, {
|
|
4007
3126
|
taskId,
|
|
4008
3127
|
agent: "plan-status",
|
|
4009
3128
|
outcome: status === "completed" ? "pass" : "fail",
|
|
@@ -4014,8 +3133,8 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
4014
3133
|
warn(`[plan/manager] run-memory record for ${taskId} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4015
3134
|
}
|
|
4016
3135
|
}
|
|
4017
|
-
if (status === "completed" &&
|
|
4018
|
-
const mergeFailure =
|
|
3136
|
+
if (status === "completed" && _internals6.isGitRepo(directory) && _internals6.isEpicModeActiveForProject(directory)) {
|
|
3137
|
+
const mergeFailure = _internals6.getWorktreeMergeFailure(taskId);
|
|
4019
3138
|
if (mergeFailure) {
|
|
4020
3139
|
criticalWarn(`[plan/manager] Rule 2 auto-commit SKIPPED for ${taskId}: worktree merge-back ${mergeFailure.outcome} at stage '${mergeFailure.stage}'. The task's changes are NOT in the main tree, so no completion marker is written (Rule 3 must not treat this task as satisfied). Resolve the preserved worktree, then re-run the task. Detail: ${mergeFailure.message}`);
|
|
4021
3140
|
return updatedPlan;
|
|
@@ -4029,8 +3148,8 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
4029
3148
|
break;
|
|
4030
3149
|
}
|
|
4031
3150
|
}
|
|
4032
|
-
const canonicalScope =
|
|
4033
|
-
await
|
|
3151
|
+
const canonicalScope = _internals6.readTaskScopes(directory, taskId);
|
|
3152
|
+
await _internals6.commitTaskCompletion(directory, taskId, taskDescription, canonicalScope ?? undefined);
|
|
4034
3153
|
} catch (commitErr) {
|
|
4035
3154
|
criticalWarn(`[plan/manager] Rule 2 auto-commit for ${taskId} threw (non-fatal): ${commitErr instanceof Error ? commitErr.message : String(commitErr)}`);
|
|
4036
3155
|
}
|
|
@@ -4343,19 +3462,19 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
4343
3462
|
// src/evidence/manager.ts
|
|
4344
3463
|
init_zod();
|
|
4345
3464
|
import {
|
|
4346
|
-
mkdirSync as
|
|
3465
|
+
mkdirSync as mkdirSync4,
|
|
4347
3466
|
readdirSync as readdirSync2,
|
|
4348
3467
|
realpathSync as realpathSync2,
|
|
4349
3468
|
renameSync as renameSync3,
|
|
4350
3469
|
rmSync,
|
|
4351
3470
|
statSync as statSync2
|
|
4352
3471
|
} from "fs";
|
|
4353
|
-
import * as
|
|
4354
|
-
import * as
|
|
3472
|
+
import * as fs7 from "fs/promises";
|
|
3473
|
+
import * as path7 from "path";
|
|
4355
3474
|
|
|
4356
3475
|
// src/evaluation/retention.ts
|
|
4357
|
-
import * as
|
|
4358
|
-
import * as
|
|
3476
|
+
import * as fs5 from "fs/promises";
|
|
3477
|
+
import * as path6 from "path";
|
|
4359
3478
|
function artifactKey(artifact) {
|
|
4360
3479
|
return `${artifact.namespace}/${artifact.id}`;
|
|
4361
3480
|
}
|
|
@@ -4363,10 +3482,10 @@ function validId(value) {
|
|
|
4363
3482
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/.test(value);
|
|
4364
3483
|
}
|
|
4365
3484
|
async function evaluationRunCandidates(directory) {
|
|
4366
|
-
const root =
|
|
3485
|
+
const root = path6.join(directory, ".swarm", "evolution", "runs");
|
|
4367
3486
|
let entries = [];
|
|
4368
3487
|
try {
|
|
4369
|
-
entries = await
|
|
3488
|
+
entries = await fs5.readdir(root, { withFileTypes: true });
|
|
4370
3489
|
} catch (error2) {
|
|
4371
3490
|
if (error2.code === "ENOENT") {
|
|
4372
3491
|
return { candidates: [], corrupt: [] };
|
|
@@ -4382,9 +3501,9 @@ async function evaluationRunCandidates(directory) {
|
|
|
4382
3501
|
if (!validId(id))
|
|
4383
3502
|
continue;
|
|
4384
3503
|
const artifact = { namespace: "evaluation-run", id };
|
|
4385
|
-
const target =
|
|
3504
|
+
const target = path6.join(root, entry.name);
|
|
4386
3505
|
try {
|
|
4387
|
-
const run = EvaluationRunV1Schema.parse(JSON.parse(await
|
|
3506
|
+
const run = EvaluationRunV1Schema.parse(JSON.parse(await fs5.readFile(target, "utf8")));
|
|
4388
3507
|
if (run.runId !== id)
|
|
4389
3508
|
throw new Error("run id/path mismatch");
|
|
4390
3509
|
candidates.push({
|
|
@@ -4400,10 +3519,10 @@ async function evaluationRunCandidates(directory) {
|
|
|
4400
3519
|
return { candidates, corrupt };
|
|
4401
3520
|
}
|
|
4402
3521
|
async function gateAuditCandidates(directory) {
|
|
4403
|
-
const root =
|
|
3522
|
+
const root = path6.join(directory, ".swarm", "evidence", "gate-audit");
|
|
4404
3523
|
let entries = [];
|
|
4405
3524
|
try {
|
|
4406
|
-
entries = await
|
|
3525
|
+
entries = await fs5.readdir(root, { withFileTypes: true });
|
|
4407
3526
|
} catch (error2) {
|
|
4408
3527
|
if (error2.code === "ENOENT") {
|
|
4409
3528
|
return { candidates: [], corrupt: [] };
|
|
@@ -4416,9 +3535,9 @@ async function gateAuditCandidates(directory) {
|
|
|
4416
3535
|
if (!entry.isDirectory() || !validId(entry.name))
|
|
4417
3536
|
continue;
|
|
4418
3537
|
const artifact = { namespace: "gate-audit", id: entry.name };
|
|
4419
|
-
const target =
|
|
3538
|
+
const target = path6.join(root, entry.name);
|
|
4420
3539
|
try {
|
|
4421
|
-
const result = GateAuditResultV1Schema.parse(JSON.parse(await
|
|
3540
|
+
const result = GateAuditResultV1Schema.parse(JSON.parse(await fs5.readFile(path6.join(target, "results.json"), "utf8")));
|
|
4422
3541
|
if (result.runId !== entry.name)
|
|
4423
3542
|
throw new Error("run id/path mismatch");
|
|
4424
3543
|
candidates.push({
|
|
@@ -4449,10 +3568,10 @@ async function protectedArtifacts(directory) {
|
|
|
4449
3568
|
}
|
|
4450
3569
|
async function removeCandidate(candidate) {
|
|
4451
3570
|
if (candidate.kind === "directory") {
|
|
4452
|
-
await
|
|
3571
|
+
await fs5.rm(candidate.target, { recursive: true, force: true });
|
|
4453
3572
|
return;
|
|
4454
3573
|
}
|
|
4455
|
-
await
|
|
3574
|
+
await fs5.unlink(candidate.target);
|
|
4456
3575
|
}
|
|
4457
3576
|
var _retentionInternals = {
|
|
4458
3577
|
protectedArtifacts,
|
|
@@ -4460,7 +3579,7 @@ var _retentionInternals = {
|
|
|
4460
3579
|
};
|
|
4461
3580
|
async function archiveEvaluationArtifacts(args) {
|
|
4462
3581
|
assertProjectRoot(args.directory);
|
|
4463
|
-
return withEvidenceLock(args.directory,
|
|
3582
|
+
return withEvidenceLock(args.directory, path6.join("evolution", "retention-index"), "evaluation-retention", "archive", async () => {
|
|
4464
3583
|
const inventory = await retentionCandidates(args.directory);
|
|
4465
3584
|
let protectedKeys = await _retentionInternals.protectedArtifacts(args.directory);
|
|
4466
3585
|
const protectedRefs = inventory.candidates.filter((candidate) => protectedKeys.has(artifactKey(candidate.artifact))).map((candidate) => candidate.artifact);
|
|
@@ -4509,7 +3628,7 @@ async function archiveEvaluationArtifacts(args) {
|
|
|
4509
3628
|
}
|
|
4510
3629
|
|
|
4511
3630
|
// src/evidence/documents-retention.ts
|
|
4512
|
-
import * as
|
|
3631
|
+
import * as fs6 from "fs";
|
|
4513
3632
|
import * as fsp from "fs/promises";
|
|
4514
3633
|
import * as readline from "readline";
|
|
4515
3634
|
var EVIDENCE_CACHE_FILE = "evidence-cache/documents.jsonl";
|
|
@@ -4517,13 +3636,13 @@ var MAX_READ_BYTES = 100 * 1024 * 1024;
|
|
|
4517
3636
|
var RENAME_MAX_ATTEMPTS = 5;
|
|
4518
3637
|
var RENAME_BACKOFF_MS = 10;
|
|
4519
3638
|
var RENAME_RETRY_CODES = new Set(["EPERM", "EBUSY", "ENOTEMPTY", "EACCES"]);
|
|
4520
|
-
var
|
|
3639
|
+
var _internals7 = {
|
|
4521
3640
|
stat: (p) => fsp.stat(p),
|
|
4522
|
-
createReadStream: (p, options) =>
|
|
4523
|
-
openSync: (p, flags) =>
|
|
4524
|
-
writeSync: (fd, data, position) =>
|
|
4525
|
-
fsyncSync: (fd) =>
|
|
4526
|
-
closeSync: (fd) =>
|
|
3641
|
+
createReadStream: (p, options) => fs6.createReadStream(p, options),
|
|
3642
|
+
openSync: (p, flags) => fs6.openSync(p, flags),
|
|
3643
|
+
writeSync: (fd, data, position) => fs6.writeSync(fd, data, position),
|
|
3644
|
+
fsyncSync: (fd) => fs6.fsyncSync(fd),
|
|
3645
|
+
closeSync: (fd) => fs6.closeSync(fd),
|
|
4527
3646
|
renameWithRetry: atomicRenameWithRetry,
|
|
4528
3647
|
unlink: (p) => fsp.unlink(p)
|
|
4529
3648
|
};
|
|
@@ -4559,9 +3678,9 @@ function emptyResult(dryRun) {
|
|
|
4559
3678
|
};
|
|
4560
3679
|
}
|
|
4561
3680
|
async function readCacheRows(filePath) {
|
|
4562
|
-
const stat2 = await
|
|
3681
|
+
const stat2 = await _internals7.stat(filePath);
|
|
4563
3682
|
const bytesBefore = stat2.size;
|
|
4564
|
-
const stream =
|
|
3683
|
+
const stream = _internals7.createReadStream(filePath, {
|
|
4565
3684
|
encoding: "utf8",
|
|
4566
3685
|
highWaterMark: 64 * 1024
|
|
4567
3686
|
});
|
|
@@ -4637,23 +3756,23 @@ async function rewriteAtomic(filePath, survivors) {
|
|
|
4637
3756
|
`)}
|
|
4638
3757
|
` : "";
|
|
4639
3758
|
let succeeded = false;
|
|
4640
|
-
const fd =
|
|
3759
|
+
const fd = _internals7.openSync(tmpPath, "w");
|
|
4641
3760
|
try {
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
await
|
|
3761
|
+
_internals7.writeSync(fd, payload, 0);
|
|
3762
|
+
_internals7.fsyncSync(fd);
|
|
3763
|
+
_internals7.closeSync(fd);
|
|
3764
|
+
await _internals7.renameWithRetry(tmpPath, filePath);
|
|
4646
3765
|
succeeded = true;
|
|
4647
3766
|
} catch (err) {
|
|
4648
3767
|
try {
|
|
4649
|
-
|
|
3768
|
+
_internals7.closeSync(fd);
|
|
4650
3769
|
} catch {}
|
|
4651
3770
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4652
3771
|
throw new Error(`documents-cache rewrite failed: ${msg}`);
|
|
4653
3772
|
} finally {
|
|
4654
3773
|
if (!succeeded) {
|
|
4655
3774
|
try {
|
|
4656
|
-
await
|
|
3775
|
+
await _internals7.unlink(tmpPath);
|
|
4657
3776
|
} catch {}
|
|
4658
3777
|
}
|
|
4659
3778
|
}
|
|
@@ -4666,7 +3785,7 @@ async function pruneEvidenceDocuments(args) {
|
|
|
4666
3785
|
const filePath = validateSwarmPath(args.directory, EVIDENCE_CACHE_FILE);
|
|
4667
3786
|
let stat2;
|
|
4668
3787
|
try {
|
|
4669
|
-
stat2 = await
|
|
3788
|
+
stat2 = await _internals7.stat(filePath);
|
|
4670
3789
|
} catch (err) {
|
|
4671
3790
|
const code = err?.code;
|
|
4672
3791
|
if (code === "ENOENT")
|
|
@@ -4767,21 +3886,21 @@ function isValidEvidenceType(type) {
|
|
|
4767
3886
|
var sanitizeTaskId2 = sanitizeTaskId;
|
|
4768
3887
|
function validateProjectRoot(directory) {
|
|
4769
3888
|
assertProjectRoot(directory, {
|
|
4770
|
-
realpathSync:
|
|
4771
|
-
statSync:
|
|
3889
|
+
realpathSync: _internals8.realpathSync,
|
|
3890
|
+
statSync: _internals8.statSync
|
|
4772
3891
|
}, "evidence");
|
|
4773
3892
|
}
|
|
4774
3893
|
async function saveEvidence(directory, taskId, evidence, abortSignal) {
|
|
4775
3894
|
abortSignal?.throwIfAborted();
|
|
4776
|
-
|
|
3895
|
+
_internals8.validateProjectRoot(directory);
|
|
4777
3896
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
4778
|
-
const relativePath =
|
|
3897
|
+
const relativePath = path7.join("evidence", sanitizedTaskId, "evidence.json");
|
|
4779
3898
|
validateSwarmPath(directory, relativePath);
|
|
4780
|
-
|
|
3899
|
+
_internals8.validateEvidence(evidence);
|
|
4781
3900
|
return withEvidenceLock(directory, relativePath, "evidence-manager", sanitizedTaskId, async () => {
|
|
4782
3901
|
abortSignal?.throwIfAborted();
|
|
4783
3902
|
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
4784
|
-
const evidenceDir =
|
|
3903
|
+
const evidenceDir = path7.dirname(evidencePath);
|
|
4785
3904
|
let bundle;
|
|
4786
3905
|
const existingContent = await readSwarmFileAsync(directory, relativePath);
|
|
4787
3906
|
abortSignal?.throwIfAborted();
|
|
@@ -4824,8 +3943,8 @@ async function saveEvidence(directory, taskId, evidence, abortSignal) {
|
|
|
4824
3943
|
if (bundleJson.length > EVIDENCE_MAX_JSON_BYTES) {
|
|
4825
3944
|
throw new Error(`Evidence bundle size (${bundleJson.length} bytes) exceeds maximum (${EVIDENCE_MAX_JSON_BYTES} bytes)`);
|
|
4826
3945
|
}
|
|
4827
|
-
|
|
4828
|
-
const tempPath =
|
|
3946
|
+
mkdirSync4(evidenceDir, { recursive: true });
|
|
3947
|
+
const tempPath = path7.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
4829
3948
|
try {
|
|
4830
3949
|
await bunWrite(tempPath, bundleJson);
|
|
4831
3950
|
abortSignal?.throwIfAborted();
|
|
@@ -4871,7 +3990,7 @@ function wrapFlatRetrospective(flatEntry, taskId) {
|
|
|
4871
3990
|
}
|
|
4872
3991
|
async function loadEvidence(directory, taskId, options) {
|
|
4873
3992
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
4874
|
-
const relativePath =
|
|
3993
|
+
const relativePath = path7.join("evidence", sanitizedTaskId, "evidence.json");
|
|
4875
3994
|
if (relativePath.length > 4096) {
|
|
4876
3995
|
return { status: "not_found" };
|
|
4877
3996
|
}
|
|
@@ -4887,7 +4006,7 @@ async function loadEvidence(directory, taskId, options) {
|
|
|
4887
4006
|
return { status: "invalid_schema", errors: ["Invalid JSON"] };
|
|
4888
4007
|
}
|
|
4889
4008
|
if (isFlatRetrospective(parsed)) {
|
|
4890
|
-
const wrappedBundle =
|
|
4009
|
+
const wrappedBundle = _internals8.wrapFlatRetrospective(parsed, sanitizedTaskId);
|
|
4891
4010
|
try {
|
|
4892
4011
|
const validated = EvidenceBundleSchema.parse(wrappedBundle);
|
|
4893
4012
|
if (options?.migrate === false) {
|
|
@@ -4896,12 +4015,12 @@ async function loadEvidence(directory, taskId, options) {
|
|
|
4896
4015
|
try {
|
|
4897
4016
|
validateProjectRoot(directory);
|
|
4898
4017
|
await withEvidenceLock(directory, relativePath, "evidence-loader", sanitizedTaskId, async () => {
|
|
4899
|
-
const evidenceDir =
|
|
4018
|
+
const evidenceDir = path7.dirname(evidencePath);
|
|
4900
4019
|
const bundleJson = JSON.stringify(validated);
|
|
4901
|
-
const tempPath =
|
|
4020
|
+
const tempPath = path7.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
4902
4021
|
try {
|
|
4903
4022
|
await bunWrite(tempPath, bundleJson);
|
|
4904
|
-
await
|
|
4023
|
+
await fs7.rename(tempPath, evidencePath);
|
|
4905
4024
|
invalidateCachedArtifact(evidencePath);
|
|
4906
4025
|
} catch (writeError) {
|
|
4907
4026
|
try {
|
|
@@ -4944,7 +4063,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
4944
4063
|
}
|
|
4945
4064
|
const taskIds = [];
|
|
4946
4065
|
for (const entry of entries) {
|
|
4947
|
-
const entryPath =
|
|
4066
|
+
const entryPath = path7.join(evidenceBasePath, entry);
|
|
4948
4067
|
try {
|
|
4949
4068
|
const stats = statSync2(entryPath);
|
|
4950
4069
|
if (!stats.isDirectory()) {
|
|
@@ -4967,7 +4086,7 @@ async function deleteEvidence(directory, taskId) {
|
|
|
4967
4086
|
} catch {
|
|
4968
4087
|
return false;
|
|
4969
4088
|
}
|
|
4970
|
-
const relativePath =
|
|
4089
|
+
const relativePath = path7.join("evidence", sanitizedTaskId);
|
|
4971
4090
|
const evidenceDir = validateSwarmPath(directory, relativePath);
|
|
4972
4091
|
try {
|
|
4973
4092
|
statSync2(evidenceDir);
|
|
@@ -4983,18 +4102,18 @@ async function deleteEvidence(directory, taskId) {
|
|
|
4983
4102
|
}
|
|
4984
4103
|
}
|
|
4985
4104
|
async function checkRequirementCoverage(phase, directory) {
|
|
4986
|
-
const relativePath =
|
|
4987
|
-
const absolutePath =
|
|
4105
|
+
const relativePath = path7.join("evidence", `req-coverage-phase-${phase}.json`);
|
|
4106
|
+
const absolutePath = path7.resolve(directory, ".swarm", relativePath);
|
|
4988
4107
|
try {
|
|
4989
|
-
await
|
|
4108
|
+
await fs7.access(absolutePath);
|
|
4990
4109
|
return { exists: true, path: absolutePath };
|
|
4991
4110
|
} catch {
|
|
4992
4111
|
return { exists: false, path: absolutePath };
|
|
4993
4112
|
}
|
|
4994
4113
|
}
|
|
4995
4114
|
async function archiveEvidence(directory, maxAgeDays, maxBundles, options) {
|
|
4996
|
-
const taskIds = await
|
|
4997
|
-
const cutoffDate = options?.now ? new Date(options.now) :
|
|
4115
|
+
const taskIds = await _internals8.listEvidenceTaskIds(directory);
|
|
4116
|
+
const cutoffDate = options?.now ? new Date(options.now) : _internals8.now();
|
|
4998
4117
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
4999
4118
|
const cutoffIso = cutoffDate.toISOString();
|
|
5000
4119
|
const selected = new Set;
|
|
@@ -5006,7 +4125,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles, options) {
|
|
|
5006
4125
|
for (const taskId of taskIds) {
|
|
5007
4126
|
let result;
|
|
5008
4127
|
try {
|
|
5009
|
-
result = await
|
|
4128
|
+
result = await _internals8.loadEvidence(directory, taskId);
|
|
5010
4129
|
} catch {
|
|
5011
4130
|
warn("archive: skipping corrupt or unreadable evidence for task", taskId);
|
|
5012
4131
|
continue;
|
|
@@ -5034,7 +4153,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles, options) {
|
|
|
5034
4153
|
}
|
|
5035
4154
|
if (!options?.dryRun) {
|
|
5036
4155
|
for (const taskId of [...selected].sort()) {
|
|
5037
|
-
const deleted = await
|
|
4156
|
+
const deleted = await _internals8.deleteEvidence(directory, taskId);
|
|
5038
4157
|
if (deleted)
|
|
5039
4158
|
archived.push(taskId);
|
|
5040
4159
|
else
|
|
@@ -5060,7 +4179,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles, options) {
|
|
|
5060
4179
|
};
|
|
5061
4180
|
if (typeof options?.cacheMaxBytes === "number" && options.cacheMaxBytes > 0 || typeof options?.cacheMaxRecords === "number" && options.cacheMaxRecords > 0) {
|
|
5062
4181
|
try {
|
|
5063
|
-
documentsCache = await
|
|
4182
|
+
documentsCache = await _internals8.pruneEvidenceDocuments({
|
|
5064
4183
|
directory,
|
|
5065
4184
|
maxBytes: options?.cacheMaxBytes,
|
|
5066
4185
|
maxRecords: options?.cacheMaxRecords,
|
|
@@ -5088,7 +4207,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles, options) {
|
|
|
5088
4207
|
function validateEvidence(evidence) {
|
|
5089
4208
|
return EvidenceSchema.parse(evidence);
|
|
5090
4209
|
}
|
|
5091
|
-
var
|
|
4210
|
+
var _internals8 = {
|
|
5092
4211
|
wrapFlatRetrospective,
|
|
5093
4212
|
loadEvidence,
|
|
5094
4213
|
listEvidenceTaskIds,
|
|
@@ -5212,4 +4331,4 @@ function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
|
|
|
5212
4331
|
return merged;
|
|
5213
4332
|
}
|
|
5214
4333
|
|
|
5215
|
-
export { projectDbExists, getProjectDb, closeProjectDb, normalizePath, buildTaskCompletionDescriptor, ensureTaskCheckpointReceipt, updateTaskCheckpointReceipt,
|
|
4334
|
+
export { projectDbExists, getProjectDb, closeProjectDb, normalizePath, buildTaskCompletionDescriptor, ensureTaskCheckpointReceipt, updateTaskCheckpointReceipt, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, getDefaultBaseBranch, detectDefaultRemoteBranch, resetToRemoteBranch, resetToMainAfterMerge, containsPathTraversal, containsControlChars, validateSymlinkBoundary, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, updateTaskStatus, derivePlanMarkdown, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|