opencode-swarm 7.86.0 → 7.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{capability-probe-jevmgwmf.js → capability-probe-wsjzcp48.js} +2 -2
- package/dist/cli/{config-doctor-zejarrr6.js → config-doctor-6h64pn8n.js} +4 -4
- package/dist/cli/{dispatch-k86d928w.js → dispatch-kb69qw40.js} +3 -3
- package/dist/cli/{evidence-summary-service-g2znnd33.js → evidence-summary-service-gg5m9z57.js} +4 -4
- package/dist/cli/{guardrail-explain-rtd1x26f.js → guardrail-explain-wb1cj312.js} +13 -13
- package/dist/cli/{guardrail-log-80116wmz.js → guardrail-log-eegabqcp.js} +5 -5
- package/dist/cli/{index-jwz50183.js → index-0m44n5qv.js} +14 -14
- package/dist/cli/{index-0sxvwjt0.js → index-1cb4wxnm.js} +2 -2
- package/dist/cli/{index-zfsbaaqh.js → index-5e4e2hvv.js} +1 -1
- package/dist/cli/{index-vq2321gg.js → index-5hvbw5xh.js} +2 -2
- package/dist/cli/{index-5cb86007.js → index-5vpe6vq9.js} +1 -1
- package/dist/cli/{index-red8fm8p.js → index-89xjr3h4.js} +1162 -214
- package/dist/cli/{index-f8r50m3h.js → index-adz3nk9b.js} +2 -2
- package/dist/cli/{index-7r2b453y.js → index-f13d3b69.js} +2 -2
- package/dist/cli/{index-ckntc5gf.js → index-gn8n22th.js} +2 -2
- package/dist/cli/{index-hw9b2xng.js → index-q9h0wb04.js} +36 -3
- package/dist/cli/{index-d9fbxaqd.js → index-s8bj492g.js} +1 -1
- package/dist/cli/{index-hz59hg4h.js → index-v4fcn4tr.js} +1 -1
- package/dist/cli/{index-eb85wtx9.js → index-vqyfscxd.js} +2 -2
- package/dist/cli/{index-5q66xc88.js → index-wv2yj8ka.js} +2598 -1406
- package/dist/cli/{index-yx44zd0p.js → index-zgwm4ryv.js} +9 -1
- package/dist/cli/index.js +12 -12
- package/dist/cli/{pending-delegations-rd40tv9s.js → pending-delegations-35fvcj7z.js} +3 -3
- package/dist/cli/{pr-subscriptions-y1nn36e5.js → pr-subscriptions-b18n1yd8.js} +4 -4
- package/dist/cli/{schema-8d32b2v6.js → schema-84146tvk.js} +3 -1
- package/dist/cli/{skill-generator-a5ehggyg.js → skill-generator-3pvpk4y2.js} +2 -2
- package/dist/commands/coupling.d.ts +36 -0
- package/dist/commands/epic.d.ts +52 -0
- package/dist/commands/registry.d.ts +18 -2
- package/dist/config/constants.d.ts +1 -0
- package/dist/config/schema.d.ts +145 -0
- package/dist/git/branch.d.ts +22 -1
- package/dist/hooks/delegation-gate/worktree-merge-status.d.ts +86 -0
- package/dist/index.js +8401 -5792
- package/dist/memory/schema.d.ts +3 -3
- package/dist/plan/manager.d.ts +10 -0
- package/dist/state.d.ts +16 -0
- package/dist/tools/epic-plan-waves.d.ts +79 -0
- package/dist/tools/epic-record-divergence.d.ts +73 -0
- package/dist/tools/epic-run-phase.d.ts +179 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/manifest.d.ts +3 -0
- package/dist/tools/tool-metadata.d.ts +12 -0
- package/dist/turbo/epic/activation.d.ts +193 -0
- package/dist/turbo/epic/calibration-engine.d.ts +88 -0
- package/dist/turbo/epic/calibration.d.ts +65 -0
- package/dist/turbo/epic/cochange-conflict.d.ts +79 -0
- package/dist/turbo/epic/cochange-source.d.ts +80 -0
- package/dist/turbo/epic/coupling-report.d.ts +85 -0
- package/dist/turbo/epic/divergence-recorder.d.ts +112 -0
- package/dist/turbo/epic/index.d.ts +24 -0
- package/dist/turbo/epic/promotion-evidence.d.ts +42 -0
- package/dist/turbo/epic/state.d.ts +85 -0
- package/dist/turbo/epic/task-commit.d.ts +110 -0
- package/dist/turbo/epic/upstream-commits.d.ts +82 -0
- package/dist/turbo/epic/wave-planner.d.ts +83 -0
- package/dist/turbo/lean/partition-common.d.ts +85 -0
- package/dist/turbo/lean/planner.d.ts +12 -20
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/logger.d.ts +19 -0
- package/package.json +1 -1
|
@@ -8,13 +8,16 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
readSwarmFileAsync,
|
|
10
10
|
validateSwarmPath
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-gn8n22th.js";
|
|
12
12
|
import {
|
|
13
13
|
readCachedParsedFile
|
|
14
14
|
} from "./index-jtqkh8jf.js";
|
|
15
15
|
import {
|
|
16
|
+
criticalWarn,
|
|
17
|
+
error,
|
|
18
|
+
init_logger,
|
|
16
19
|
warn
|
|
17
|
-
} from "./index-
|
|
20
|
+
} from "./index-zgwm4ryv.js";
|
|
18
21
|
import {
|
|
19
22
|
withEvidenceLock
|
|
20
23
|
} from "./index-bcp79s17.js";
|
|
@@ -33,13 +36,13 @@ import {
|
|
|
33
36
|
// src/plan/manager.ts
|
|
34
37
|
import {
|
|
35
38
|
copyFileSync,
|
|
36
|
-
existsSync as
|
|
39
|
+
existsSync as existsSync6,
|
|
37
40
|
readdirSync as readdirSync2,
|
|
38
|
-
renameSync as
|
|
39
|
-
unlinkSync
|
|
41
|
+
renameSync as renameSync5,
|
|
42
|
+
unlinkSync as unlinkSync3
|
|
40
43
|
} from "fs";
|
|
41
44
|
import * as fsPromises from "fs/promises";
|
|
42
|
-
import * as
|
|
45
|
+
import * as path6 from "path";
|
|
43
46
|
|
|
44
47
|
// src/config/plan-schema.ts
|
|
45
48
|
var ExecutionProfileSchema = exports_external.object({
|
|
@@ -102,13 +105,953 @@ var PlanSchema = exports_external.object({
|
|
|
102
105
|
execution_profile: ExecutionProfileSchema.optional()
|
|
103
106
|
});
|
|
104
107
|
|
|
108
|
+
// src/git/branch.ts
|
|
109
|
+
import * as child_process from "child_process";
|
|
110
|
+
import path from "path";
|
|
111
|
+
|
|
112
|
+
// src/utils/git-binary-missing-error.ts
|
|
113
|
+
class GitBinaryMissingError extends Error {
|
|
114
|
+
name = "GitBinaryMissingError";
|
|
115
|
+
constructor(message = "git binary is not available", options) {
|
|
116
|
+
super(message, options);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function isGitBinaryMissing(err) {
|
|
120
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/git/branch.ts
|
|
124
|
+
init_logger();
|
|
125
|
+
var GIT_TIMEOUT_MS = 30000;
|
|
126
|
+
var GIT_MAX_BUFFER_BYTES = 5 * 1024 * 1024;
|
|
127
|
+
function unique(values) {
|
|
128
|
+
return [...new Set(values.filter(Boolean))];
|
|
129
|
+
}
|
|
130
|
+
function windowsGitCandidates() {
|
|
131
|
+
if (process.platform !== "win32")
|
|
132
|
+
return ["git"];
|
|
133
|
+
const roots = unique([
|
|
134
|
+
process.env.ProgramFiles ?? "",
|
|
135
|
+
process.env["ProgramFiles(x86)"] ?? "",
|
|
136
|
+
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "Programs") : ""
|
|
137
|
+
]);
|
|
138
|
+
const installed = roots.flatMap((root) => [
|
|
139
|
+
path.join(root, "Git", "cmd", "git.exe"),
|
|
140
|
+
path.join(root, "Git", "bin", "git.exe")
|
|
141
|
+
]);
|
|
142
|
+
return unique(["git", ...installed]);
|
|
143
|
+
}
|
|
144
|
+
function errorMessage(err) {
|
|
145
|
+
return err instanceof Error ? err.message : String(err);
|
|
146
|
+
}
|
|
147
|
+
function isNotGitRepositoryMessage(message) {
|
|
148
|
+
const lower = message.toLowerCase();
|
|
149
|
+
return lower.includes("not a git repository") || lower.includes("not a git repo");
|
|
150
|
+
}
|
|
151
|
+
function gitExec(args, cwd) {
|
|
152
|
+
let missingGitError;
|
|
153
|
+
const subcommand = args[0];
|
|
154
|
+
const hardenedArgs = subcommand === "commit" || subcommand === "tag" ? ["-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false", ...args] : args;
|
|
155
|
+
for (const command of windowsGitCandidates()) {
|
|
156
|
+
const result = child_process.spawnSync(command, hardenedArgs, {
|
|
157
|
+
cwd,
|
|
158
|
+
encoding: "utf-8",
|
|
159
|
+
timeout: GIT_TIMEOUT_MS,
|
|
160
|
+
windowsHide: true,
|
|
161
|
+
maxBuffer: GIT_MAX_BUFFER_BYTES,
|
|
162
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
163
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
164
|
+
});
|
|
165
|
+
if (result.error) {
|
|
166
|
+
if (isGitBinaryMissing(result.error)) {
|
|
167
|
+
missingGitError ??= result.error;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
throw new Error(errorMessage(result.error));
|
|
171
|
+
}
|
|
172
|
+
if (result.status !== 0) {
|
|
173
|
+
throw new Error(result.stderr || result.stdout || `git exited with ${result.status}`);
|
|
174
|
+
}
|
|
175
|
+
return result.stdout;
|
|
176
|
+
}
|
|
177
|
+
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", { cause: missingGitError });
|
|
178
|
+
}
|
|
179
|
+
function getGitRepositoryStatus(cwd) {
|
|
180
|
+
try {
|
|
181
|
+
gitExec(["rev-parse", "--git-dir"], cwd);
|
|
182
|
+
return { isRepo: true };
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (err instanceof GitBinaryMissingError) {
|
|
185
|
+
return {
|
|
186
|
+
isRepo: false,
|
|
187
|
+
reason: "git_unavailable",
|
|
188
|
+
message: err.message
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const message = errorMessage(err);
|
|
192
|
+
return {
|
|
193
|
+
isRepo: false,
|
|
194
|
+
reason: isNotGitRepositoryMessage(message) ? "not_git_repo" : "git_error",
|
|
195
|
+
message
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function isGitRepo(cwd) {
|
|
200
|
+
return getGitRepositoryStatus(cwd).isRepo;
|
|
201
|
+
}
|
|
202
|
+
function getCurrentBranch(cwd) {
|
|
203
|
+
const output = gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
204
|
+
return output.trim();
|
|
205
|
+
}
|
|
206
|
+
function getDefaultBaseBranch(cwd) {
|
|
207
|
+
try {
|
|
208
|
+
gitExec(["rev-parse", "--verify", "origin/main"], cwd);
|
|
209
|
+
return "origin/main";
|
|
210
|
+
} catch {
|
|
211
|
+
try {
|
|
212
|
+
gitExec(["rev-parse", "--verify", "origin/master"], cwd);
|
|
213
|
+
return "origin/master";
|
|
214
|
+
} catch {
|
|
215
|
+
return "origin/main";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function hasUncommittedChanges(cwd) {
|
|
220
|
+
const status = gitExec(["status", "--porcelain"], cwd);
|
|
221
|
+
return status.trim().length > 0;
|
|
222
|
+
}
|
|
223
|
+
function detectDefaultRemoteBranch(cwd) {
|
|
224
|
+
try {
|
|
225
|
+
const output = gitExec(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd);
|
|
226
|
+
const trimmed = output.trim();
|
|
227
|
+
if (trimmed.startsWith("refs/remotes/origin/")) {
|
|
228
|
+
return trimmed.slice("refs/remotes/origin/".length);
|
|
229
|
+
}
|
|
230
|
+
} catch {}
|
|
231
|
+
try {
|
|
232
|
+
const output = gitExec(["config", "init.defaultBranch"], cwd);
|
|
233
|
+
const branch = output.trim();
|
|
234
|
+
if (branch) {
|
|
235
|
+
return branch;
|
|
236
|
+
}
|
|
237
|
+
} catch {}
|
|
238
|
+
try {
|
|
239
|
+
gitExec(["rev-parse", "--verify", "origin/main"], cwd);
|
|
240
|
+
return "main";
|
|
241
|
+
} catch {}
|
|
242
|
+
try {
|
|
243
|
+
gitExec(["rev-parse", "--verify", "origin/master"], cwd);
|
|
244
|
+
return "master";
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function resetToRemoteBranch(cwd, options) {
|
|
250
|
+
const warnings = [];
|
|
251
|
+
const prunedBranches = [];
|
|
252
|
+
try {
|
|
253
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
254
|
+
const defaultRemoteBranch = _internals.detectDefaultRemoteBranch(cwd);
|
|
255
|
+
if (!defaultRemoteBranch) {
|
|
256
|
+
return {
|
|
257
|
+
success: false,
|
|
258
|
+
targetBranch: "",
|
|
259
|
+
localBranch: currentBranch,
|
|
260
|
+
message: "Could not detect default remote branch",
|
|
261
|
+
alreadyAligned: false,
|
|
262
|
+
prunedBranches: [],
|
|
263
|
+
warnings: []
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const targetBranch = `origin/${defaultRemoteBranch}`;
|
|
267
|
+
if (currentBranch === "HEAD") {
|
|
268
|
+
return {
|
|
269
|
+
success: false,
|
|
270
|
+
targetBranch,
|
|
271
|
+
localBranch: "HEAD",
|
|
272
|
+
message: "Cannot reset: detached HEAD state",
|
|
273
|
+
alreadyAligned: false,
|
|
274
|
+
prunedBranches: [],
|
|
275
|
+
warnings: []
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (hasUncommittedChanges(cwd)) {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
targetBranch,
|
|
282
|
+
localBranch: currentBranch,
|
|
283
|
+
message: "Cannot reset: uncommitted changes in working tree",
|
|
284
|
+
alreadyAligned: false,
|
|
285
|
+
prunedBranches: [],
|
|
286
|
+
warnings: []
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const logOutput = gitExec(["log", `${targetBranch}..HEAD`, "--oneline"], cwd);
|
|
291
|
+
if (logOutput.trim().length > 0) {
|
|
292
|
+
return {
|
|
293
|
+
success: false,
|
|
294
|
+
targetBranch,
|
|
295
|
+
localBranch: currentBranch,
|
|
296
|
+
message: "Cannot reset: unpushed commits",
|
|
297
|
+
alreadyAligned: false,
|
|
298
|
+
prunedBranches: [],
|
|
299
|
+
warnings: []
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
} catch {}
|
|
303
|
+
try {
|
|
304
|
+
gitExec(["fetch", "--prune", "origin"], cwd);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
targetBranch,
|
|
309
|
+
localBranch: currentBranch,
|
|
310
|
+
message: `Fetch failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
311
|
+
alreadyAligned: false,
|
|
312
|
+
prunedBranches: [],
|
|
313
|
+
warnings: []
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const headSha = gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
317
|
+
const remoteSha = gitExec(["rev-parse", `${targetBranch}`], cwd).trim();
|
|
318
|
+
if (headSha === remoteSha) {
|
|
319
|
+
return {
|
|
320
|
+
success: true,
|
|
321
|
+
targetBranch,
|
|
322
|
+
localBranch: currentBranch,
|
|
323
|
+
message: "Already aligned with remote",
|
|
324
|
+
alreadyAligned: true,
|
|
325
|
+
prunedBranches: [],
|
|
326
|
+
warnings: []
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
gitExec(["checkout", currentBranch], cwd);
|
|
331
|
+
} catch (err) {
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
targetBranch,
|
|
335
|
+
localBranch: currentBranch,
|
|
336
|
+
message: `Checkout failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
337
|
+
alreadyAligned: false,
|
|
338
|
+
prunedBranches: [],
|
|
339
|
+
warnings: []
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
let resetSucceeded = false;
|
|
343
|
+
let lastError;
|
|
344
|
+
for (let retry = 0;retry < 4; retry++) {
|
|
345
|
+
if (retry > 0 && process.platform === "win32") {
|
|
346
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
gitExec(["reset", "--hard", targetBranch], cwd);
|
|
350
|
+
resetSucceeded = true;
|
|
351
|
+
break;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
lastError = err;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (!resetSucceeded) {
|
|
357
|
+
return {
|
|
358
|
+
success: false,
|
|
359
|
+
targetBranch,
|
|
360
|
+
localBranch: currentBranch,
|
|
361
|
+
message: `Reset failed: ${lastError instanceof Error ? lastError.message : String(lastError)}`,
|
|
362
|
+
alreadyAligned: false,
|
|
363
|
+
prunedBranches: [],
|
|
364
|
+
warnings: []
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (options?.pruneBranches) {
|
|
368
|
+
try {
|
|
369
|
+
const mergedOutput = gitExec(["branch", "--merged", targetBranch], cwd);
|
|
370
|
+
const mergedLines = mergedOutput.split(`
|
|
371
|
+
`);
|
|
372
|
+
for (const line of mergedLines) {
|
|
373
|
+
const trimmedLine = line.trim();
|
|
374
|
+
if (!trimmedLine || trimmedLine.startsWith("*")) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
gitExec(["branch", "-d", trimmedLine], cwd);
|
|
379
|
+
prunedBranches.push(trimmedLine);
|
|
380
|
+
} catch {
|
|
381
|
+
warnings.push(`Could not safely delete branch: ${trimmedLine}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} catch (err) {
|
|
385
|
+
warnings.push(`Failed to get merged branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
const branchVvOutput = gitExec(["branch", "-vv"], cwd);
|
|
389
|
+
const vvLines = branchVvOutput.split(`
|
|
390
|
+
`);
|
|
391
|
+
for (const line of vvLines) {
|
|
392
|
+
const trimmedLine = line.trim();
|
|
393
|
+
if (!trimmedLine || trimmedLine.startsWith("*")) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (trimmedLine.includes(": gone]")) {
|
|
397
|
+
const parts = trimmedLine.split(/\s+/);
|
|
398
|
+
const branchName = parts[0];
|
|
399
|
+
try {
|
|
400
|
+
gitExec(["branch", "-d", branchName], cwd);
|
|
401
|
+
prunedBranches.push(branchName);
|
|
402
|
+
} catch {
|
|
403
|
+
warnings.push(`Could not delete gone branch: ${branchName}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} catch (err) {
|
|
408
|
+
warnings.push(`Failed to prune gone branches: ${err instanceof Error ? err.message : String(err)}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
success: true,
|
|
413
|
+
targetBranch,
|
|
414
|
+
localBranch: currentBranch,
|
|
415
|
+
message: "Successfully reset to remote branch",
|
|
416
|
+
alreadyAligned: false,
|
|
417
|
+
prunedBranches,
|
|
418
|
+
warnings
|
|
419
|
+
};
|
|
420
|
+
} catch (err) {
|
|
421
|
+
return {
|
|
422
|
+
success: false,
|
|
423
|
+
targetBranch: "",
|
|
424
|
+
localBranch: "",
|
|
425
|
+
message: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
|
426
|
+
alreadyAligned: false,
|
|
427
|
+
prunedBranches: [],
|
|
428
|
+
warnings: []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function resetToMainAfterMerge(cwd, options) {
|
|
433
|
+
const warnings = [];
|
|
434
|
+
try {
|
|
435
|
+
const defaultBranch = _internals.detectDefaultRemoteBranch(cwd);
|
|
436
|
+
if (!defaultBranch) {
|
|
437
|
+
return {
|
|
438
|
+
success: false,
|
|
439
|
+
targetBranch: "",
|
|
440
|
+
previousBranch: "",
|
|
441
|
+
message: "Could not detect default remote branch",
|
|
442
|
+
branchDeleted: false,
|
|
443
|
+
changesDiscarded: false,
|
|
444
|
+
warnings
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
448
|
+
const targetBranch = `origin/${defaultBranch}`;
|
|
449
|
+
if (currentBranch === "HEAD") {
|
|
450
|
+
return {
|
|
451
|
+
success: false,
|
|
452
|
+
targetBranch,
|
|
453
|
+
previousBranch: "HEAD",
|
|
454
|
+
message: "Cannot reset: detached HEAD state",
|
|
455
|
+
branchDeleted: false,
|
|
456
|
+
changesDiscarded: false,
|
|
457
|
+
warnings
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (currentBranch === defaultBranch) {
|
|
461
|
+
try {
|
|
462
|
+
const logOutput = _internals.gitExec(["log", `${targetBranch}..HEAD`, "--oneline"], cwd);
|
|
463
|
+
if (logOutput.trim().length > 0) {
|
|
464
|
+
return {
|
|
465
|
+
success: false,
|
|
466
|
+
targetBranch,
|
|
467
|
+
previousBranch: currentBranch,
|
|
468
|
+
message: `Cannot reset: ${defaultBranch} has unpushed commits. Push them first.`,
|
|
469
|
+
branchDeleted: false,
|
|
470
|
+
changesDiscarded: false,
|
|
471
|
+
warnings
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
} catch {}
|
|
475
|
+
} else {
|
|
476
|
+
try {
|
|
477
|
+
_internals.gitExec(["rev-parse", "--abbrev-ref", `${currentBranch}@{upstream}`], cwd);
|
|
478
|
+
} catch {
|
|
479
|
+
try {
|
|
480
|
+
const localSha = _internals.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
481
|
+
const remoteSha = _internals.gitExec(["rev-parse", targetBranch], cwd).trim();
|
|
482
|
+
if (localSha !== remoteSha) {
|
|
483
|
+
return {
|
|
484
|
+
success: false,
|
|
485
|
+
targetBranch,
|
|
486
|
+
previousBranch: currentBranch,
|
|
487
|
+
message: `Cannot reset: branch ${currentBranch} is local-only and diverges from ${defaultBranch}. Push or check manually.`,
|
|
488
|
+
branchDeleted: false,
|
|
489
|
+
changesDiscarded: false,
|
|
490
|
+
warnings
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
} catch {
|
|
494
|
+
return {
|
|
495
|
+
success: false,
|
|
496
|
+
targetBranch,
|
|
497
|
+
previousBranch: currentBranch,
|
|
498
|
+
message: `Cannot reset: unable to compare ${currentBranch} with ${defaultBranch}`,
|
|
499
|
+
branchDeleted: false,
|
|
500
|
+
changesDiscarded: false,
|
|
501
|
+
warnings
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
_internals.gitExec(["fetch", "--prune", "origin"], cwd);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
return {
|
|
510
|
+
success: false,
|
|
511
|
+
targetBranch,
|
|
512
|
+
previousBranch: currentBranch,
|
|
513
|
+
message: `Cannot reset: fetch failed \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
514
|
+
branchDeleted: false,
|
|
515
|
+
changesDiscarded: false,
|
|
516
|
+
warnings
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const previousBranch = currentBranch;
|
|
520
|
+
let switchedBranch = false;
|
|
521
|
+
if (currentBranch !== defaultBranch) {
|
|
522
|
+
try {
|
|
523
|
+
_internals.gitExec(["checkout", defaultBranch], cwd);
|
|
524
|
+
switchedBranch = true;
|
|
525
|
+
} catch (err) {
|
|
526
|
+
return {
|
|
527
|
+
success: false,
|
|
528
|
+
targetBranch,
|
|
529
|
+
previousBranch,
|
|
530
|
+
message: `Checkout to ${defaultBranch} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
531
|
+
branchDeleted: false,
|
|
532
|
+
changesDiscarded: false,
|
|
533
|
+
warnings
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
try {
|
|
538
|
+
_internals.gitExec(["reset", "--hard", targetBranch], cwd);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
return {
|
|
541
|
+
success: false,
|
|
542
|
+
targetBranch,
|
|
543
|
+
previousBranch,
|
|
544
|
+
message: `Reset to ${targetBranch} failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
545
|
+
branchDeleted: false,
|
|
546
|
+
changesDiscarded: false,
|
|
547
|
+
warnings
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
let changesDiscarded = false;
|
|
551
|
+
if (hasUncommittedChanges(cwd)) {
|
|
552
|
+
let discardSucceeded = false;
|
|
553
|
+
for (let retry = 0;retry < 4; retry++) {
|
|
554
|
+
if (retry > 0 && process.platform === "win32") {
|
|
555
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
_internals.gitExec(["checkout", "--", "."], cwd);
|
|
559
|
+
discardSucceeded = true;
|
|
560
|
+
break;
|
|
561
|
+
} catch {}
|
|
562
|
+
}
|
|
563
|
+
if (!discardSucceeded) {
|
|
564
|
+
warnings.push("Could not discard all uncommitted changes after reset");
|
|
565
|
+
}
|
|
566
|
+
changesDiscarded = discardSucceeded;
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
_internals.gitExec(["clean", "-fdX"], cwd);
|
|
570
|
+
} catch {
|
|
571
|
+
warnings.push("Could not clean untracked files");
|
|
572
|
+
}
|
|
573
|
+
let branchDeleted = false;
|
|
574
|
+
if (switchedBranch && previousBranch !== defaultBranch) {
|
|
575
|
+
try {
|
|
576
|
+
const mergedOutput = _internals.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
577
|
+
const isMerged = mergedOutput.split(`
|
|
578
|
+
`).some((line) => line.trim() === previousBranch || line.trim() === `* ${previousBranch}`);
|
|
579
|
+
if (isMerged) {
|
|
580
|
+
_internals.gitExec(["branch", "-d", previousBranch], cwd);
|
|
581
|
+
branchDeleted = true;
|
|
582
|
+
} else {
|
|
583
|
+
warnings.push(`Branch ${previousBranch} is not merged into ${defaultBranch} \u2014 keeping it`);
|
|
584
|
+
}
|
|
585
|
+
} catch {
|
|
586
|
+
warnings.push(`Could not delete branch ${previousBranch}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (options?.pruneBranches) {
|
|
590
|
+
try {
|
|
591
|
+
const mergedOutput = _internals.gitExec(["branch", "--merged", defaultBranch], cwd);
|
|
592
|
+
const mergedLines = mergedOutput.split(`
|
|
593
|
+
`);
|
|
594
|
+
for (const line of mergedLines) {
|
|
595
|
+
const trimmedLine = line.trim();
|
|
596
|
+
if (!trimmedLine || trimmedLine.startsWith("*") || trimmedLine === defaultBranch) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
_internals.gitExec(["branch", "-d", trimmedLine], cwd);
|
|
601
|
+
} catch {
|
|
602
|
+
warnings.push(`Could not prune branch: ${trimmedLine}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
} catch (err) {
|
|
606
|
+
warnings.push(`Prune failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
success: true,
|
|
611
|
+
targetBranch,
|
|
612
|
+
previousBranch,
|
|
613
|
+
message: branchDeleted ? `Reset to ${defaultBranch} and deleted branch ${previousBranch}` : `Reset to ${defaultBranch}`,
|
|
614
|
+
branchDeleted,
|
|
615
|
+
changesDiscarded,
|
|
616
|
+
warnings
|
|
617
|
+
};
|
|
618
|
+
} catch (err) {
|
|
619
|
+
return {
|
|
620
|
+
success: false,
|
|
621
|
+
targetBranch: "",
|
|
622
|
+
previousBranch: "",
|
|
623
|
+
message: `Unexpected error: ${err instanceof Error ? err.message : String(err)}`,
|
|
624
|
+
branchDeleted: false,
|
|
625
|
+
changesDiscarded: false,
|
|
626
|
+
warnings
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
var _internals = {
|
|
631
|
+
gitExec,
|
|
632
|
+
detectDefaultRemoteBranch,
|
|
633
|
+
getDefaultBaseBranch,
|
|
634
|
+
getGitRepositoryStatus,
|
|
635
|
+
resetToRemoteBranch,
|
|
636
|
+
resetToMainAfterMerge
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
// src/hooks/delegation-gate/worktree-merge-status.ts
|
|
640
|
+
import * as fs from "fs";
|
|
641
|
+
var failuresByTask = new Map;
|
|
642
|
+
var durableStatusPath;
|
|
643
|
+
var hasLoaded = false;
|
|
644
|
+
function loadDurableStatus(statusPath) {
|
|
645
|
+
try {
|
|
646
|
+
hasLoaded = true;
|
|
647
|
+
if (!fs.existsSync(statusPath)) {
|
|
648
|
+
failuresByTask.clear();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const data = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
|
|
652
|
+
if (data && typeof data === "object") {
|
|
653
|
+
failuresByTask.clear();
|
|
654
|
+
for (const [taskId, failure] of Object.entries(data)) {
|
|
655
|
+
failuresByTask.set(taskId, failure);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
} catch {
|
|
659
|
+
failuresByTask.clear();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function getWorktreeMergeFailure(taskId) {
|
|
663
|
+
if (!hasLoaded && durableStatusPath) {
|
|
664
|
+
loadDurableStatus(durableStatusPath);
|
|
665
|
+
}
|
|
666
|
+
return failuresByTask.get(taskId);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/turbo/epic/state.ts
|
|
670
|
+
init_logger();
|
|
671
|
+
import * as fs2 from "fs";
|
|
672
|
+
import * as path2 from "path";
|
|
673
|
+
var STATE_FILE = "epic-state.json";
|
|
674
|
+
function nowISO() {
|
|
675
|
+
return new Date().toISOString();
|
|
676
|
+
}
|
|
677
|
+
function ensureSwarmDir(directory) {
|
|
678
|
+
const swarmDir = path2.resolve(directory, ".swarm");
|
|
679
|
+
if (!fs2.existsSync(swarmDir)) {
|
|
680
|
+
fs2.mkdirSync(swarmDir, { recursive: true });
|
|
681
|
+
}
|
|
682
|
+
return swarmDir;
|
|
683
|
+
}
|
|
684
|
+
function emptyPersisted() {
|
|
685
|
+
return { version: 1, updatedAt: nowISO(), sessions: {} };
|
|
686
|
+
}
|
|
687
|
+
function emptySessionState(sessionID) {
|
|
688
|
+
return { sessionID, active: false };
|
|
689
|
+
}
|
|
690
|
+
var stateUnreadableMap = new Map;
|
|
691
|
+
function isStateUnreadable(directory) {
|
|
692
|
+
return stateUnreadableMap.get(directory) ?? false;
|
|
693
|
+
}
|
|
694
|
+
function markStateUnreadable(directory, reason) {
|
|
695
|
+
stateUnreadableMap.set(directory, true);
|
|
696
|
+
error(`[turbo/epic/state] state file unreadable for ${directory}: ${reason} \u2014 failing closed`);
|
|
697
|
+
}
|
|
698
|
+
function readPersisted(directory) {
|
|
699
|
+
try {
|
|
700
|
+
const filePath = path2.join(directory, ".swarm", STATE_FILE);
|
|
701
|
+
if (!fs2.existsSync(filePath)) {
|
|
702
|
+
const seed = emptyPersisted();
|
|
703
|
+
try {
|
|
704
|
+
ensureSwarmDir(directory);
|
|
705
|
+
fs2.writeFileSync(filePath, `${JSON.stringify(seed, null, 2)}
|
|
706
|
+
`, "utf-8");
|
|
707
|
+
} catch {}
|
|
708
|
+
return seed;
|
|
709
|
+
}
|
|
710
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
711
|
+
const parsed = JSON.parse(raw);
|
|
712
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 1 || !parsed.sessions || typeof parsed.sessions !== "object" || Array.isArray(parsed.sessions)) {
|
|
713
|
+
markStateUnreadable(directory, `malformed shape (version=${parsed?.version}, sessions type=${Array.isArray(parsed?.sessions) ? "array" : typeof parsed?.sessions})`);
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
version: 1,
|
|
718
|
+
updatedAt: parsed.updatedAt ?? nowISO(),
|
|
719
|
+
sessions: parsed.sessions
|
|
720
|
+
};
|
|
721
|
+
} catch (error2) {
|
|
722
|
+
const reason = error2 instanceof Error ? error2.message : String(error2);
|
|
723
|
+
markStateUnreadable(directory, reason);
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function writePersisted(directory, persisted) {
|
|
728
|
+
if (stateUnreadableMap.get(directory)) {
|
|
729
|
+
throw new Error(`Epic state is unreadable. Please repair .swarm/${STATE_FILE} before continuing.`);
|
|
730
|
+
}
|
|
731
|
+
let filePath;
|
|
732
|
+
let tmpPath;
|
|
733
|
+
let payload;
|
|
734
|
+
try {
|
|
735
|
+
ensureSwarmDir(directory);
|
|
736
|
+
filePath = path2.join(directory, ".swarm", STATE_FILE);
|
|
737
|
+
tmpPath = `${filePath}.tmp.${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
738
|
+
persisted.updatedAt = nowISO();
|
|
739
|
+
payload = `${JSON.stringify(persisted, null, 2)}
|
|
740
|
+
`;
|
|
741
|
+
} catch (error2) {
|
|
742
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
743
|
+
error(`[turbo/epic/state] Failed to prepare ${STATE_FILE} write: ${msg}`);
|
|
744
|
+
throw new Error(`Epic state persistence prepare failed: ${msg}`);
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
fs2.writeFileSync(tmpPath, payload, "utf-8");
|
|
748
|
+
fs2.renameSync(tmpPath, filePath);
|
|
749
|
+
} catch (error2) {
|
|
750
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
751
|
+
error(`[turbo/epic/state] Failed to persist ${STATE_FILE} atomically: ${msg}`);
|
|
752
|
+
try {
|
|
753
|
+
if (fs2.existsSync(tmpPath))
|
|
754
|
+
fs2.unlinkSync(tmpPath);
|
|
755
|
+
} catch {}
|
|
756
|
+
throw new Error(`Epic state persistence failed: ${msg}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function loadEpicSessionState(directory, sessionID) {
|
|
760
|
+
if (stateUnreadableMap.get(directory))
|
|
761
|
+
return null;
|
|
762
|
+
const persisted = readPersisted(directory);
|
|
763
|
+
if (!persisted)
|
|
764
|
+
return null;
|
|
765
|
+
return persisted.sessions[sessionID] ?? null;
|
|
766
|
+
}
|
|
767
|
+
function saveEpicSessionState(directory, state) {
|
|
768
|
+
if (stateUnreadableMap.get(directory)) {
|
|
769
|
+
throw new Error(`Epic state is unreadable for ${directory}. Repair .swarm/${STATE_FILE} before continuing.`);
|
|
770
|
+
}
|
|
771
|
+
const persisted = readPersisted(directory);
|
|
772
|
+
if (!persisted) {
|
|
773
|
+
throw new Error(`Epic state is unreadable for ${directory}. Repair .swarm/${STATE_FILE} before continuing.`);
|
|
774
|
+
}
|
|
775
|
+
persisted.sessions[state.sessionID] = state;
|
|
776
|
+
writePersisted(directory, persisted);
|
|
777
|
+
}
|
|
778
|
+
function isEpicModeActive(directory, sessionID) {
|
|
779
|
+
const state = loadEpicSessionState(directory, sessionID);
|
|
780
|
+
return state?.active === true;
|
|
781
|
+
}
|
|
782
|
+
function isEpicModeActiveForProject(directory) {
|
|
783
|
+
if (stateUnreadableMap.get(directory))
|
|
784
|
+
return false;
|
|
785
|
+
if (!fs2.existsSync(path2.join(directory, ".swarm", STATE_FILE))) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
const persisted = readPersisted(directory);
|
|
789
|
+
if (!persisted)
|
|
790
|
+
return false;
|
|
791
|
+
for (const session of Object.values(persisted.sessions)) {
|
|
792
|
+
if (session?.active === true)
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
function enableEpicMode(directory, sessionID) {
|
|
798
|
+
const current = loadEpicSessionState(directory, sessionID) ?? emptySessionState(sessionID);
|
|
799
|
+
current.active = true;
|
|
800
|
+
current.enabledAt = nowISO();
|
|
801
|
+
current.disabledAt = undefined;
|
|
802
|
+
saveEpicSessionState(directory, current);
|
|
803
|
+
}
|
|
804
|
+
function disableEpicMode(directory, sessionID) {
|
|
805
|
+
const current = loadEpicSessionState(directory, sessionID);
|
|
806
|
+
if (!current) {
|
|
807
|
+
saveEpicSessionState(directory, {
|
|
808
|
+
...emptySessionState(sessionID),
|
|
809
|
+
disabledAt: nowISO()
|
|
810
|
+
});
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
current.active = false;
|
|
814
|
+
current.disabledAt = nowISO();
|
|
815
|
+
saveEpicSessionState(directory, current);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/turbo/epic/task-commit.ts
|
|
819
|
+
init_logger();
|
|
820
|
+
function scrubTaskIdForGitSubject(taskId) {
|
|
821
|
+
return taskId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
822
|
+
}
|
|
823
|
+
function formatTaskCommitMessage(taskId, description) {
|
|
824
|
+
const safeId = scrubTaskIdForGitSubject(taskId);
|
|
825
|
+
const summary = (description ?? "completed").replace(/\s+/g, " ").trim();
|
|
826
|
+
const truncated = summary.length > 60 ? `${summary.slice(0, 57)}...` : summary;
|
|
827
|
+
return `swarm(task ${safeId}): ${truncated || "completed"}`;
|
|
828
|
+
}
|
|
829
|
+
var INDEX_LOCK_BACKOFF_MS = [100, 200, 400, 800];
|
|
830
|
+
var INDEX_LOCK_ERROR_RE = /index\.lock|unable to create.*\.lock/i;
|
|
831
|
+
function isLockContentionError(err) {
|
|
832
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
833
|
+
return INDEX_LOCK_ERROR_RE.test(msg);
|
|
834
|
+
}
|
|
835
|
+
async function commitTaskCompletion(directory, taskId, description, scopePaths) {
|
|
836
|
+
if (!_internals2.isGitRepo(directory)) {
|
|
837
|
+
return { committed: false, reason: "no-git" };
|
|
838
|
+
}
|
|
839
|
+
try {
|
|
840
|
+
if (_internals2.hasExistingTaskCommit(directory, taskId)) {
|
|
841
|
+
return { committed: true, reason: "idempotent-skip" };
|
|
842
|
+
}
|
|
843
|
+
} catch {}
|
|
844
|
+
const rawPaths = (scopePaths ?? []).filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
845
|
+
const droppedMagic = [];
|
|
846
|
+
const paths = rawPaths.filter((p) => {
|
|
847
|
+
if (p.startsWith(":")) {
|
|
848
|
+
droppedMagic.push(p);
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
return true;
|
|
852
|
+
});
|
|
853
|
+
if (droppedMagic.length > 0) {
|
|
854
|
+
criticalWarn(`[epic:task-commit] dropped ${droppedMagic.length} scope path(s) starting with ':' (git pathspec magic, not allowed): ${droppedMagic.slice(0, 5).join(", ")}${droppedMagic.length > 5 ? `, +${droppedMagic.length - 5} more` : ""}. Architect should declare literal file paths only.`);
|
|
855
|
+
}
|
|
856
|
+
const message = formatTaskCommitMessage(taskId, description);
|
|
857
|
+
let lastError = null;
|
|
858
|
+
for (let attempt = 0;attempt <= INDEX_LOCK_BACKOFF_MS.length; attempt++) {
|
|
859
|
+
try {
|
|
860
|
+
if (paths.length > 0) {
|
|
861
|
+
_internals2.stageScopedPaths(directory, paths);
|
|
862
|
+
}
|
|
863
|
+
_internals2.commitAllowEmpty(directory, message);
|
|
864
|
+
const sha = _internals2.gitHeadSha(directory);
|
|
865
|
+
return { committed: true, reason: "success", sha };
|
|
866
|
+
} catch (err) {
|
|
867
|
+
lastError = err;
|
|
868
|
+
if (attempt < INDEX_LOCK_BACKOFF_MS.length && isLockContentionError(err)) {
|
|
869
|
+
await _internals2.sleep(INDEX_LOCK_BACKOFF_MS[attempt]);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
876
|
+
criticalWarn(`[epic:task-commit] commit for task ${taskId} failed (non-fatal): ${msg}`);
|
|
877
|
+
return { committed: false, reason: "commit-failed", error: msg };
|
|
878
|
+
}
|
|
879
|
+
var _internals2 = {
|
|
880
|
+
isGitRepo: (cwd) => isGitRepo(cwd),
|
|
881
|
+
stageScopedPaths: (cwd, paths) => {
|
|
882
|
+
const CHUNK = 200;
|
|
883
|
+
for (let i = 0;i < paths.length; i += CHUNK) {
|
|
884
|
+
const chunk = paths.slice(i, i + CHUNK);
|
|
885
|
+
_internals.gitExec(["add", "--", ...chunk, ":(exclude,glob)**/.swarm/**"], cwd);
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
commitAllowEmpty: (cwd, message) => {
|
|
889
|
+
_internals.gitExec(["commit", "--allow-empty", "--no-verify", "-m", message], cwd);
|
|
890
|
+
},
|
|
891
|
+
gitHeadSha: (cwd) => {
|
|
892
|
+
return _internals.gitExec(["rev-parse", "HEAD"], cwd).trim();
|
|
893
|
+
},
|
|
894
|
+
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
|
|
895
|
+
hasExistingTaskCommit: (cwd, taskId) => {
|
|
896
|
+
const safeId = scrubTaskIdForGitSubject(taskId);
|
|
897
|
+
const escaped = safeId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
898
|
+
const output = _internals.gitExec([
|
|
899
|
+
"log",
|
|
900
|
+
"--extended-regexp",
|
|
901
|
+
`--grep=^swarm\\(task ${escaped}\\):`,
|
|
902
|
+
"--pretty=format:%H",
|
|
903
|
+
"-n",
|
|
904
|
+
"1"
|
|
905
|
+
], cwd);
|
|
906
|
+
return output.trim().length > 0;
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
// src/turbo/lean/conflicts.ts
|
|
911
|
+
import * as fs3 from "fs";
|
|
912
|
+
import * as path3 from "path";
|
|
913
|
+
var DEFAULT_GLOBAL_FILES = [
|
|
914
|
+
"package.json",
|
|
915
|
+
"package-lock.json",
|
|
916
|
+
"bun.lock",
|
|
917
|
+
"bun.lockb",
|
|
918
|
+
"pnpm-lock.yaml",
|
|
919
|
+
"yarn.lock",
|
|
920
|
+
"Cargo.lock",
|
|
921
|
+
"Gemfile.lock",
|
|
922
|
+
"composer.lock",
|
|
923
|
+
"poetry.lock",
|
|
924
|
+
"go.mod",
|
|
925
|
+
"go.sum",
|
|
926
|
+
"tsconfig.json",
|
|
927
|
+
"bunfig.toml",
|
|
928
|
+
"CHANGELOG.md",
|
|
929
|
+
".release-please-manifest.json",
|
|
930
|
+
"src/index.ts",
|
|
931
|
+
"src/tools/index.ts",
|
|
932
|
+
"src/agents/index.ts",
|
|
933
|
+
"src/config/index.ts",
|
|
934
|
+
"src/hooks/index.ts",
|
|
935
|
+
"turbo.json",
|
|
936
|
+
"nx.json",
|
|
937
|
+
"packageManager",
|
|
938
|
+
".npmrc",
|
|
939
|
+
".nvmrc",
|
|
940
|
+
".node-version"
|
|
941
|
+
];
|
|
942
|
+
var DEFAULT_PROTECTED_PATTERNS = [
|
|
943
|
+
"guardrail",
|
|
944
|
+
"delegation",
|
|
945
|
+
"authority",
|
|
946
|
+
"permission",
|
|
947
|
+
"crypto",
|
|
948
|
+
"secret",
|
|
949
|
+
"security",
|
|
950
|
+
"auth",
|
|
951
|
+
"/auth/",
|
|
952
|
+
"/auth.",
|
|
953
|
+
"auth.",
|
|
954
|
+
".env",
|
|
955
|
+
"credentials",
|
|
956
|
+
"secrets",
|
|
957
|
+
"private",
|
|
958
|
+
"/security/",
|
|
959
|
+
"/security.",
|
|
960
|
+
"/protect/",
|
|
961
|
+
"/protect."
|
|
962
|
+
];
|
|
963
|
+
var BARREL_FILE_PATTERNS = [
|
|
964
|
+
/\/index\.ts$/,
|
|
965
|
+
/\/index\.tsx$/,
|
|
966
|
+
/\/index\.js$/,
|
|
967
|
+
/\/index\.mjs$/,
|
|
968
|
+
/\/exports\.ts$/,
|
|
969
|
+
/\/types\.ts$/
|
|
970
|
+
];
|
|
971
|
+
function normalizePath(filePath) {
|
|
972
|
+
if (filePath === "." || filePath === "./") {
|
|
973
|
+
return ".";
|
|
974
|
+
}
|
|
975
|
+
let result = filePath.replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\.\//, "").replace(/(?:^|\/)\.\//g, "/");
|
|
976
|
+
result = result.replace(/\/$/, "");
|
|
977
|
+
result = result.replace(/\/\.$/, "");
|
|
978
|
+
if (process.platform === "win32") {
|
|
979
|
+
result = result.toLowerCase();
|
|
980
|
+
}
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
function pathsConflict(path1, path22) {
|
|
984
|
+
if (process.platform === "win32") {
|
|
985
|
+
path1 = path1.toLowerCase();
|
|
986
|
+
path22 = path22.toLowerCase();
|
|
987
|
+
}
|
|
988
|
+
if (path1 === path22) {
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
const [shorter, longer] = path1.length <= path22.length ? [path1, path22] : [path22, path1];
|
|
992
|
+
if (longer.startsWith(`${shorter}/`)) {
|
|
993
|
+
return true;
|
|
994
|
+
}
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
function isGlobalFile(normalizedPath) {
|
|
998
|
+
if (DEFAULT_GLOBAL_FILES.some((gf) => normalizedPath.endsWith(gf))) {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
for (const pattern of BARREL_FILE_PATTERNS) {
|
|
1002
|
+
if (pattern.test(normalizedPath)) {
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
function isProtectedPath(normalizedPath) {
|
|
1009
|
+
const lowerPath = normalizedPath.toLowerCase();
|
|
1010
|
+
for (const pattern of DEFAULT_PROTECTED_PATTERNS) {
|
|
1011
|
+
if (lowerPath.includes(pattern.toLowerCase())) {
|
|
1012
|
+
if (pattern === "auth" || pattern === "/auth/") {
|
|
1013
|
+
if (lowerPath === "auth" || lowerPath.endsWith("/auth") || lowerPath.includes("/auth/")) {
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
} else if (pattern === "auth.") {
|
|
1017
|
+
const idx = lowerPath.indexOf("auth.");
|
|
1018
|
+
if (idx !== -1) {
|
|
1019
|
+
const before = idx === 0 || lowerPath[idx - 1] === "/";
|
|
1020
|
+
if (before) {
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
function readTaskScopes(directory, taskId) {
|
|
1032
|
+
const scopePath = path3.join(directory, ".swarm", "scopes", `scope-${taskId}.json`);
|
|
1033
|
+
try {
|
|
1034
|
+
if (!fs3.existsSync(scopePath)) {
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
const raw = fs3.readFileSync(scopePath, "utf-8");
|
|
1038
|
+
const parsed = JSON.parse(raw);
|
|
1039
|
+
if (!parsed || !Array.isArray(parsed.files)) {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
return parsed.files;
|
|
1043
|
+
} catch {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
105
1048
|
// src/utils/spec-hash.ts
|
|
106
1049
|
import { createHash as createHash2 } from "crypto";
|
|
107
1050
|
|
|
108
1051
|
// src/sdd/effective-spec.ts
|
|
109
1052
|
import { createHash } from "crypto";
|
|
110
|
-
import * as
|
|
111
|
-
import * as
|
|
1053
|
+
import * as fs4 from "fs";
|
|
1054
|
+
import * as path4 from "path";
|
|
112
1055
|
|
|
113
1056
|
// src/config/spec-schema.ts
|
|
114
1057
|
var ObligationSchema = exports_external.enum(["MUST", "SHALL", "SHOULD", "MAY"]);
|
|
@@ -198,32 +1141,32 @@ function validateSpecContent(content) {
|
|
|
198
1141
|
}
|
|
199
1142
|
|
|
200
1143
|
// src/sdd/effective-spec.ts
|
|
201
|
-
var SWARM_SPEC_REL =
|
|
1144
|
+
var SWARM_SPEC_REL = path4.join(".swarm", "spec.md");
|
|
202
1145
|
var OPENSPEC_ROOT = "openspec";
|
|
203
1146
|
var MAX_SPEC_BYTES = 256 * 1024;
|
|
204
1147
|
var MAX_SOURCE_BYTES = 512 * 1024;
|
|
205
1148
|
var MAX_SPEC_FILES = 100;
|
|
206
1149
|
var MAX_WALK_DEPTH = 10;
|
|
207
1150
|
function toPosix(relPath) {
|
|
208
|
-
return relPath.split(
|
|
1151
|
+
return relPath.split(path4.sep).join("/");
|
|
209
1152
|
}
|
|
210
1153
|
function hash(content) {
|
|
211
1154
|
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
212
1155
|
}
|
|
213
1156
|
function readTextBounded(absPath) {
|
|
214
|
-
const stat =
|
|
1157
|
+
const stat = fs4.lstatSync(absPath);
|
|
215
1158
|
if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) {
|
|
216
1159
|
return null;
|
|
217
1160
|
}
|
|
218
|
-
return
|
|
1161
|
+
return fs4.readFileSync(absPath, "utf-8");
|
|
219
1162
|
}
|
|
220
1163
|
function fileArtifact(root, absPath) {
|
|
221
1164
|
try {
|
|
222
|
-
const stat =
|
|
1165
|
+
const stat = fs4.lstatSync(absPath);
|
|
223
1166
|
if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES)
|
|
224
1167
|
return null;
|
|
225
1168
|
return {
|
|
226
|
-
relPath: toPosix(
|
|
1169
|
+
relPath: toPosix(path4.relative(root, absPath)),
|
|
227
1170
|
bytes: stat.size,
|
|
228
1171
|
mtimeMs: stat.mtimeMs
|
|
229
1172
|
};
|
|
@@ -232,8 +1175,8 @@ function fileArtifact(root, absPath) {
|
|
|
232
1175
|
}
|
|
233
1176
|
}
|
|
234
1177
|
function walkSpecFiles(root, startRel) {
|
|
235
|
-
const start =
|
|
236
|
-
if (!
|
|
1178
|
+
const start = path4.join(root, startRel);
|
|
1179
|
+
if (!fs4.existsSync(start))
|
|
237
1180
|
return [];
|
|
238
1181
|
const artifacts = [];
|
|
239
1182
|
const stack = [
|
|
@@ -245,13 +1188,13 @@ function walkSpecFiles(root, startRel) {
|
|
|
245
1188
|
continue;
|
|
246
1189
|
let entries;
|
|
247
1190
|
try {
|
|
248
|
-
entries =
|
|
1191
|
+
entries = fs4.readdirSync(item.abs, { withFileTypes: true });
|
|
249
1192
|
} catch {
|
|
250
1193
|
continue;
|
|
251
1194
|
}
|
|
252
1195
|
const dirents = entries.filter((entry) => typeof entry?.name === "string");
|
|
253
1196
|
for (const entry of dirents.sort((a, b) => b.name.localeCompare(a.name))) {
|
|
254
|
-
const abs =
|
|
1197
|
+
const abs = path4.join(item.abs, entry.name);
|
|
255
1198
|
if (entry.isSymbolicLink())
|
|
256
1199
|
continue;
|
|
257
1200
|
if (entry.isDirectory()) {
|
|
@@ -268,23 +1211,23 @@ function walkSpecFiles(root, startRel) {
|
|
|
268
1211
|
return artifacts.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
269
1212
|
}
|
|
270
1213
|
function listOpenSpecChanges(root) {
|
|
271
|
-
const changesDir =
|
|
272
|
-
if (!
|
|
1214
|
+
const changesDir = path4.join(root, OPENSPEC_ROOT, "changes");
|
|
1215
|
+
if (!fs4.existsSync(changesDir))
|
|
273
1216
|
return [];
|
|
274
1217
|
let entries;
|
|
275
1218
|
try {
|
|
276
|
-
entries =
|
|
1219
|
+
entries = fs4.readdirSync(changesDir, { withFileTypes: true });
|
|
277
1220
|
} catch {
|
|
278
1221
|
return [];
|
|
279
1222
|
}
|
|
280
1223
|
return entries.filter((entry) => entry.isDirectory() && entry.name !== "archive").sort((a, b) => a.name.localeCompare(b.name)).map((entry) => {
|
|
281
|
-
const rel =
|
|
1224
|
+
const rel = path4.join(OPENSPEC_ROOT, "changes", entry.name);
|
|
282
1225
|
return {
|
|
283
1226
|
id: entry.name,
|
|
284
|
-
proposal:
|
|
285
|
-
design:
|
|
286
|
-
tasks:
|
|
287
|
-
specs: walkSpecFiles(root,
|
|
1227
|
+
proposal: fs4.existsSync(path4.join(root, rel, "proposal.md")),
|
|
1228
|
+
design: fs4.existsSync(path4.join(root, rel, "design.md")),
|
|
1229
|
+
tasks: fs4.existsSync(path4.join(root, rel, "tasks.md")),
|
|
1230
|
+
specs: walkSpecFiles(root, path4.join(rel, "specs"))
|
|
288
1231
|
};
|
|
289
1232
|
});
|
|
290
1233
|
}
|
|
@@ -379,14 +1322,14 @@ function renderRequirement(req, used, warnings) {
|
|
|
379
1322
|
return `- ${text} _(source: ${req.sourceRel})_`;
|
|
380
1323
|
}
|
|
381
1324
|
function loadSddStatusSync(directory) {
|
|
382
|
-
const root =
|
|
383
|
-
const swSpecPath =
|
|
384
|
-
const openSpecPath =
|
|
1325
|
+
const root = path4.resolve(directory);
|
|
1326
|
+
const swSpecPath = path4.join(root, SWARM_SPEC_REL);
|
|
1327
|
+
const openSpecPath = path4.join(root, OPENSPEC_ROOT);
|
|
385
1328
|
const errors = [];
|
|
386
1329
|
const warnings = [];
|
|
387
|
-
const swSpecExists =
|
|
388
|
-
const openSpecExists =
|
|
389
|
-
const currentSpecs = walkSpecFiles(root,
|
|
1330
|
+
const swSpecExists = fs4.existsSync(swSpecPath);
|
|
1331
|
+
const openSpecExists = fs4.existsSync(openSpecPath);
|
|
1332
|
+
const currentSpecs = walkSpecFiles(root, path4.join(OPENSPEC_ROOT, "specs"));
|
|
390
1333
|
const changes = listOpenSpecChanges(root);
|
|
391
1334
|
const effectiveSpec = readEffectiveSpecSync(root);
|
|
392
1335
|
if (openSpecExists && currentSpecs.length === 0 && changes.length === 0) {
|
|
@@ -415,8 +1358,8 @@ function loadSddStatusSync(directory) {
|
|
|
415
1358
|
};
|
|
416
1359
|
}
|
|
417
1360
|
function buildOpenSpecProjectionSync(directory, options = {}) {
|
|
418
|
-
const root =
|
|
419
|
-
const currentSpecs = walkSpecFiles(root,
|
|
1361
|
+
const root = path4.resolve(directory);
|
|
1362
|
+
const currentSpecs = walkSpecFiles(root, path4.join(OPENSPEC_ROOT, "specs"));
|
|
420
1363
|
const allChanges = listOpenSpecChanges(root);
|
|
421
1364
|
const changes = options.changeId ? allChanges.filter((change) => change.id === options.changeId) : allChanges;
|
|
422
1365
|
const sourcePaths = [];
|
|
@@ -431,7 +1374,7 @@ function buildOpenSpecProjectionSync(directory, options = {}) {
|
|
|
431
1374
|
if (currentSpecs.length === 0 && changes.length === 0)
|
|
432
1375
|
return null;
|
|
433
1376
|
for (const artifact of currentSpecs) {
|
|
434
|
-
const abs =
|
|
1377
|
+
const abs = path4.join(root, artifact.relPath);
|
|
435
1378
|
const content2 = readTextBounded(abs);
|
|
436
1379
|
if (content2 === null) {
|
|
437
1380
|
warnings.push(`Skipped unreadable or oversized spec ${artifact.relPath}.`);
|
|
@@ -446,7 +1389,7 @@ function buildOpenSpecProjectionSync(directory, options = {}) {
|
|
|
446
1389
|
for (const change of changes) {
|
|
447
1390
|
const reqs = [];
|
|
448
1391
|
for (const artifact of change.specs) {
|
|
449
|
-
const abs =
|
|
1392
|
+
const abs = path4.join(root, artifact.relPath);
|
|
450
1393
|
const content2 = readTextBounded(abs);
|
|
451
1394
|
if (content2 === null) {
|
|
452
1395
|
warnings.push(`Skipped unreadable or oversized spec ${artifact.relPath}.`);
|
|
@@ -513,12 +1456,12 @@ function buildOpenSpecProjectionSync(directory, options = {}) {
|
|
|
513
1456
|
};
|
|
514
1457
|
}
|
|
515
1458
|
function readEffectiveSpecSync(directory) {
|
|
516
|
-
const root =
|
|
517
|
-
const swSpecPath =
|
|
1459
|
+
const root = path4.resolve(directory);
|
|
1460
|
+
const swSpecPath = path4.join(root, SWARM_SPEC_REL);
|
|
518
1461
|
try {
|
|
519
|
-
const stat =
|
|
1462
|
+
const stat = fs4.lstatSync(swSpecPath);
|
|
520
1463
|
if (stat.isFile() && stat.size <= MAX_SPEC_BYTES) {
|
|
521
|
-
const content =
|
|
1464
|
+
const content = fs4.readFileSync(swSpecPath, "utf-8");
|
|
522
1465
|
return {
|
|
523
1466
|
source: "swarm",
|
|
524
1467
|
content,
|
|
@@ -528,49 +1471,49 @@ function readEffectiveSpecSync(directory) {
|
|
|
528
1471
|
warnings: []
|
|
529
1472
|
};
|
|
530
1473
|
}
|
|
531
|
-
} catch (
|
|
532
|
-
if (
|
|
533
|
-
throw
|
|
1474
|
+
} catch (error2) {
|
|
1475
|
+
if (error2.code !== "ENOENT") {
|
|
1476
|
+
throw error2;
|
|
534
1477
|
}
|
|
535
1478
|
}
|
|
536
1479
|
return buildOpenSpecProjectionSync(root);
|
|
537
1480
|
}
|
|
538
1481
|
function writeProjectedSpecSync(directory, options = {}) {
|
|
539
|
-
const root =
|
|
1482
|
+
const root = path4.resolve(directory);
|
|
540
1483
|
const projection = buildOpenSpecProjectionSync(root, {
|
|
541
1484
|
changeId: options.changeId
|
|
542
1485
|
});
|
|
543
|
-
const target =
|
|
1486
|
+
const target = path4.join(root, SWARM_SPEC_REL);
|
|
544
1487
|
if (!projection || options.dryRun) {
|
|
545
1488
|
return { written: false, projection, path: target };
|
|
546
1489
|
}
|
|
547
|
-
|
|
1490
|
+
fs4.mkdirSync(path4.dirname(target), { recursive: true });
|
|
548
1491
|
let archivePath;
|
|
549
|
-
if (
|
|
550
|
-
const prior =
|
|
1492
|
+
if (fs4.existsSync(target)) {
|
|
1493
|
+
const prior = fs4.readFileSync(target, "utf-8");
|
|
551
1494
|
if (prior !== projection.content) {
|
|
552
|
-
const archiveDir =
|
|
553
|
-
|
|
1495
|
+
const archiveDir = path4.join(root, ".swarm", "spec-archive");
|
|
1496
|
+
fs4.mkdirSync(archiveDir, { recursive: true });
|
|
554
1497
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
555
|
-
archivePath =
|
|
556
|
-
|
|
1498
|
+
archivePath = path4.join(archiveDir, `sdd-projection-${stamp}.md`);
|
|
1499
|
+
fs4.writeFileSync(archivePath, prior, "utf-8");
|
|
557
1500
|
}
|
|
558
1501
|
}
|
|
559
1502
|
const tmp = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
560
|
-
|
|
561
|
-
|
|
1503
|
+
fs4.writeFileSync(tmp, projection.content, "utf-8");
|
|
1504
|
+
fs4.renameSync(tmp, target);
|
|
562
1505
|
return { written: true, projection, archivePath, path: target };
|
|
563
1506
|
}
|
|
564
1507
|
|
|
565
1508
|
// src/utils/spec-hash.ts
|
|
566
1509
|
async function computeSpecHash(directory) {
|
|
567
|
-
const spec =
|
|
1510
|
+
const spec = _internals3.readEffectiveSpecSync(directory);
|
|
568
1511
|
if (!spec)
|
|
569
1512
|
return null;
|
|
570
1513
|
return createHash2("sha256").update(spec.content, "utf-8").digest("hex");
|
|
571
1514
|
}
|
|
572
1515
|
async function isSpecStale(directory, plan) {
|
|
573
|
-
const currentHash = await
|
|
1516
|
+
const currentHash = await _internals3.computeSpecHash(directory);
|
|
574
1517
|
if (!plan.specHash) {
|
|
575
1518
|
return { stale: false };
|
|
576
1519
|
}
|
|
@@ -590,7 +1533,7 @@ async function isSpecStale(directory, plan) {
|
|
|
590
1533
|
}
|
|
591
1534
|
return { stale: false };
|
|
592
1535
|
}
|
|
593
|
-
var
|
|
1536
|
+
var _internals3 = {
|
|
594
1537
|
computeSpecHash,
|
|
595
1538
|
isSpecStale,
|
|
596
1539
|
readEffectiveSpecSync
|
|
@@ -598,8 +1541,8 @@ var _internals = {
|
|
|
598
1541
|
|
|
599
1542
|
// src/plan/ledger.ts
|
|
600
1543
|
import * as crypto from "crypto";
|
|
601
|
-
import * as
|
|
602
|
-
import * as
|
|
1544
|
+
import * as fs5 from "fs";
|
|
1545
|
+
import * as path5 from "path";
|
|
603
1546
|
|
|
604
1547
|
// src/plan/utils.ts
|
|
605
1548
|
function derivePlanId(plan) {
|
|
@@ -618,10 +1561,10 @@ class LedgerStaleWriterError extends Error {
|
|
|
618
1561
|
}
|
|
619
1562
|
}
|
|
620
1563
|
function getLedgerPath(directory) {
|
|
621
|
-
return
|
|
1564
|
+
return path5.join(directory, ".swarm", LEDGER_FILENAME);
|
|
622
1565
|
}
|
|
623
1566
|
function getPlanJsonPath(directory) {
|
|
624
|
-
return
|
|
1567
|
+
return path5.join(directory, ".swarm", PLAN_JSON_FILENAME);
|
|
625
1568
|
}
|
|
626
1569
|
function computePlanHash(plan) {
|
|
627
1570
|
const normalized = {
|
|
@@ -656,7 +1599,7 @@ function computePlanHash(plan) {
|
|
|
656
1599
|
function computeCurrentPlanHash(directory) {
|
|
657
1600
|
const planPath = getPlanJsonPath(directory);
|
|
658
1601
|
try {
|
|
659
|
-
const content =
|
|
1602
|
+
const content = fs5.readFileSync(planPath, "utf8");
|
|
660
1603
|
const plan = JSON.parse(content);
|
|
661
1604
|
return computePlanHash(plan);
|
|
662
1605
|
} catch {
|
|
@@ -665,15 +1608,15 @@ function computeCurrentPlanHash(directory) {
|
|
|
665
1608
|
}
|
|
666
1609
|
async function ledgerExists(directory) {
|
|
667
1610
|
const ledgerPath = getLedgerPath(directory);
|
|
668
|
-
return
|
|
1611
|
+
return fs5.existsSync(ledgerPath);
|
|
669
1612
|
}
|
|
670
1613
|
async function getLatestLedgerSeq(directory) {
|
|
671
1614
|
const ledgerPath = getLedgerPath(directory);
|
|
672
|
-
if (!
|
|
1615
|
+
if (!fs5.existsSync(ledgerPath)) {
|
|
673
1616
|
return 0;
|
|
674
1617
|
}
|
|
675
1618
|
try {
|
|
676
|
-
const content =
|
|
1619
|
+
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
677
1620
|
const lines = content.trim().split(`
|
|
678
1621
|
`).filter((line) => line.trim() !== "");
|
|
679
1622
|
if (lines.length === 0) {
|
|
@@ -695,11 +1638,11 @@ async function getLatestLedgerSeq(directory) {
|
|
|
695
1638
|
}
|
|
696
1639
|
async function readLedgerEvents(directory) {
|
|
697
1640
|
const ledgerPath = getLedgerPath(directory);
|
|
698
|
-
if (!
|
|
1641
|
+
if (!fs5.existsSync(ledgerPath)) {
|
|
699
1642
|
return [];
|
|
700
1643
|
}
|
|
701
1644
|
try {
|
|
702
|
-
const content =
|
|
1645
|
+
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
703
1646
|
const lines = content.trim().split(`
|
|
704
1647
|
`).filter((line) => line.trim() !== "");
|
|
705
1648
|
const events = [];
|
|
@@ -724,15 +1667,15 @@ async function readLedgerEvents(directory) {
|
|
|
724
1667
|
async function initLedger(directory, planId, initialPlanHash, initialPlan) {
|
|
725
1668
|
const ledgerPath = getLedgerPath(directory);
|
|
726
1669
|
const planJsonPath = getPlanJsonPath(directory);
|
|
727
|
-
if (
|
|
1670
|
+
if (fs5.existsSync(ledgerPath)) {
|
|
728
1671
|
throw new Error("Ledger already initialized. Use appendLedgerEvent to add events.");
|
|
729
1672
|
}
|
|
730
1673
|
let planHashAfter = initialPlanHash ?? "";
|
|
731
1674
|
let embeddedPlan = initialPlan;
|
|
732
1675
|
if (!initialPlanHash) {
|
|
733
1676
|
try {
|
|
734
|
-
if (
|
|
735
|
-
const content =
|
|
1677
|
+
if (fs5.existsSync(planJsonPath)) {
|
|
1678
|
+
const content = fs5.readFileSync(planJsonPath, "utf8");
|
|
736
1679
|
const plan = JSON.parse(content);
|
|
737
1680
|
planHashAfter = computePlanHash(plan);
|
|
738
1681
|
if (!embeddedPlan)
|
|
@@ -752,12 +1695,12 @@ async function initLedger(directory, planId, initialPlanHash, initialPlan) {
|
|
|
752
1695
|
schema_version: LEDGER_SCHEMA_VERSION,
|
|
753
1696
|
...payload ? { payload } : {}
|
|
754
1697
|
};
|
|
755
|
-
|
|
1698
|
+
fs5.mkdirSync(path5.join(directory, ".swarm"), { recursive: true });
|
|
756
1699
|
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
757
1700
|
const line = `${JSON.stringify(event)}
|
|
758
1701
|
`;
|
|
759
|
-
|
|
760
|
-
|
|
1702
|
+
fs5.writeFileSync(tempPath, line, "utf8");
|
|
1703
|
+
fs5.renameSync(tempPath, ledgerPath);
|
|
761
1704
|
}
|
|
762
1705
|
async function appendLedgerEvent(directory, eventInput, options) {
|
|
763
1706
|
const ledgerPath = getLedgerPath(directory);
|
|
@@ -779,17 +1722,17 @@ async function appendLedgerEvent(directory, eventInput, options) {
|
|
|
779
1722
|
plan_hash_after: planHashAfter,
|
|
780
1723
|
schema_version: LEDGER_SCHEMA_VERSION
|
|
781
1724
|
};
|
|
782
|
-
|
|
1725
|
+
fs5.mkdirSync(path5.join(directory, ".swarm"), { recursive: true });
|
|
783
1726
|
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
784
1727
|
const line = `${JSON.stringify(event)}
|
|
785
1728
|
`;
|
|
786
|
-
if (
|
|
787
|
-
const existingContent =
|
|
788
|
-
|
|
1729
|
+
if (fs5.existsSync(ledgerPath)) {
|
|
1730
|
+
const existingContent = fs5.readFileSync(ledgerPath, "utf8");
|
|
1731
|
+
fs5.writeFileSync(tempPath, existingContent + line, "utf8");
|
|
789
1732
|
} else {
|
|
790
1733
|
throw new Error("Ledger not initialized. Call initLedger() first.");
|
|
791
1734
|
}
|
|
792
|
-
|
|
1735
|
+
fs5.renameSync(tempPath, ledgerPath);
|
|
793
1736
|
return event;
|
|
794
1737
|
}
|
|
795
1738
|
async function takeSnapshotWithRetry(directory, plan, options) {
|
|
@@ -876,12 +1819,12 @@ async function replayFromLedger(directory, _options) {
|
|
|
876
1819
|
}
|
|
877
1820
|
}
|
|
878
1821
|
const planJsonPath = getPlanJsonPath(directory);
|
|
879
|
-
if (!
|
|
1822
|
+
if (!fs5.existsSync(planJsonPath)) {
|
|
880
1823
|
return null;
|
|
881
1824
|
}
|
|
882
1825
|
let plan;
|
|
883
1826
|
try {
|
|
884
|
-
const content =
|
|
1827
|
+
const content = fs5.readFileSync(planJsonPath, "utf8");
|
|
885
1828
|
plan = JSON.parse(content);
|
|
886
1829
|
} catch {
|
|
887
1830
|
return null;
|
|
@@ -976,11 +1919,11 @@ function applyEventToPlan(plan, event) {
|
|
|
976
1919
|
}
|
|
977
1920
|
async function readLedgerEventsWithIntegrity(directory) {
|
|
978
1921
|
const ledgerPath = getLedgerPath(directory);
|
|
979
|
-
if (!
|
|
1922
|
+
if (!fs5.existsSync(ledgerPath)) {
|
|
980
1923
|
return { events: [], truncated: false, badSuffix: null };
|
|
981
1924
|
}
|
|
982
1925
|
try {
|
|
983
|
-
const content =
|
|
1926
|
+
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
984
1927
|
const lines = content.split(`
|
|
985
1928
|
`);
|
|
986
1929
|
const events = [];
|
|
@@ -1009,9 +1952,9 @@ async function readLedgerEventsWithIntegrity(directory) {
|
|
|
1009
1952
|
}
|
|
1010
1953
|
async function quarantineLedgerSuffix(directory, badSuffix) {
|
|
1011
1954
|
try {
|
|
1012
|
-
const quarantinePath =
|
|
1013
|
-
|
|
1014
|
-
console.warn(`[ledger] Corrupted suffix quarantined to ${
|
|
1955
|
+
const quarantinePath = path5.join(directory, ".swarm", "plan-ledger.quarantine");
|
|
1956
|
+
fs5.writeFileSync(quarantinePath, badSuffix, "utf8");
|
|
1957
|
+
console.warn(`[ledger] Corrupted suffix quarantined to ${path5.relative(directory, quarantinePath)}`);
|
|
1015
1958
|
} catch {}
|
|
1016
1959
|
}
|
|
1017
1960
|
async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
@@ -1069,10 +2012,15 @@ class PlanTaskRemovalNotAcknowledgedError extends Error {
|
|
|
1069
2012
|
var startupLedgerCheckedWorkspaces = new Set;
|
|
1070
2013
|
var recoveryMutexes = new Map;
|
|
1071
2014
|
var PLAN_JSON_CACHE_NAMESPACE = "plan-json:validated:v1";
|
|
1072
|
-
var
|
|
2015
|
+
var _internals4 = {
|
|
1073
2016
|
loadPlan,
|
|
1074
2017
|
loadPlanJsonOnly,
|
|
1075
|
-
regeneratePlanMarkdown
|
|
2018
|
+
regeneratePlanMarkdown,
|
|
2019
|
+
isGitRepo,
|
|
2020
|
+
isEpicModeActiveForProject,
|
|
2021
|
+
readTaskScopes,
|
|
2022
|
+
commitTaskCompletion,
|
|
2023
|
+
getWorktreeMergeFailure
|
|
1076
2024
|
};
|
|
1077
2025
|
var CAS_BACKOFF_START_MS = 5;
|
|
1078
2026
|
var CAS_BACKOFF_CAP_MS = 250;
|
|
@@ -1088,9 +2036,9 @@ async function retryCasWithBackoff(directory, eventInput, options) {
|
|
|
1088
2036
|
expectedHash: currentExpected,
|
|
1089
2037
|
planHashAfter: options.planHashAfter
|
|
1090
2038
|
});
|
|
1091
|
-
} catch (
|
|
1092
|
-
if (!(
|
|
1093
|
-
throw
|
|
2039
|
+
} catch (error2) {
|
|
2040
|
+
if (!(error2 instanceof LedgerStaleWriterError) || attempt >= maxRetries) {
|
|
2041
|
+
throw error2;
|
|
1094
2042
|
}
|
|
1095
2043
|
attempt++;
|
|
1096
2044
|
const base = Math.min(CAS_BACKOFF_START_MS * 2 ** (attempt - 1), CAS_BACKOFF_CAP_MS);
|
|
@@ -1101,7 +2049,7 @@ async function retryCasWithBackoff(directory, eventInput, options) {
|
|
|
1101
2049
|
expectedHashPrefix: currentExpected.slice(0, 8),
|
|
1102
2050
|
delayMs
|
|
1103
2051
|
});
|
|
1104
|
-
await new Promise((
|
|
2052
|
+
await new Promise((resolve4) => setTimeout(resolve4, delayMs));
|
|
1105
2053
|
if (options.verifyValid) {
|
|
1106
2054
|
const stillValid = await options.verifyValid();
|
|
1107
2055
|
if (!stillValid)
|
|
@@ -1114,8 +2062,8 @@ async function retryCasWithBackoff(directory, eventInput, options) {
|
|
|
1114
2062
|
async function loadPlanJsonOnly(directory) {
|
|
1115
2063
|
try {
|
|
1116
2064
|
return await parsePlanJsonCached(directory);
|
|
1117
|
-
} catch (
|
|
1118
|
-
warn(`Plan validation failed for .swarm/plan.json: ${
|
|
2065
|
+
} catch (error2) {
|
|
2066
|
+
warn(`Plan validation failed for .swarm/plan.json: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
1119
2067
|
}
|
|
1120
2068
|
return null;
|
|
1121
2069
|
}
|
|
@@ -1144,7 +2092,7 @@ async function getLatestLedgerHash(directory) {
|
|
|
1144
2092
|
}
|
|
1145
2093
|
}
|
|
1146
2094
|
async function parsePlanJsonCached(directory) {
|
|
1147
|
-
const planJsonPath =
|
|
2095
|
+
const planJsonPath = path6.resolve(directory, ".swarm", "plan.json");
|
|
1148
2096
|
return readCachedParsedFile(planJsonPath, PLAN_JSON_CACHE_NAMESPACE, () => readSwarmFileAsync(directory, "plan.json"), (planJsonContent) => {
|
|
1149
2097
|
if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
|
|
1150
2098
|
throw new Error("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
|
|
@@ -1204,19 +2152,19 @@ async function isPlanMdInSync(directory, plan) {
|
|
|
1204
2152
|
return normalizedActual.includes(normalizedExpected) || normalizedExpected.includes(normalizedActual.replace(/^#.*$/gm, "").trim());
|
|
1205
2153
|
}
|
|
1206
2154
|
async function regeneratePlanMarkdown(directory, plan) {
|
|
1207
|
-
const swarmDir =
|
|
2155
|
+
const swarmDir = path6.resolve(directory, ".swarm");
|
|
1208
2156
|
const contentHash = computePlanContentHash(plan);
|
|
1209
2157
|
const markdown = derivePlanMarkdown(plan);
|
|
1210
2158
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
1211
2159
|
${markdown}`;
|
|
1212
|
-
const mdPath =
|
|
1213
|
-
const mdTempPath =
|
|
2160
|
+
const mdPath = path6.join(swarmDir, "plan.md");
|
|
2161
|
+
const mdTempPath = path6.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1214
2162
|
try {
|
|
1215
2163
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
1216
|
-
|
|
2164
|
+
renameSync5(mdTempPath, mdPath);
|
|
1217
2165
|
} finally {
|
|
1218
2166
|
try {
|
|
1219
|
-
|
|
2167
|
+
unlinkSync3(mdTempPath);
|
|
1220
2168
|
} catch {}
|
|
1221
2169
|
}
|
|
1222
2170
|
}
|
|
@@ -1234,7 +2182,7 @@ async function loadPlan(directory) {
|
|
|
1234
2182
|
const inSync = await isPlanMdInSync(directory, validated);
|
|
1235
2183
|
if (!inSync) {
|
|
1236
2184
|
try {
|
|
1237
|
-
await
|
|
2185
|
+
await _internals4.regeneratePlanMarkdown(directory, validated);
|
|
1238
2186
|
} catch (regenError) {
|
|
1239
2187
|
warn(`Failed to regenerate plan.md: ${regenError instanceof Error ? regenError.message : String(regenError)}. Proceeding with plan.json only.`);
|
|
1240
2188
|
}
|
|
@@ -1242,7 +2190,7 @@ async function loadPlan(directory) {
|
|
|
1242
2190
|
if (await ledgerExists(directory)) {
|
|
1243
2191
|
const planHash = computePlanHash(validated);
|
|
1244
2192
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
1245
|
-
const resolvedWorkspace =
|
|
2193
|
+
const resolvedWorkspace = path6.resolve(directory);
|
|
1246
2194
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
1247
2195
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
1248
2196
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
@@ -1299,7 +2247,7 @@ async function loadPlan(directory) {
|
|
|
1299
2247
|
runtimePlan._specStale = true;
|
|
1300
2248
|
runtimePlan._specStaleReason = staleResult.reason;
|
|
1301
2249
|
try {
|
|
1302
|
-
const specStalenessPath =
|
|
2250
|
+
const specStalenessPath = path6.join(directory, ".swarm", "spec-staleness.json");
|
|
1303
2251
|
await fsPromises.writeFile(specStalenessPath, JSON.stringify({
|
|
1304
2252
|
type: "spec_stale_detected",
|
|
1305
2253
|
timestamp: new Date().toISOString(),
|
|
@@ -1311,7 +2259,7 @@ async function loadPlan(directory) {
|
|
|
1311
2259
|
}, null, 2), "utf-8");
|
|
1312
2260
|
} catch {}
|
|
1313
2261
|
try {
|
|
1314
|
-
const eventsPath =
|
|
2262
|
+
const eventsPath = path6.join(directory, ".swarm", "events.jsonl");
|
|
1315
2263
|
const event = {
|
|
1316
2264
|
type: "spec_stale_detected",
|
|
1317
2265
|
timestamp: new Date().toISOString(),
|
|
@@ -1328,8 +2276,8 @@ async function loadPlan(directory) {
|
|
|
1328
2276
|
}
|
|
1329
2277
|
return validated;
|
|
1330
2278
|
}
|
|
1331
|
-
} catch (
|
|
1332
|
-
warn(`[loadPlan] plan.json validation failed: ${
|
|
2279
|
+
} catch (error2) {
|
|
2280
|
+
warn(`[loadPlan] plan.json validation failed: ${error2 instanceof Error ? error2.message : String(error2)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/SWARM_PLAN.md for a checkpoint.`);
|
|
1333
2281
|
let rawPlanId = null;
|
|
1334
2282
|
try {
|
|
1335
2283
|
const rawParsed = JSON.parse(planJsonContent);
|
|
@@ -1376,11 +2324,11 @@ async function loadPlan(directory) {
|
|
|
1376
2324
|
return migrated;
|
|
1377
2325
|
}
|
|
1378
2326
|
if (await ledgerExists(directory)) {
|
|
1379
|
-
const resolvedDir =
|
|
2327
|
+
const resolvedDir = path6.resolve(directory);
|
|
1380
2328
|
const existingMutex = recoveryMutexes.get(resolvedDir);
|
|
1381
2329
|
if (existingMutex) {
|
|
1382
2330
|
await existingMutex;
|
|
1383
|
-
const postRecoveryPlan = await
|
|
2331
|
+
const postRecoveryPlan = await _internals4.loadPlanJsonOnly(directory);
|
|
1384
2332
|
if (postRecoveryPlan)
|
|
1385
2333
|
return postRecoveryPlan;
|
|
1386
2334
|
}
|
|
@@ -1440,7 +2388,7 @@ async function loadPlan(directory) {
|
|
|
1440
2388
|
return null;
|
|
1441
2389
|
}
|
|
1442
2390
|
async function savePlanWithAutoAcknowledgedRemovals(directory, plan, source, reason, options) {
|
|
1443
|
-
const existing = await
|
|
2391
|
+
const existing = await _internals4.loadPlanJsonOnly(directory);
|
|
1444
2392
|
const newIds = new Set;
|
|
1445
2393
|
for (const phase of plan.phases) {
|
|
1446
2394
|
for (const task of phase.tasks)
|
|
@@ -1468,7 +2416,7 @@ async function savePlan(directory, plan, options) {
|
|
|
1468
2416
|
const validated = PlanSchema.parse(plan);
|
|
1469
2417
|
if (options?.preserveCompletedStatuses !== false) {
|
|
1470
2418
|
try {
|
|
1471
|
-
const currentPlan2 = await
|
|
2419
|
+
const currentPlan2 = await _internals4.loadPlanJsonOnly(directory);
|
|
1472
2420
|
if (currentPlan2) {
|
|
1473
2421
|
const completedTaskIds = new Set;
|
|
1474
2422
|
for (const phase of currentPlan2.phases) {
|
|
@@ -1501,7 +2449,7 @@ async function savePlan(directory, plan, options) {
|
|
|
1501
2449
|
phase.status = "pending";
|
|
1502
2450
|
}
|
|
1503
2451
|
}
|
|
1504
|
-
const currentPlan = await
|
|
2452
|
+
const currentPlan = await _internals4.loadPlanJsonOnly(directory);
|
|
1505
2453
|
const planId = derivePlanId(validated);
|
|
1506
2454
|
const planHashForInit = computePlanHash(validated);
|
|
1507
2455
|
if (!await ledgerExists(directory)) {
|
|
@@ -1516,13 +2464,13 @@ async function savePlan(directory, plan, options) {
|
|
|
1516
2464
|
} else {
|
|
1517
2465
|
const existingEvents = await readLedgerEvents(directory);
|
|
1518
2466
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
1519
|
-
const swarmDir2 =
|
|
1520
|
-
const oldLedgerPath =
|
|
1521
|
-
const oldLedgerBackupPath =
|
|
2467
|
+
const swarmDir2 = path6.resolve(directory, ".swarm");
|
|
2468
|
+
const oldLedgerPath = path6.join(swarmDir2, "plan-ledger.jsonl");
|
|
2469
|
+
const oldLedgerBackupPath = path6.join(swarmDir2, `plan-ledger.backup-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
1522
2470
|
let backupExists = false;
|
|
1523
|
-
if (
|
|
2471
|
+
if (existsSync6(oldLedgerPath)) {
|
|
1524
2472
|
try {
|
|
1525
|
-
|
|
2473
|
+
renameSync5(oldLedgerPath, oldLedgerBackupPath);
|
|
1526
2474
|
backupExists = true;
|
|
1527
2475
|
} catch (renameErr) {
|
|
1528
2476
|
throw new Error(`[savePlan] Cannot reinitialize ledger: could not move old ledger aside (rename failed: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}). The existing ledger has plan_id="${existingEvents[0].plan_id}" which does not match the current plan="${planId}". To proceed, close any programs that may have the ledger file open, or run /swarm reset-session to clear the ledger.`);
|
|
@@ -1534,20 +2482,20 @@ async function savePlan(directory, plan, options) {
|
|
|
1534
2482
|
await initLedger(directory, planId, planHashForInit, validated);
|
|
1535
2483
|
initSucceeded = true;
|
|
1536
2484
|
} catch (initErr) {
|
|
1537
|
-
const
|
|
1538
|
-
if (
|
|
2485
|
+
const errorMessage2 = String(initErr);
|
|
2486
|
+
if (errorMessage2.includes("already initialized")) {
|
|
1539
2487
|
try {
|
|
1540
|
-
if (
|
|
1541
|
-
|
|
2488
|
+
if (existsSync6(oldLedgerBackupPath))
|
|
2489
|
+
unlinkSync3(oldLedgerBackupPath);
|
|
1542
2490
|
} catch {}
|
|
1543
2491
|
} else {
|
|
1544
|
-
if (
|
|
2492
|
+
if (existsSync6(oldLedgerBackupPath)) {
|
|
1545
2493
|
try {
|
|
1546
|
-
|
|
2494
|
+
renameSync5(oldLedgerBackupPath, oldLedgerPath);
|
|
1547
2495
|
} catch {
|
|
1548
2496
|
copyFileSync(oldLedgerBackupPath, oldLedgerPath);
|
|
1549
2497
|
try {
|
|
1550
|
-
|
|
2498
|
+
unlinkSync3(oldLedgerBackupPath);
|
|
1551
2499
|
} catch {}
|
|
1552
2500
|
}
|
|
1553
2501
|
}
|
|
@@ -1556,21 +2504,21 @@ async function savePlan(directory, plan, options) {
|
|
|
1556
2504
|
}
|
|
1557
2505
|
}
|
|
1558
2506
|
if (initSucceeded && backupExists) {
|
|
1559
|
-
const archivePath =
|
|
2507
|
+
const archivePath = path6.join(swarmDir2, `plan-ledger.archived-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
1560
2508
|
try {
|
|
1561
|
-
|
|
2509
|
+
renameSync5(oldLedgerBackupPath, archivePath);
|
|
1562
2510
|
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived old ledger to ${archivePath} and reinitializing.`);
|
|
1563
2511
|
} catch (renameErr) {
|
|
1564
2512
|
warn(`[savePlan] Could not archive old ledger (rename failed: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}). Old ledger may still exist at ${oldLedgerBackupPath}.`);
|
|
1565
2513
|
try {
|
|
1566
|
-
if (
|
|
1567
|
-
|
|
2514
|
+
if (existsSync6(oldLedgerBackupPath))
|
|
2515
|
+
unlinkSync3(oldLedgerBackupPath);
|
|
1568
2516
|
} catch {}
|
|
1569
2517
|
}
|
|
1570
2518
|
} else if (!initSucceeded && backupExists) {
|
|
1571
2519
|
try {
|
|
1572
|
-
if (
|
|
1573
|
-
|
|
2520
|
+
if (existsSync6(oldLedgerBackupPath))
|
|
2521
|
+
unlinkSync3(oldLedgerBackupPath);
|
|
1574
2522
|
} catch {}
|
|
1575
2523
|
}
|
|
1576
2524
|
const MAX_ARCHIVED_SIBLINGS = 5;
|
|
@@ -1581,7 +2529,7 @@ async function savePlan(directory, plan, options) {
|
|
|
1581
2529
|
const toRemove = archivedSiblings.slice(0, archivedSiblings.length - MAX_ARCHIVED_SIBLINGS);
|
|
1582
2530
|
for (const file of toRemove) {
|
|
1583
2531
|
try {
|
|
1584
|
-
|
|
2532
|
+
unlinkSync3(path6.join(swarmDir2, file));
|
|
1585
2533
|
} catch {}
|
|
1586
2534
|
}
|
|
1587
2535
|
}
|
|
@@ -1646,7 +2594,7 @@ async function savePlan(directory, plan, options) {
|
|
|
1646
2594
|
expectedHash: currentHash,
|
|
1647
2595
|
planHashAfter: hashAfter,
|
|
1648
2596
|
verifyValid: async () => {
|
|
1649
|
-
const onDisk = await
|
|
2597
|
+
const onDisk = await _internals4.loadPlanJsonOnly(directory);
|
|
1650
2598
|
if (!onDisk)
|
|
1651
2599
|
return true;
|
|
1652
2600
|
for (const p of onDisk.phases) {
|
|
@@ -1657,11 +2605,11 @@ async function savePlan(directory, plan, options) {
|
|
|
1657
2605
|
}
|
|
1658
2606
|
});
|
|
1659
2607
|
}
|
|
1660
|
-
} catch (
|
|
1661
|
-
if (
|
|
1662
|
-
throw new PlanConcurrentModificationError(`Concurrent plan modification detected after retries: ${
|
|
2608
|
+
} catch (error2) {
|
|
2609
|
+
if (error2 instanceof LedgerStaleWriterError) {
|
|
2610
|
+
throw new PlanConcurrentModificationError(`Concurrent plan modification detected after retries: ${error2.message}. Please retry the operation.`);
|
|
1663
2611
|
}
|
|
1664
|
-
throw
|
|
2612
|
+
throw error2;
|
|
1665
2613
|
}
|
|
1666
2614
|
}
|
|
1667
2615
|
try {
|
|
@@ -1684,7 +2632,7 @@ async function savePlan(directory, plan, options) {
|
|
|
1684
2632
|
expectedHash: currentHash,
|
|
1685
2633
|
planHashAfter: hashAfter,
|
|
1686
2634
|
verifyValid: async () => {
|
|
1687
|
-
const onDisk = await
|
|
2635
|
+
const onDisk = await _internals4.loadPlanJsonOnly(directory);
|
|
1688
2636
|
if (!onDisk)
|
|
1689
2637
|
return true;
|
|
1690
2638
|
for (const p of onDisk.phases) {
|
|
@@ -1699,11 +2647,11 @@ async function savePlan(directory, plan, options) {
|
|
|
1699
2647
|
}
|
|
1700
2648
|
}
|
|
1701
2649
|
}
|
|
1702
|
-
} catch (
|
|
1703
|
-
if (
|
|
1704
|
-
throw new PlanConcurrentModificationError(`Concurrent plan modification detected after retries: ${
|
|
2650
|
+
} catch (error2) {
|
|
2651
|
+
if (error2 instanceof LedgerStaleWriterError) {
|
|
2652
|
+
throw new PlanConcurrentModificationError(`Concurrent plan modification detected after retries: ${error2.message}. Please retry the operation.`);
|
|
1705
2653
|
}
|
|
1706
|
-
throw
|
|
2654
|
+
throw error2;
|
|
1707
2655
|
}
|
|
1708
2656
|
}
|
|
1709
2657
|
const SNAPSHOT_INTERVAL = 50;
|
|
@@ -1714,19 +2662,19 @@ async function savePlan(directory, plan, options) {
|
|
|
1714
2662
|
source: "savePlan_manager"
|
|
1715
2663
|
});
|
|
1716
2664
|
}
|
|
1717
|
-
const swarmDir =
|
|
1718
|
-
const planPath =
|
|
1719
|
-
const tempPath =
|
|
2665
|
+
const swarmDir = path6.resolve(directory, ".swarm");
|
|
2666
|
+
const planPath = path6.join(swarmDir, "plan.json");
|
|
2667
|
+
const tempPath = path6.join(swarmDir, `plan.json.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1720
2668
|
try {
|
|
1721
2669
|
await bunWrite(tempPath, JSON.stringify(validated, null, 2));
|
|
1722
|
-
|
|
2670
|
+
renameSync5(tempPath, planPath);
|
|
1723
2671
|
} finally {
|
|
1724
2672
|
try {
|
|
1725
|
-
|
|
2673
|
+
unlinkSync3(tempPath);
|
|
1726
2674
|
} catch {}
|
|
1727
2675
|
}
|
|
1728
2676
|
try {
|
|
1729
|
-
const markerPath =
|
|
2677
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
1730
2678
|
const inProgressMarker = JSON.stringify({
|
|
1731
2679
|
source: "plan_manager",
|
|
1732
2680
|
timestamp: new Date().toISOString(),
|
|
@@ -1741,14 +2689,14 @@ async function savePlan(directory, plan, options) {
|
|
|
1741
2689
|
const markdown = derivePlanMarkdown(validated);
|
|
1742
2690
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
1743
2691
|
${markdown}`;
|
|
1744
|
-
const mdPath =
|
|
1745
|
-
const mdTempPath =
|
|
2692
|
+
const mdPath = path6.join(swarmDir, "plan.md");
|
|
2693
|
+
const mdTempPath = path6.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1746
2694
|
try {
|
|
1747
2695
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
1748
|
-
|
|
2696
|
+
renameSync5(mdTempPath, mdPath);
|
|
1749
2697
|
} finally {
|
|
1750
2698
|
try {
|
|
1751
|
-
|
|
2699
|
+
unlinkSync3(mdTempPath);
|
|
1752
2700
|
} catch {}
|
|
1753
2701
|
}
|
|
1754
2702
|
} catch (mdError) {
|
|
@@ -1763,7 +2711,7 @@ ${markdown}`;
|
|
|
1763
2711
|
} catch {}
|
|
1764
2712
|
}
|
|
1765
2713
|
try {
|
|
1766
|
-
const markerPath =
|
|
2714
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
1767
2715
|
const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
1768
2716
|
const marker = JSON.stringify({
|
|
1769
2717
|
source: "plan_manager",
|
|
@@ -1779,14 +2727,14 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
1779
2727
|
const targetPlan = plan ?? await replayFromLedger(directory);
|
|
1780
2728
|
if (!targetPlan)
|
|
1781
2729
|
return null;
|
|
1782
|
-
const swarmDir =
|
|
1783
|
-
const planPath =
|
|
1784
|
-
const mdPath =
|
|
1785
|
-
const tempPlanPath =
|
|
2730
|
+
const swarmDir = path6.join(directory, ".swarm");
|
|
2731
|
+
const planPath = path6.join(swarmDir, "plan.json");
|
|
2732
|
+
const mdPath = path6.join(swarmDir, "plan.md");
|
|
2733
|
+
const tempPlanPath = path6.join(swarmDir, `plan.json.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1786
2734
|
await bunWrite(tempPlanPath, JSON.stringify(targetPlan, null, 2));
|
|
1787
|
-
|
|
2735
|
+
renameSync5(tempPlanPath, planPath);
|
|
1788
2736
|
try {
|
|
1789
|
-
const markerPath =
|
|
2737
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
1790
2738
|
const inProgressMarker = JSON.stringify({
|
|
1791
2739
|
source: "plan_manager",
|
|
1792
2740
|
timestamp: new Date().toISOString(),
|
|
@@ -1801,12 +2749,12 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
1801
2749
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
1802
2750
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
1803
2751
|
${markdown}`;
|
|
1804
|
-
const tempMdPath =
|
|
2752
|
+
const tempMdPath = path6.join(swarmDir, `plan.md.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1805
2753
|
await bunWrite(tempMdPath, markdownWithHash);
|
|
1806
|
-
|
|
2754
|
+
renameSync5(tempMdPath, mdPath);
|
|
1807
2755
|
} finally {
|
|
1808
2756
|
try {
|
|
1809
|
-
const markerPath =
|
|
2757
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
1810
2758
|
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
1811
2759
|
const marker = JSON.stringify({
|
|
1812
2760
|
source: "plan_manager",
|
|
@@ -1869,13 +2817,13 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
1869
2817
|
planHashAfter: hashAfter,
|
|
1870
2818
|
source: "close_terminal"
|
|
1871
2819
|
});
|
|
1872
|
-
const swarmDir =
|
|
1873
|
-
const planPath =
|
|
1874
|
-
const tempPlanPath =
|
|
2820
|
+
const swarmDir = path6.join(directory, ".swarm");
|
|
2821
|
+
const planPath = path6.join(swarmDir, "plan.json");
|
|
2822
|
+
const tempPlanPath = path6.join(swarmDir, `plan.json.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1875
2823
|
await bunWrite(tempPlanPath, JSON.stringify(validated, null, 2));
|
|
1876
|
-
|
|
2824
|
+
renameSync5(tempPlanPath, planPath);
|
|
1877
2825
|
try {
|
|
1878
|
-
const markerPath =
|
|
2826
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
1879
2827
|
const inProgressMarker = JSON.stringify({
|
|
1880
2828
|
source: "plan_manager_close",
|
|
1881
2829
|
timestamp: new Date().toISOString(),
|
|
@@ -1886,17 +2834,17 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
1886
2834
|
await bunWrite(markerPath, inProgressMarker);
|
|
1887
2835
|
} catch {}
|
|
1888
2836
|
try {
|
|
1889
|
-
const mdPath =
|
|
2837
|
+
const mdPath = path6.join(swarmDir, "plan.md");
|
|
1890
2838
|
const contentHash = computePlanContentHash(validated);
|
|
1891
2839
|
const markdown = derivePlanMarkdown(validated);
|
|
1892
2840
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
1893
2841
|
${markdown}`;
|
|
1894
|
-
const mdTempPath =
|
|
2842
|
+
const mdTempPath = path6.join(swarmDir, `plan.md.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1895
2843
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
1896
|
-
|
|
2844
|
+
renameSync5(mdTempPath, mdPath);
|
|
1897
2845
|
} finally {
|
|
1898
2846
|
try {
|
|
1899
|
-
const markerPath =
|
|
2847
|
+
const markerPath = path6.join(swarmDir, ".plan-write-marker");
|
|
1900
2848
|
const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
1901
2849
|
const marker = JSON.stringify({
|
|
1902
2850
|
source: "plan_manager_close",
|
|
@@ -2180,14 +3128,14 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
2180
3128
|
|
|
2181
3129
|
// src/evidence/manager.ts
|
|
2182
3130
|
import {
|
|
2183
|
-
mkdirSync as
|
|
3131
|
+
mkdirSync as mkdirSync5,
|
|
2184
3132
|
readdirSync as readdirSync3,
|
|
2185
3133
|
realpathSync,
|
|
2186
3134
|
rmSync,
|
|
2187
3135
|
statSync
|
|
2188
3136
|
} from "fs";
|
|
2189
|
-
import * as
|
|
2190
|
-
import * as
|
|
3137
|
+
import * as fs6 from "fs/promises";
|
|
3138
|
+
import * as path7 from "path";
|
|
2191
3139
|
|
|
2192
3140
|
// src/config/evidence-schema.ts
|
|
2193
3141
|
var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
|
|
@@ -2519,22 +3467,22 @@ function validateProjectRoot(directory) {
|
|
|
2519
3467
|
if (depth >= MAX_DEPTH)
|
|
2520
3468
|
break;
|
|
2521
3469
|
depth++;
|
|
2522
|
-
const parent =
|
|
3470
|
+
const parent = path7.dirname(current);
|
|
2523
3471
|
if (parent === current)
|
|
2524
3472
|
break;
|
|
2525
|
-
const parentSwarm =
|
|
3473
|
+
const parentSwarm = path7.join(parent, ".swarm");
|
|
2526
3474
|
try {
|
|
2527
3475
|
if (statSync(parentSwarm).isDirectory()) {
|
|
2528
3476
|
let hasProjectIndicator = false;
|
|
2529
3477
|
for (const indicator of PROJECT_INDICATORS) {
|
|
2530
3478
|
try {
|
|
2531
|
-
const indicatorStat = statSync(
|
|
3479
|
+
const indicatorStat = statSync(path7.join(parent, indicator));
|
|
2532
3480
|
if (indicatorStat.isFile() || indicatorStat.isDirectory()) {
|
|
2533
3481
|
hasProjectIndicator = true;
|
|
2534
3482
|
break;
|
|
2535
3483
|
}
|
|
2536
|
-
} catch (
|
|
2537
|
-
if (
|
|
3484
|
+
} catch (error2) {
|
|
3485
|
+
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") {} else {
|
|
2538
3486
|
hasProjectIndicator = true;
|
|
2539
3487
|
break;
|
|
2540
3488
|
}
|
|
@@ -2545,30 +3493,30 @@ function validateProjectRoot(directory) {
|
|
|
2545
3493
|
throw new Error(`Cannot write evidence in "${resolved}" \u2014 parent directory "${parent}" already contains a .swarm/ folder. Evidence must be written to the project root.`);
|
|
2546
3494
|
}
|
|
2547
3495
|
}
|
|
2548
|
-
} catch (
|
|
2549
|
-
if (
|
|
2550
|
-
throw
|
|
3496
|
+
} catch (error2) {
|
|
3497
|
+
if (error2 instanceof Error && error2.message.startsWith("Cannot write evidence")) {
|
|
3498
|
+
throw error2;
|
|
2551
3499
|
}
|
|
2552
3500
|
}
|
|
2553
3501
|
current = parent;
|
|
2554
3502
|
}
|
|
2555
3503
|
}
|
|
2556
3504
|
async function saveEvidence(directory, taskId, evidence) {
|
|
2557
|
-
|
|
3505
|
+
_internals5.validateProjectRoot(directory);
|
|
2558
3506
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
2559
|
-
const relativePath =
|
|
3507
|
+
const relativePath = path7.join("evidence", sanitizedTaskId, "evidence.json");
|
|
2560
3508
|
validateSwarmPath(directory, relativePath);
|
|
2561
3509
|
return withEvidenceLock(directory, relativePath, "evidence-manager", sanitizedTaskId, async () => {
|
|
2562
3510
|
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
2563
|
-
const evidenceDir =
|
|
3511
|
+
const evidenceDir = path7.dirname(evidencePath);
|
|
2564
3512
|
let bundle;
|
|
2565
3513
|
const existingContent = await readSwarmFileAsync(directory, relativePath);
|
|
2566
3514
|
if (existingContent !== null) {
|
|
2567
3515
|
try {
|
|
2568
3516
|
const parsed = JSON.parse(existingContent);
|
|
2569
3517
|
bundle = EvidenceBundleSchema.parse(parsed);
|
|
2570
|
-
} catch (
|
|
2571
|
-
warn(`Existing evidence bundle invalid for task ${sanitizedTaskId}, creating new: ${
|
|
3518
|
+
} catch (error2) {
|
|
3519
|
+
warn(`Existing evidence bundle invalid for task ${sanitizedTaskId}, creating new: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2572
3520
|
const now = new Date().toISOString();
|
|
2573
3521
|
bundle = {
|
|
2574
3522
|
schema_version: "1.0.0",
|
|
@@ -2602,16 +3550,16 @@ async function saveEvidence(directory, taskId, evidence) {
|
|
|
2602
3550
|
if (bundleJson.length > EVIDENCE_MAX_JSON_BYTES) {
|
|
2603
3551
|
throw new Error(`Evidence bundle size (${bundleJson.length} bytes) exceeds maximum (${EVIDENCE_MAX_JSON_BYTES} bytes)`);
|
|
2604
3552
|
}
|
|
2605
|
-
|
|
2606
|
-
const tempPath =
|
|
3553
|
+
mkdirSync5(evidenceDir, { recursive: true });
|
|
3554
|
+
const tempPath = path7.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
2607
3555
|
try {
|
|
2608
3556
|
await bunWrite(tempPath, bundleJson);
|
|
2609
|
-
await
|
|
2610
|
-
} catch (
|
|
3557
|
+
await fs6.rename(tempPath, evidencePath);
|
|
3558
|
+
} catch (error2) {
|
|
2611
3559
|
try {
|
|
2612
3560
|
rmSync(tempPath, { force: true });
|
|
2613
3561
|
} catch {}
|
|
2614
|
-
throw
|
|
3562
|
+
throw error2;
|
|
2615
3563
|
}
|
|
2616
3564
|
return updatedBundle;
|
|
2617
3565
|
});
|
|
@@ -2647,7 +3595,7 @@ function wrapFlatRetrospective(flatEntry, taskId) {
|
|
|
2647
3595
|
}
|
|
2648
3596
|
async function loadEvidence(directory, taskId) {
|
|
2649
3597
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
2650
|
-
const relativePath =
|
|
3598
|
+
const relativePath = path7.join("evidence", sanitizedTaskId, "evidence.json");
|
|
2651
3599
|
if (relativePath.length > 4096) {
|
|
2652
3600
|
return { status: "not_found" };
|
|
2653
3601
|
}
|
|
@@ -2663,17 +3611,17 @@ async function loadEvidence(directory, taskId) {
|
|
|
2663
3611
|
return { status: "invalid_schema", errors: ["Invalid JSON"] };
|
|
2664
3612
|
}
|
|
2665
3613
|
if (isFlatRetrospective(parsed)) {
|
|
2666
|
-
const wrappedBundle =
|
|
3614
|
+
const wrappedBundle = _internals5.wrapFlatRetrospective(parsed, sanitizedTaskId);
|
|
2667
3615
|
try {
|
|
2668
3616
|
const validated = EvidenceBundleSchema.parse(wrappedBundle);
|
|
2669
3617
|
try {
|
|
2670
3618
|
await withEvidenceLock(directory, relativePath, "evidence-loader", sanitizedTaskId, async () => {
|
|
2671
|
-
const evidenceDir =
|
|
3619
|
+
const evidenceDir = path7.dirname(evidencePath);
|
|
2672
3620
|
const bundleJson = JSON.stringify(validated);
|
|
2673
|
-
const tempPath =
|
|
3621
|
+
const tempPath = path7.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
2674
3622
|
try {
|
|
2675
3623
|
await bunWrite(tempPath, bundleJson);
|
|
2676
|
-
await
|
|
3624
|
+
await fs6.rename(tempPath, evidencePath);
|
|
2677
3625
|
} catch (writeError) {
|
|
2678
3626
|
try {
|
|
2679
3627
|
rmSync(tempPath, { force: true });
|
|
@@ -2685,18 +3633,18 @@ async function loadEvidence(directory, taskId) {
|
|
|
2685
3633
|
warn(`Evidence lock failed during flat-retrospective write-back for task ${sanitizedTaskId}: ${lockErr instanceof Error ? lockErr.message : String(lockErr)}`);
|
|
2686
3634
|
}
|
|
2687
3635
|
return { status: "found", bundle: validated };
|
|
2688
|
-
} catch (
|
|
2689
|
-
warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${
|
|
2690
|
-
const errors =
|
|
3636
|
+
} catch (error2) {
|
|
3637
|
+
warn(`Wrapped flat retrospective failed validation for task ${sanitizedTaskId}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3638
|
+
const errors = error2 instanceof ZodError ? error2.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [error2 instanceof Error ? error2.message : String(error2)];
|
|
2691
3639
|
return { status: "invalid_schema", errors };
|
|
2692
3640
|
}
|
|
2693
3641
|
}
|
|
2694
3642
|
try {
|
|
2695
3643
|
const validated = EvidenceBundleSchema.parse(parsed);
|
|
2696
3644
|
return { status: "found", bundle: validated };
|
|
2697
|
-
} catch (
|
|
2698
|
-
warn(`Evidence bundle validation failed for task ${sanitizedTaskId}: ${
|
|
2699
|
-
const errors =
|
|
3645
|
+
} catch (error2) {
|
|
3646
|
+
warn(`Evidence bundle validation failed for task ${sanitizedTaskId}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3647
|
+
const errors = error2 instanceof ZodError ? error2.issues.map((e) => `${e.path.join(".")}: ${e.message}`) : [error2 instanceof Error ? error2.message : String(error2)];
|
|
2700
3648
|
return { status: "invalid_schema", errors };
|
|
2701
3649
|
}
|
|
2702
3650
|
}
|
|
@@ -2715,7 +3663,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
2715
3663
|
}
|
|
2716
3664
|
const taskIds = [];
|
|
2717
3665
|
for (const entry of entries) {
|
|
2718
|
-
const entryPath =
|
|
3666
|
+
const entryPath = path7.join(evidenceBasePath, entry);
|
|
2719
3667
|
try {
|
|
2720
3668
|
const stats = statSync(entryPath);
|
|
2721
3669
|
if (!stats.isDirectory()) {
|
|
@@ -2723,9 +3671,9 @@ async function listEvidenceTaskIds(directory) {
|
|
|
2723
3671
|
}
|
|
2724
3672
|
sanitizeTaskId2(entry);
|
|
2725
3673
|
taskIds.push(entry);
|
|
2726
|
-
} catch (
|
|
2727
|
-
if (
|
|
2728
|
-
warn(`Error reading evidence entry '${entry}': ${
|
|
3674
|
+
} catch (error2) {
|
|
3675
|
+
if (error2 instanceof Error && !error2.message.startsWith("Invalid task ID")) {
|
|
3676
|
+
warn(`Error reading evidence entry '${entry}': ${error2.message}`);
|
|
2729
3677
|
}
|
|
2730
3678
|
}
|
|
2731
3679
|
}
|
|
@@ -2733,7 +3681,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
2733
3681
|
}
|
|
2734
3682
|
async function deleteEvidence(directory, taskId) {
|
|
2735
3683
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
2736
|
-
const relativePath =
|
|
3684
|
+
const relativePath = path7.join("evidence", sanitizedTaskId);
|
|
2737
3685
|
const evidenceDir = validateSwarmPath(directory, relativePath);
|
|
2738
3686
|
try {
|
|
2739
3687
|
statSync(evidenceDir);
|
|
@@ -2743,30 +3691,30 @@ async function deleteEvidence(directory, taskId) {
|
|
|
2743
3691
|
try {
|
|
2744
3692
|
rmSync(evidenceDir, { recursive: true, force: true });
|
|
2745
3693
|
return true;
|
|
2746
|
-
} catch (
|
|
2747
|
-
warn(`Failed to delete evidence for task ${sanitizedTaskId}: ${
|
|
3694
|
+
} catch (error2) {
|
|
3695
|
+
warn(`Failed to delete evidence for task ${sanitizedTaskId}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2748
3696
|
return false;
|
|
2749
3697
|
}
|
|
2750
3698
|
}
|
|
2751
3699
|
async function checkRequirementCoverage(phase, directory) {
|
|
2752
|
-
const relativePath =
|
|
2753
|
-
const absolutePath =
|
|
3700
|
+
const relativePath = path7.join("evidence", `req-coverage-phase-${phase}.json`);
|
|
3701
|
+
const absolutePath = path7.resolve(directory, ".swarm", relativePath);
|
|
2754
3702
|
try {
|
|
2755
|
-
await
|
|
3703
|
+
await fs6.access(absolutePath);
|
|
2756
3704
|
return { exists: true, path: absolutePath };
|
|
2757
3705
|
} catch {
|
|
2758
3706
|
return { exists: false, path: absolutePath };
|
|
2759
3707
|
}
|
|
2760
3708
|
}
|
|
2761
3709
|
async function archiveEvidence(directory, maxAgeDays, maxBundles) {
|
|
2762
|
-
const taskIds = await
|
|
3710
|
+
const taskIds = await _internals5.listEvidenceTaskIds(directory);
|
|
2763
3711
|
const cutoffDate = new Date;
|
|
2764
3712
|
cutoffDate.setDate(cutoffDate.getDate() - maxAgeDays);
|
|
2765
3713
|
const cutoffIso = cutoffDate.toISOString();
|
|
2766
3714
|
const archived = [];
|
|
2767
3715
|
const remainingBundles = [];
|
|
2768
3716
|
for (const taskId of taskIds) {
|
|
2769
|
-
const result = await
|
|
3717
|
+
const result = await _internals5.loadEvidence(directory, taskId);
|
|
2770
3718
|
if (result.status !== "found") {
|
|
2771
3719
|
continue;
|
|
2772
3720
|
}
|
|
@@ -2794,7 +3742,7 @@ async function archiveEvidence(directory, maxAgeDays, maxBundles) {
|
|
|
2794
3742
|
}
|
|
2795
3743
|
return archived;
|
|
2796
3744
|
}
|
|
2797
|
-
var
|
|
3745
|
+
var _internals5 = {
|
|
2798
3746
|
wrapFlatRetrospective,
|
|
2799
3747
|
loadEvidence,
|
|
2800
3748
|
listEvidenceTaskIds,
|
|
@@ -2911,4 +3859,4 @@ function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
|
|
|
2911
3859
|
return merged;
|
|
2912
3860
|
}
|
|
2913
3861
|
|
|
2914
|
-
export { PlanSchema, loadSddStatusSync, buildOpenSpecProjectionSync, readEffectiveSpecSync, writeProjectedSpecSync, computeSpecHash, derivePlanId, computePlanHash, initLedger, appendLedgerEvent, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, RetrospectiveEvidenceSchema, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|
|
3862
|
+
export { PlanSchema, getGitRepositoryStatus, isGitRepo, resetToRemoteBranch, resetToMainAfterMerge, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, loadSddStatusSync, buildOpenSpecProjectionSync, readEffectiveSpecSync, writeProjectedSpecSync, computeSpecHash, derivePlanId, computePlanHash, initLedger, appendLedgerEvent, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, RetrospectiveEvidenceSchema, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|