opencode-swarm 7.122.0 → 7.122.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{config-doctor-404c8mf8.js → config-doctor-3ksh1534.js} +2 -2
- package/dist/cli/{core-xnwasfpc.js → core-cwgb7ev8.js} +1 -1
- package/dist/cli/{curation-policy-cj1tpzr5.js → curation-policy-4j4qce5a.js} +5 -5
- package/dist/cli/{curator-llm-factory-8rg0f77t.js → curator-llm-factory-x769hn66.js} +22 -20
- package/dist/cli/{curator-3rze3pd4.js → curator-qfh07497.js} +22 -20
- package/dist/cli/{evidence-summary-service-k14ykm03.js → evidence-summary-service-96ndczbk.js} +7 -5
- package/dist/cli/{gate-evidence-tbpezk28.js → gate-evidence-xqks3kk2.js} +3 -3
- package/dist/cli/{guardrail-explain-hdsxa2c5.js → guardrail-explain-kj0e9r25.js} +23 -21
- package/dist/cli/{guardrail-log-dhpp0kmn.js → guardrail-log-z291m4gw.js} +3 -3
- package/dist/cli/{hive-promoter-dh38qd9m.js → hive-promoter-00mktxkk.js} +22 -20
- package/dist/cli/{index-zvae4m8g.js → index-17eybc3z.js} +1 -1
- package/dist/cli/{index-kyvg2cmp.js → index-19zn67q6.js} +1 -1
- package/dist/cli/{index-rbfq77sz.js → index-2j25br0p.js} +12 -10
- package/dist/cli/{index-9xpgytj9.js → index-3rr0ese8.js} +1 -1
- package/dist/cli/{index-gr7ssnj9.js → index-5he0kwnx.js} +3 -3
- package/dist/cli/{index-68aggz0v.js → index-6srk9a5q.js} +2 -2
- package/dist/cli/{index-qg11yf8q.js → index-6xnxz6z0.js} +1 -1
- package/dist/cli/{index-emkb0bbe.js → index-7cspygmb.js} +277 -673
- package/dist/cli/{index-3r1w7xky.js → index-cqsak7bf.js} +2 -2
- package/dist/cli/index-ctpgsj84.js +547 -0
- package/dist/cli/index-d6h5hq91.js +564 -0
- package/dist/cli/{index-vqcgmy4y.js → index-det5jwfk.js} +1 -1
- package/dist/cli/{index-3sx27bxt.js → index-dywekeen.js} +3 -3
- package/dist/cli/{index-wtn8wf7h.js → index-gw3m5fw0.js} +1 -1
- package/dist/cli/{index-rkd6qwjh.js → index-hns7xjm7.js} +2 -2
- package/dist/cli/{index-xdsj4qtd.js → index-j9kxwshw.js} +5 -5
- package/dist/cli/{index-jzpv74mq.js → index-jm81nhks.js} +24 -22
- package/dist/cli/{index-ahxrne50.js → index-kanrv3sv.js} +160 -58
- package/dist/cli/{index-j33zb3cx.js → index-m0bmh3w0.js} +4 -4
- package/dist/cli/index-m1abzg93.js +68 -0
- package/dist/cli/{index-ny13nr0x.js → index-n2712byd.js} +6 -6
- package/dist/cli/{index-3jvqw5hy.js → index-q27bajqb.js} +1 -1
- package/dist/cli/{index-qt2yaghg.js → index-vcgfqd8f.js} +1 -63
- package/dist/cli/{index-tftv9xh9.js → index-wr8qgpz5.js} +1 -1
- package/dist/cli/index.js +22 -20
- package/dist/cli/{knowledge-escalator-sya06p12.js → knowledge-escalator-dyxsm78k.js} +6 -6
- package/dist/cli/{knowledge-events-xzps61a9.js → knowledge-events-36q6nvae.js} +4 -4
- package/dist/cli/{knowledge-link-sy85fw3w.js → knowledge-link-4yd2z8ev.js} +3 -3
- package/dist/cli/{knowledge-store-qg66a93n.js → knowledge-store-e07b4s54.js} +4 -4
- package/dist/cli/{knowledge-validator-gar6wv4f.js → knowledge-validator-sfyf7wn2.js} +7 -7
- package/dist/cli/pr-subscriptions-x653151h.js +1 -1
- package/dist/cli/{scan-cursor-nx5y5k7r.js → scan-cursor-hm0aqqae.js} +5 -5
- package/dist/cli/{schema-vwxpsk6j.js → schema-13bnng2n.js} +1 -1
- package/dist/cli/scope-persistence-d9erwnvb.js +36 -0
- package/dist/cli/{skill-generator-nt5qxafq.js → skill-generator-g6r52x94.js} +8 -8
- package/dist/config/constants.d.ts +3 -2
- package/dist/hooks/delegation-gate.d.ts +19 -1
- package/dist/hooks/guardrails/nontransient-circuit.d.ts +47 -0
- package/dist/hooks/scope-guard.d.ts +9 -2
- package/dist/hooks/write-target-resolver.d.ts +30 -0
- package/dist/index.js +405 -369
- package/dist/sandbox/win32/restricted-environment-executor.d.ts +3 -2
- package/dist/scope/scope-binding.d.ts +89 -0
- package/dist/scope/scope-persistence.d.ts +53 -12
- package/dist/state.d.ts +25 -1
- package/dist/tools/declare-scope.d.ts +5 -1
- package/dist/tools/file-extractor-planner.d.ts +31 -0
- package/dist/tools/file-extractor.d.ts +2 -7
- package/package.json +1 -1
- package/dist/cli/index-51gg9947.js +0 -254
- package/dist/cli/scope-persistence-mfr36yc9.js +0 -20
- package/dist/cli/{index-h07wn84h.js → index-7mkpsw6g.js} +3 -3
- package/dist/cli/{pending-delegations-0z3znsec.js → pending-delegations-68cbaqv2.js} +1 -1
|
@@ -5,11 +5,8 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
EVIDENCE_MAX_JSON_BYTES,
|
|
7
7
|
EvidenceBundleSchema,
|
|
8
|
-
EvidenceSchema
|
|
9
|
-
|
|
10
|
-
PlanSchema,
|
|
11
|
-
TaskStatusSchema
|
|
12
|
-
} from "./index-qt2yaghg.js";
|
|
8
|
+
EvidenceSchema
|
|
9
|
+
} from "./index-vcgfqd8f.js";
|
|
13
10
|
import {
|
|
14
11
|
_internals,
|
|
15
12
|
buildDefaultPolicy,
|
|
@@ -17,6 +14,25 @@ import {
|
|
|
17
14
|
init_runner_client,
|
|
18
15
|
probe
|
|
19
16
|
} from "./index-q1exe2b3.js";
|
|
17
|
+
import {
|
|
18
|
+
LedgerStaleWriterError,
|
|
19
|
+
appendLedgerEvent,
|
|
20
|
+
computeCurrentPlanHash,
|
|
21
|
+
computePlanHash,
|
|
22
|
+
derivePlanId,
|
|
23
|
+
getLatestLedgerSeq,
|
|
24
|
+
initLedger,
|
|
25
|
+
ledgerExists,
|
|
26
|
+
loadLastApprovedPlan,
|
|
27
|
+
readLedgerEvents,
|
|
28
|
+
replayFromLedger,
|
|
29
|
+
replayFromLedgerWithStatus,
|
|
30
|
+
takeSnapshotEvent,
|
|
31
|
+
takeSnapshotWithRetry
|
|
32
|
+
} from "./index-ctpgsj84.js";
|
|
33
|
+
import {
|
|
34
|
+
PlanSchema
|
|
35
|
+
} from "./index-m1abzg93.js";
|
|
20
36
|
import {
|
|
21
37
|
readSwarmFileAsync,
|
|
22
38
|
validateSwarmPath
|
|
@@ -25,7 +41,6 @@ import {
|
|
|
25
41
|
criticalWarn,
|
|
26
42
|
error,
|
|
27
43
|
init_logger,
|
|
28
|
-
log,
|
|
29
44
|
warn
|
|
30
45
|
} from "./index-zgwm4ryv.js";
|
|
31
46
|
import {
|
|
@@ -33,16 +48,16 @@ import {
|
|
|
33
48
|
readTaskEvidence,
|
|
34
49
|
readTaskEvidenceRaw,
|
|
35
50
|
sanitizeTaskId
|
|
36
|
-
} from "./index-
|
|
51
|
+
} from "./index-6xnxz6z0.js";
|
|
37
52
|
import {
|
|
38
53
|
atomicWriteFile
|
|
39
|
-
} from "./index-
|
|
40
|
-
import {
|
|
41
|
-
withEvidenceLock
|
|
42
|
-
} from "./index-76kvm9z5.js";
|
|
54
|
+
} from "./index-7mkpsw6g.js";
|
|
43
55
|
import {
|
|
44
56
|
readCachedParsedFile
|
|
45
57
|
} from "./index-fsrp8wp3.js";
|
|
58
|
+
import {
|
|
59
|
+
withEvidenceLock
|
|
60
|
+
} from "./index-76kvm9z5.js";
|
|
46
61
|
import {
|
|
47
62
|
emit
|
|
48
63
|
} from "./index-q2rrv0cy.js";
|
|
@@ -442,12 +457,33 @@ function detectPowerShellEscape(command) {
|
|
|
442
457
|
var init_edge_cases = () => {};
|
|
443
458
|
|
|
444
459
|
// src/sandbox/win32/restricted-environment-executor.ts
|
|
460
|
+
import { Buffer } from "buffer";
|
|
445
461
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
446
462
|
import * as os2 from "os";
|
|
447
463
|
import * as path2 from "path";
|
|
464
|
+
function containsUnsafePathSyntax(value) {
|
|
465
|
+
return [...value].some((character) => character.charCodeAt(0) < 32 || UNSAFE_PATH_CHARACTERS.has(character));
|
|
466
|
+
}
|
|
467
|
+
function getSafeSystemRoot() {
|
|
468
|
+
const raw = process.env.SystemRoot;
|
|
469
|
+
if (typeof raw !== "string" || !/^[A-Za-z]:[\\/]/.test(raw) || containsUnsafePathSyntax(raw.slice(2)) || raw.slice(3).split(/[\\/]+/).some((segment) => segment === "." || segment === "..")) {
|
|
470
|
+
return DEFAULT_SYSTEM_ROOT;
|
|
471
|
+
}
|
|
472
|
+
const normalized = path2.win32.normalize(raw);
|
|
473
|
+
return normalized.length > 3 ? normalized.replace(/\\+$/, "") : normalized;
|
|
474
|
+
}
|
|
475
|
+
function getWindowsExecutablePaths() {
|
|
476
|
+
const systemRoot = getSafeSystemRoot();
|
|
477
|
+
return {
|
|
478
|
+
systemRoot,
|
|
479
|
+
cmdExe: path2.win32.join(systemRoot, "System32", "cmd.exe"),
|
|
480
|
+
powerShellExe: path2.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
|
|
481
|
+
};
|
|
482
|
+
}
|
|
448
483
|
function probeWindowsSandbox() {
|
|
449
484
|
try {
|
|
450
|
-
const result = spawnSync3("
|
|
485
|
+
const result = spawnSync3(getWindowsExecutablePaths().cmdExe, ["/d", "/s", "/c", "echo ok"], {
|
|
486
|
+
cwd: os2.tmpdir(),
|
|
451
487
|
windowsHide: true,
|
|
452
488
|
encoding: "utf-8",
|
|
453
489
|
timeout: 5000,
|
|
@@ -469,14 +505,37 @@ function probeWindowsSandbox() {
|
|
|
469
505
|
return false;
|
|
470
506
|
}
|
|
471
507
|
}
|
|
472
|
-
function
|
|
473
|
-
return s.replace(/
|
|
508
|
+
function psSingleQuoteEscape(s) {
|
|
509
|
+
return s.replace(/'/g, "''");
|
|
510
|
+
}
|
|
511
|
+
function normalizeInheritedPathEntry(rawEntry) {
|
|
512
|
+
const entry = rawEntry.trim();
|
|
513
|
+
if (!/^[A-Za-z]:[\\/]/.test(entry) || containsUnsafePathSyntax(entry.slice(2)) || entry.slice(3).split(/[\\/]+/).some((segment) => segment === "." || segment === "..")) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const normalized = path2.win32.normalize(entry);
|
|
517
|
+
return normalized.length > 3 ? normalized.replace(/\\+$/, "") : normalized;
|
|
474
518
|
}
|
|
475
519
|
function getSafeWindowsPath() {
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
520
|
+
const { systemRoot } = getWindowsExecutablePaths();
|
|
521
|
+
const candidates = [
|
|
522
|
+
path2.win32.join(systemRoot, "System32"),
|
|
523
|
+
systemRoot,
|
|
524
|
+
...(process.env.PATH ?? "").split(";")
|
|
525
|
+
];
|
|
526
|
+
const seen = new Set;
|
|
527
|
+
const safeEntries = [];
|
|
528
|
+
for (const candidate of candidates) {
|
|
529
|
+
const normalized = normalizeInheritedPathEntry(candidate);
|
|
530
|
+
if (!normalized)
|
|
531
|
+
continue;
|
|
532
|
+
const identity = normalized.toLowerCase();
|
|
533
|
+
if (seen.has(identity))
|
|
534
|
+
continue;
|
|
535
|
+
seen.add(identity);
|
|
536
|
+
safeEntries.push(normalized);
|
|
537
|
+
}
|
|
538
|
+
return safeEntries.join(";");
|
|
480
539
|
}
|
|
481
540
|
function isPowerShellNativeCommand(command) {
|
|
482
541
|
return /^(?:Remove-Item|rm|del|erase|Copy-Item|cp|copy|Move-Item|mv|move|Rename-Item|ren|New-Item|ni|Get-Item|gi|Get-ChildItem|ls|dir|gci|Get-Content|cat|type|gc|Set-Content|sc|Add-Content|ac|Clear-Content|clc|Test-Path|Resolve-Path|Split-Path|Join-Path|Out-File|Get-Date)\b/i.test(command.trimStart());
|
|
@@ -557,8 +616,11 @@ class WindowsSandboxExecutor {
|
|
|
557
616
|
throw new SandboxError("PowerShell-native command body contains unsafe characters", "UNSAFE_PS_COMMAND");
|
|
558
617
|
}
|
|
559
618
|
const safePath = getSafeWindowsPath();
|
|
560
|
-
const
|
|
561
|
-
const
|
|
619
|
+
const { cmdExe, powerShellExe } = getWindowsExecutablePaths();
|
|
620
|
+
const commandBase64 = Buffer.from(command, "utf8").toString("base64");
|
|
621
|
+
const escapedTemp = psSingleQuoteEscape(temp);
|
|
622
|
+
const escapedPath = psSingleQuoteEscape(safePath);
|
|
623
|
+
const escapedCmdExe = psSingleQuoteEscape(cmdExe);
|
|
562
624
|
const envOverrideLines = [];
|
|
563
625
|
if (envOverrides) {
|
|
564
626
|
for (const [key, value] of Object.entries(envOverrides)) {
|
|
@@ -568,27 +630,50 @@ class WindowsSandboxExecutor {
|
|
|
568
630
|
if (value === null) {
|
|
569
631
|
envOverrideLines.push(`Remove-Item Env:${key} -Force -ErrorAction SilentlyContinue;`);
|
|
570
632
|
} else {
|
|
571
|
-
const psEscaped = value
|
|
633
|
+
const psEscaped = psSingleQuoteEscape(value);
|
|
572
634
|
envOverrideLines.push(`$env:${key} = '${psEscaped}';`);
|
|
573
635
|
}
|
|
574
636
|
}
|
|
575
637
|
}
|
|
576
|
-
const commandExec = isPowerShellNativeCommand(command) ? `Invoke-Expression
|
|
638
|
+
const commandExec = isPowerShellNativeCommand(command) ? `Invoke-Expression $command;
|
|
639
|
+
exit 0;` : `$batchPath = $null;
|
|
640
|
+
$batchLocationPushed = $false;
|
|
641
|
+
try {
|
|
642
|
+
if ($command.Contains([char]13) -or $command.Contains([char]10)) {
|
|
643
|
+
$batchName = 'opencode-swarm-' + [Guid]::NewGuid().ToString('N') + '.cmd';
|
|
644
|
+
$batchPath = Join-Path $env:TEMP $batchName;
|
|
645
|
+
[IO.File]::WriteAllText($batchPath, $command, [Text.UTF8Encoding]::new($false));
|
|
646
|
+
Push-Location -LiteralPath $env:TEMP;
|
|
647
|
+
$batchLocationPushed = $true;
|
|
648
|
+
& '${escapedCmdExe}' /d /v:off /s /c ('call "' + $batchName + '"');
|
|
649
|
+
} else {
|
|
650
|
+
& '${escapedCmdExe}' /d /v:off /s /c $command;
|
|
651
|
+
}
|
|
652
|
+
$childExitCode = $LASTEXITCODE;
|
|
653
|
+
} finally {
|
|
654
|
+
if ($batchLocationPushed) {
|
|
655
|
+
Pop-Location;
|
|
656
|
+
}
|
|
657
|
+
if ($null -ne $batchPath) {
|
|
658
|
+
Remove-Item -LiteralPath $batchPath -Force -ErrorAction SilentlyContinue;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if ($null -eq $childExitCode) {
|
|
662
|
+
throw 'cmd.exe did not report an exit code';
|
|
663
|
+
}
|
|
664
|
+
exit [int]$childExitCode;`;
|
|
577
665
|
const envOverrideBlock = envOverrideLines.length > 0 ? `
|
|
578
666
|
${envOverrideLines.join(`
|
|
579
667
|
`)}` : "";
|
|
580
668
|
const psScript = `
|
|
581
669
|
$ErrorActionPreference = 'Stop';
|
|
670
|
+
$ProgressPreference = 'SilentlyContinue';
|
|
582
671
|
try {
|
|
583
|
-
# Set scoped temp directory
|
|
584
672
|
$env:TEMP = '${escapedTemp}';
|
|
585
673
|
$env:TMP = '${escapedTemp}';
|
|
586
674
|
|
|
587
|
-
|
|
588
|
-
$env:PATH = '${safePath}';
|
|
675
|
+
$env:PATH = '${escapedPath}';
|
|
589
676
|
|
|
590
|
-
# Remove dangerous environment variables that could be used to bypass restrictions
|
|
591
|
-
# Note: LD_PRELOAD, DYLD_* don't apply on Windows but we clear them for completeness
|
|
592
677
|
$dangerousVars = @(
|
|
593
678
|
'LD_PRELOAD',
|
|
594
679
|
'DYLD_INSERT_LIBRARIES',
|
|
@@ -605,12 +690,16 @@ try {
|
|
|
605
690
|
${envOverrideBlock}
|
|
606
691
|
|
|
607
692
|
# Execute the command \u2014 PS-native cmdlets via Invoke-Expression, others via the standard command interpreter
|
|
693
|
+
$commandBytes = [Convert]::FromBase64String('${commandBase64}');
|
|
694
|
+
$command = [Text.Encoding]::UTF8.GetString($commandBytes);
|
|
608
695
|
${commandExec};
|
|
609
696
|
} catch {
|
|
610
|
-
|
|
697
|
+
[Console]::Error.WriteLine($_.Exception.Message);
|
|
611
698
|
exit 1;
|
|
612
699
|
}`;
|
|
613
|
-
|
|
700
|
+
const encodedScript = Buffer.from(psScript, "utf16le").toString("base64");
|
|
701
|
+
const escapedPowerShellExe = psSingleQuoteEscape(powerShellExe);
|
|
702
|
+
return `& '${escapedPowerShellExe}' -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand ${encodedScript}; exit $LASTEXITCODE`;
|
|
614
703
|
}
|
|
615
704
|
getEnvOverrides() {
|
|
616
705
|
return {
|
|
@@ -626,7 +715,7 @@ try {
|
|
|
626
715
|
};
|
|
627
716
|
}
|
|
628
717
|
}
|
|
629
|
-
var SANDBOX_UNAVAILABLE_CODES2, _internals4;
|
|
718
|
+
var SANDBOX_UNAVAILABLE_CODES2, DEFAULT_SYSTEM_ROOT = "C:\\Windows", UNSAFE_PATH_CHARACTERS, _internals4;
|
|
630
719
|
var init_restricted_environment_executor = __esm(() => {
|
|
631
720
|
init_logger();
|
|
632
721
|
init_executor();
|
|
@@ -637,6 +726,23 @@ var init_restricted_environment_executor = __esm(() => {
|
|
|
637
726
|
"EPERM",
|
|
638
727
|
"ENOSPC"
|
|
639
728
|
]);
|
|
729
|
+
UNSAFE_PATH_CHARACTERS = new Set([
|
|
730
|
+
":",
|
|
731
|
+
'"',
|
|
732
|
+
"'",
|
|
733
|
+
";",
|
|
734
|
+
"&",
|
|
735
|
+
"|",
|
|
736
|
+
"<",
|
|
737
|
+
">",
|
|
738
|
+
"^",
|
|
739
|
+
"%",
|
|
740
|
+
"!",
|
|
741
|
+
"?",
|
|
742
|
+
"*",
|
|
743
|
+
"`",
|
|
744
|
+
"$"
|
|
745
|
+
]);
|
|
640
746
|
_internals4 = {
|
|
641
747
|
probeWindowsSandbox
|
|
642
748
|
};
|
|
@@ -857,19 +963,19 @@ var init_executor = __esm(() => {
|
|
|
857
963
|
|
|
858
964
|
// src/plan/manager.ts
|
|
859
965
|
import {
|
|
860
|
-
closeSync
|
|
966
|
+
closeSync,
|
|
861
967
|
copyFileSync,
|
|
862
|
-
existsSync as
|
|
863
|
-
fsyncSync
|
|
864
|
-
mkdirSync as
|
|
865
|
-
openSync
|
|
866
|
-
readdirSync
|
|
867
|
-
renameSync as
|
|
968
|
+
existsSync as existsSync4,
|
|
969
|
+
fsyncSync,
|
|
970
|
+
mkdirSync as mkdirSync4,
|
|
971
|
+
openSync,
|
|
972
|
+
readdirSync,
|
|
973
|
+
renameSync as renameSync3,
|
|
868
974
|
unlinkSync as unlinkSync3,
|
|
869
|
-
writeFileSync as
|
|
975
|
+
writeFileSync as writeFileSync5
|
|
870
976
|
} from "fs";
|
|
871
977
|
import * as fsPromises from "fs/promises";
|
|
872
|
-
import * as
|
|
978
|
+
import * as path8 from "path";
|
|
873
979
|
|
|
874
980
|
// src/git/branch.ts
|
|
875
981
|
init_executor();
|
|
@@ -2072,508 +2178,6 @@ function computeLcsDiff(oldLines, newLines) {
|
|
|
2072
2178
|
});
|
|
2073
2179
|
}
|
|
2074
2180
|
|
|
2075
|
-
// src/plan/ledger.ts
|
|
2076
|
-
import * as crypto2 from "crypto";
|
|
2077
|
-
import * as fs5 from "fs";
|
|
2078
|
-
import * as path8 from "path";
|
|
2079
|
-
init_logger();
|
|
2080
|
-
|
|
2081
|
-
// src/plan/utils.ts
|
|
2082
|
-
function derivePlanId(plan) {
|
|
2083
|
-
return `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
// src/plan/ledger.ts
|
|
2087
|
-
var LEDGER_SCHEMA_VERSION = "1.1.0";
|
|
2088
|
-
var LEDGER_FILENAME = "plan-ledger.jsonl";
|
|
2089
|
-
var LEDGER_LOCK_PATH = path8.join(".swarm", LEDGER_FILENAME);
|
|
2090
|
-
var PLAN_JSON_FILENAME = "plan.json";
|
|
2091
|
-
|
|
2092
|
-
class LedgerStaleWriterError extends Error {
|
|
2093
|
-
constructor(message) {
|
|
2094
|
-
super(message);
|
|
2095
|
-
this.name = "LedgerStaleWriterError";
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
function getLedgerPath(directory) {
|
|
2099
|
-
return path8.join(directory, ".swarm", LEDGER_FILENAME);
|
|
2100
|
-
}
|
|
2101
|
-
function getPlanJsonPath(directory) {
|
|
2102
|
-
return path8.join(directory, ".swarm", PLAN_JSON_FILENAME);
|
|
2103
|
-
}
|
|
2104
|
-
function writeFileFsyncedThenRename(tempPath, targetPath, data) {
|
|
2105
|
-
const fd = fs5.openSync(tempPath, "w");
|
|
2106
|
-
try {
|
|
2107
|
-
fs5.writeFileSync(fd, data, "utf8");
|
|
2108
|
-
fs5.fsyncSync(fd);
|
|
2109
|
-
} finally {
|
|
2110
|
-
fs5.closeSync(fd);
|
|
2111
|
-
}
|
|
2112
|
-
fs5.renameSync(tempPath, targetPath);
|
|
2113
|
-
}
|
|
2114
|
-
function computePlanHash(plan) {
|
|
2115
|
-
const normalized = {
|
|
2116
|
-
schema_version: plan.schema_version,
|
|
2117
|
-
title: plan.title,
|
|
2118
|
-
swarm: plan.swarm,
|
|
2119
|
-
current_phase: plan.current_phase,
|
|
2120
|
-
migration_status: plan.migration_status,
|
|
2121
|
-
execution_profile: plan.execution_profile,
|
|
2122
|
-
phases: plan.phases.map((phase) => ({
|
|
2123
|
-
id: phase.id,
|
|
2124
|
-
name: phase.name,
|
|
2125
|
-
status: phase.status,
|
|
2126
|
-
required_agents: phase.required_agents ? [...phase.required_agents].sort() : undefined,
|
|
2127
|
-
tasks: phase.tasks.map((task) => ({
|
|
2128
|
-
id: task.id,
|
|
2129
|
-
phase: task.phase,
|
|
2130
|
-
status: task.status,
|
|
2131
|
-
size: task.size,
|
|
2132
|
-
description: task.description,
|
|
2133
|
-
depends: [...task.depends].sort(),
|
|
2134
|
-
acceptance: task.acceptance,
|
|
2135
|
-
files_touched: [...task.files_touched].sort(),
|
|
2136
|
-
evidence_path: task.evidence_path,
|
|
2137
|
-
blocked_reason: task.blocked_reason
|
|
2138
|
-
}))
|
|
2139
|
-
}))
|
|
2140
|
-
};
|
|
2141
|
-
const jsonString = JSON.stringify(normalized);
|
|
2142
|
-
return crypto2.createHash("sha256").update(jsonString, "utf8").digest("hex");
|
|
2143
|
-
}
|
|
2144
|
-
function computeCurrentPlanHash(directory) {
|
|
2145
|
-
const planPath = getPlanJsonPath(directory);
|
|
2146
|
-
try {
|
|
2147
|
-
const content = fs5.readFileSync(planPath, "utf8");
|
|
2148
|
-
const plan = JSON.parse(content);
|
|
2149
|
-
return computePlanHash(plan);
|
|
2150
|
-
} catch {
|
|
2151
|
-
return "";
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
async function ledgerExists(directory) {
|
|
2155
|
-
const ledgerPath = getLedgerPath(directory);
|
|
2156
|
-
return fs5.existsSync(ledgerPath);
|
|
2157
|
-
}
|
|
2158
|
-
async function getLatestLedgerSeq(directory) {
|
|
2159
|
-
const ledgerPath = getLedgerPath(directory);
|
|
2160
|
-
if (!fs5.existsSync(ledgerPath)) {
|
|
2161
|
-
return 0;
|
|
2162
|
-
}
|
|
2163
|
-
try {
|
|
2164
|
-
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
2165
|
-
const lines = content.trim().split(`
|
|
2166
|
-
`).filter((line) => line.trim() !== "");
|
|
2167
|
-
if (lines.length === 0) {
|
|
2168
|
-
return 0;
|
|
2169
|
-
}
|
|
2170
|
-
let maxSeq = 0;
|
|
2171
|
-
for (const line of lines) {
|
|
2172
|
-
try {
|
|
2173
|
-
const event = JSON.parse(line);
|
|
2174
|
-
if (event.seq > maxSeq) {
|
|
2175
|
-
maxSeq = event.seq;
|
|
2176
|
-
}
|
|
2177
|
-
} catch {}
|
|
2178
|
-
}
|
|
2179
|
-
return maxSeq;
|
|
2180
|
-
} catch {
|
|
2181
|
-
return 0;
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
async function readLedgerEvents(directory) {
|
|
2185
|
-
const ledgerPath = getLedgerPath(directory);
|
|
2186
|
-
if (!fs5.existsSync(ledgerPath)) {
|
|
2187
|
-
return [];
|
|
2188
|
-
}
|
|
2189
|
-
try {
|
|
2190
|
-
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
2191
|
-
const lines = content.trim().split(`
|
|
2192
|
-
`).filter((line) => line.trim() !== "");
|
|
2193
|
-
const events = [];
|
|
2194
|
-
let skippedCount = 0;
|
|
2195
|
-
for (const line of lines) {
|
|
2196
|
-
try {
|
|
2197
|
-
const event = JSON.parse(line);
|
|
2198
|
-
events.push(event);
|
|
2199
|
-
} catch {
|
|
2200
|
-
skippedCount++;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
if (skippedCount > 0) {
|
|
2204
|
-
log(`[ledger] Skipped ${skippedCount} malformed line(s) in plan-ledger.jsonl`);
|
|
2205
|
-
}
|
|
2206
|
-
events.sort((a, b) => a.seq - b.seq);
|
|
2207
|
-
return events;
|
|
2208
|
-
} catch {
|
|
2209
|
-
return [];
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
async function initLedger(directory, planId, initialPlanHash, initialPlan) {
|
|
2213
|
-
const ledgerPath = getLedgerPath(directory);
|
|
2214
|
-
const planJsonPath = getPlanJsonPath(directory);
|
|
2215
|
-
if (fs5.existsSync(ledgerPath)) {
|
|
2216
|
-
throw new Error("Ledger already initialized. Use appendLedgerEvent to add events.");
|
|
2217
|
-
}
|
|
2218
|
-
let planHashAfter = initialPlanHash ?? "";
|
|
2219
|
-
let embeddedPlan = initialPlan;
|
|
2220
|
-
if (!initialPlanHash) {
|
|
2221
|
-
try {
|
|
2222
|
-
if (fs5.existsSync(planJsonPath)) {
|
|
2223
|
-
const content = fs5.readFileSync(planJsonPath, "utf8");
|
|
2224
|
-
const plan = JSON.parse(content);
|
|
2225
|
-
planHashAfter = computePlanHash(plan);
|
|
2226
|
-
if (!embeddedPlan)
|
|
2227
|
-
embeddedPlan = plan;
|
|
2228
|
-
}
|
|
2229
|
-
} catch {}
|
|
2230
|
-
}
|
|
2231
|
-
const payload = embeddedPlan ? { plan: embeddedPlan, payload_hash: planHashAfter } : undefined;
|
|
2232
|
-
const event = {
|
|
2233
|
-
seq: 1,
|
|
2234
|
-
timestamp: new Date().toISOString(),
|
|
2235
|
-
plan_id: planId,
|
|
2236
|
-
event_type: "plan_created",
|
|
2237
|
-
source: "initLedger",
|
|
2238
|
-
plan_hash_before: "",
|
|
2239
|
-
plan_hash_after: planHashAfter,
|
|
2240
|
-
schema_version: LEDGER_SCHEMA_VERSION,
|
|
2241
|
-
...payload ? { payload } : {}
|
|
2242
|
-
};
|
|
2243
|
-
fs5.mkdirSync(path8.join(directory, ".swarm"), { recursive: true });
|
|
2244
|
-
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
2245
|
-
const line = `${JSON.stringify(event)}
|
|
2246
|
-
`;
|
|
2247
|
-
writeFileFsyncedThenRename(tempPath, ledgerPath, line);
|
|
2248
|
-
}
|
|
2249
|
-
async function appendLedgerEvent(directory, eventInput, options) {
|
|
2250
|
-
return withEvidenceLock(directory, LEDGER_LOCK_PATH, "plan-ledger", "append-ledger-event", async () => {
|
|
2251
|
-
const ledgerPath = getLedgerPath(directory);
|
|
2252
|
-
const latestSeq = await getLatestLedgerSeq(directory);
|
|
2253
|
-
const nextSeq = latestSeq + 1;
|
|
2254
|
-
const planHashBefore = computeCurrentPlanHash(directory);
|
|
2255
|
-
if (options?.expectedSeq !== undefined && options.expectedSeq !== latestSeq) {
|
|
2256
|
-
throw new LedgerStaleWriterError(`Stale writer: expected seq ${options.expectedSeq} but found ${latestSeq}`);
|
|
2257
|
-
}
|
|
2258
|
-
if (options?.expectedHash !== undefined && options.expectedHash !== planHashBefore) {
|
|
2259
|
-
throw new LedgerStaleWriterError(`Stale writer: expected hash ${options.expectedHash} but found ${planHashBefore}`);
|
|
2260
|
-
}
|
|
2261
|
-
const planHashAfter = options?.planHashAfter ?? planHashBefore;
|
|
2262
|
-
const event = {
|
|
2263
|
-
...eventInput,
|
|
2264
|
-
seq: nextSeq,
|
|
2265
|
-
timestamp: new Date().toISOString(),
|
|
2266
|
-
plan_hash_before: planHashBefore,
|
|
2267
|
-
plan_hash_after: planHashAfter,
|
|
2268
|
-
schema_version: LEDGER_SCHEMA_VERSION
|
|
2269
|
-
};
|
|
2270
|
-
fs5.mkdirSync(path8.join(directory, ".swarm"), { recursive: true });
|
|
2271
|
-
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
2272
|
-
const line = `${JSON.stringify(event)}
|
|
2273
|
-
`;
|
|
2274
|
-
if (!fs5.existsSync(ledgerPath)) {
|
|
2275
|
-
throw new Error("Ledger not initialized. Call initLedger() first.");
|
|
2276
|
-
}
|
|
2277
|
-
const existingContent = fs5.readFileSync(ledgerPath, "utf8");
|
|
2278
|
-
writeFileFsyncedThenRename(tempPath, ledgerPath, existingContent + line);
|
|
2279
|
-
return event;
|
|
2280
|
-
});
|
|
2281
|
-
}
|
|
2282
|
-
async function takeSnapshotWithRetry(directory, plan, options) {
|
|
2283
|
-
const MAX_RETRIES = 3;
|
|
2284
|
-
const TOTAL_ATTEMPTS = 1 + MAX_RETRIES;
|
|
2285
|
-
const telemetrySource = options?.source ?? "save_plan_tool";
|
|
2286
|
-
const snapshotOptions = { planHashAfter: options?.planHashAfter };
|
|
2287
|
-
let lastError;
|
|
2288
|
-
for (let attempt = 1;attempt <= TOTAL_ATTEMPTS; attempt++) {
|
|
2289
|
-
try {
|
|
2290
|
-
await takeSnapshotEvent(directory, plan, snapshotOptions);
|
|
2291
|
-
return;
|
|
2292
|
-
} catch (err) {
|
|
2293
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2294
|
-
if (attempt < TOTAL_ATTEMPTS) {
|
|
2295
|
-
await new Promise((r) => setTimeout(r, 10 * 2 ** (attempt - 1)));
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
criticalWarn(`[takeSnapshotWithRetry] Snapshot failed after ${MAX_RETRIES} retries (${TOTAL_ATTEMPTS} attempts): ${lastError.message}`);
|
|
2300
|
-
try {
|
|
2301
|
-
emit("snapshot_failed", {
|
|
2302
|
-
error: lastError.message,
|
|
2303
|
-
retries: MAX_RETRIES,
|
|
2304
|
-
source: telemetrySource
|
|
2305
|
-
});
|
|
2306
|
-
} catch {}
|
|
2307
|
-
}
|
|
2308
|
-
async function takeSnapshotEvent(directory, plan, options) {
|
|
2309
|
-
const payloadHash = options?.payloadHashOverride ?? computePlanHash(plan);
|
|
2310
|
-
const snapshotPayload = {
|
|
2311
|
-
plan,
|
|
2312
|
-
payload_hash: payloadHash
|
|
2313
|
-
};
|
|
2314
|
-
if (options?.approvalMetadata) {
|
|
2315
|
-
snapshotPayload.approval = options.approvalMetadata;
|
|
2316
|
-
}
|
|
2317
|
-
const planId = derivePlanId(plan);
|
|
2318
|
-
return appendLedgerEvent(directory, {
|
|
2319
|
-
event_type: "snapshot",
|
|
2320
|
-
source: options?.source ?? "takeSnapshotEvent",
|
|
2321
|
-
plan_id: planId,
|
|
2322
|
-
payload: snapshotPayload
|
|
2323
|
-
}, { planHashAfter: options?.planHashAfter });
|
|
2324
|
-
}
|
|
2325
|
-
async function replayFromLedger(directory, options) {
|
|
2326
|
-
const { plan } = await replayFromLedgerWithStatus(directory, options);
|
|
2327
|
-
return plan;
|
|
2328
|
-
}
|
|
2329
|
-
async function replayFromLedgerWithStatus(directory, _options) {
|
|
2330
|
-
const { events, truncated, badSuffix } = await readLedgerEventsWithIntegrity(directory);
|
|
2331
|
-
if (events.length === 0) {
|
|
2332
|
-
return { plan: null, truncated, badSuffix };
|
|
2333
|
-
}
|
|
2334
|
-
if (truncated && badSuffix !== null) {
|
|
2335
|
-
await quarantineLedgerSuffix(directory, badSuffix);
|
|
2336
|
-
}
|
|
2337
|
-
const plan = reconstructPlanFromEvents(directory, events);
|
|
2338
|
-
return { plan, truncated, badSuffix };
|
|
2339
|
-
}
|
|
2340
|
-
function reconstructPlanFromEvents(directory, events) {
|
|
2341
|
-
const targetPlanId = events[0].plan_id;
|
|
2342
|
-
const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
|
|
2343
|
-
{
|
|
2344
|
-
const snapshotEvents = relevantEvents.filter((e) => e.event_type === "snapshot");
|
|
2345
|
-
if (snapshotEvents.length > 0) {
|
|
2346
|
-
const latestSnapshotEvent = snapshotEvents[snapshotEvents.length - 1];
|
|
2347
|
-
const snapshotPayload = latestSnapshotEvent.payload;
|
|
2348
|
-
let plan2 = snapshotPayload.plan;
|
|
2349
|
-
const eventsAfterSnapshot = relevantEvents.filter((e) => e.seq > latestSnapshotEvent.seq);
|
|
2350
|
-
for (const event of eventsAfterSnapshot) {
|
|
2351
|
-
plan2 = applyEventToPlan(plan2, event);
|
|
2352
|
-
if (plan2 === null) {
|
|
2353
|
-
return null;
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
return plan2;
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
const createdEvent = relevantEvents.find((e) => e.event_type === "plan_created");
|
|
2360
|
-
if (createdEvent?.payload && typeof createdEvent.payload === "object" && "plan" in createdEvent.payload) {
|
|
2361
|
-
const parseResult = PlanSchema.safeParse(createdEvent.payload.plan);
|
|
2362
|
-
if (parseResult.success) {
|
|
2363
|
-
let plan2 = parseResult.data;
|
|
2364
|
-
const eventsAfterCreated = relevantEvents.filter((e) => e.seq > createdEvent.seq);
|
|
2365
|
-
for (const event of eventsAfterCreated) {
|
|
2366
|
-
if (plan2 === null)
|
|
2367
|
-
return null;
|
|
2368
|
-
plan2 = applyEventToPlan(plan2, event);
|
|
2369
|
-
}
|
|
2370
|
-
return plan2;
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
const planJsonPath = getPlanJsonPath(directory);
|
|
2374
|
-
if (!fs5.existsSync(planJsonPath)) {
|
|
2375
|
-
return null;
|
|
2376
|
-
}
|
|
2377
|
-
let plan;
|
|
2378
|
-
try {
|
|
2379
|
-
const content = fs5.readFileSync(planJsonPath, "utf8");
|
|
2380
|
-
plan = JSON.parse(content);
|
|
2381
|
-
} catch {
|
|
2382
|
-
return null;
|
|
2383
|
-
}
|
|
2384
|
-
for (const event of relevantEvents) {
|
|
2385
|
-
if (plan === null) {
|
|
2386
|
-
return null;
|
|
2387
|
-
}
|
|
2388
|
-
plan = applyEventToPlan(plan, event);
|
|
2389
|
-
}
|
|
2390
|
-
return plan;
|
|
2391
|
-
}
|
|
2392
|
-
function applyEventToPlan(plan, event) {
|
|
2393
|
-
switch (event.event_type) {
|
|
2394
|
-
case "plan_created":
|
|
2395
|
-
if (event.payload && typeof event.payload === "object" && "plan" in event.payload) {
|
|
2396
|
-
const parsed = PlanSchema.safeParse(event.payload.plan);
|
|
2397
|
-
if (parsed.success)
|
|
2398
|
-
return parsed.data;
|
|
2399
|
-
}
|
|
2400
|
-
return plan;
|
|
2401
|
-
case "task_status_changed":
|
|
2402
|
-
if (event.task_id && event.to_status) {
|
|
2403
|
-
const parseResult = TaskStatusSchema.safeParse(event.to_status);
|
|
2404
|
-
if (!parseResult.success) {
|
|
2405
|
-
return plan;
|
|
2406
|
-
}
|
|
2407
|
-
for (const phase of plan.phases) {
|
|
2408
|
-
const task = phase.tasks.find((t) => t.id === event.task_id);
|
|
2409
|
-
if (task) {
|
|
2410
|
-
task.status = parseResult.data;
|
|
2411
|
-
break;
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
return plan;
|
|
2416
|
-
case "phase_completed":
|
|
2417
|
-
if (event.phase_id) {
|
|
2418
|
-
const phase = plan.phases.find((p) => p.id === event.phase_id);
|
|
2419
|
-
if (phase) {
|
|
2420
|
-
phase.status = "complete";
|
|
2421
|
-
}
|
|
2422
|
-
}
|
|
2423
|
-
return plan;
|
|
2424
|
-
case "plan_exported":
|
|
2425
|
-
return plan;
|
|
2426
|
-
case "task_added":
|
|
2427
|
-
return plan;
|
|
2428
|
-
case "task_removed":
|
|
2429
|
-
if (event.task_id) {
|
|
2430
|
-
for (const phase of plan.phases) {
|
|
2431
|
-
const idx = phase.tasks.findIndex((t) => t.id === event.task_id);
|
|
2432
|
-
if (idx !== -1) {
|
|
2433
|
-
phase.tasks.splice(idx, 1);
|
|
2434
|
-
break;
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
return plan;
|
|
2439
|
-
case "task_updated":
|
|
2440
|
-
return plan;
|
|
2441
|
-
case "plan_rebuilt":
|
|
2442
|
-
return plan;
|
|
2443
|
-
case "task_reordered":
|
|
2444
|
-
return plan;
|
|
2445
|
-
case "snapshot":
|
|
2446
|
-
return plan;
|
|
2447
|
-
case "plan_reset":
|
|
2448
|
-
return null;
|
|
2449
|
-
case "execution_profile_set": {
|
|
2450
|
-
const rawProfile = event.payload?.execution_profile;
|
|
2451
|
-
if (rawProfile !== undefined) {
|
|
2452
|
-
const parsed = ExecutionProfileSchema.safeParse(rawProfile);
|
|
2453
|
-
if (parsed.success) {
|
|
2454
|
-
return { ...plan, execution_profile: parsed.data };
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
return plan;
|
|
2458
|
-
}
|
|
2459
|
-
case "execution_profile_locked": {
|
|
2460
|
-
if (plan.execution_profile) {
|
|
2461
|
-
return {
|
|
2462
|
-
...plan,
|
|
2463
|
-
execution_profile: { ...plan.execution_profile, locked: true }
|
|
2464
|
-
};
|
|
2465
|
-
}
|
|
2466
|
-
return plan;
|
|
2467
|
-
}
|
|
2468
|
-
default:
|
|
2469
|
-
throw new Error(`applyEventToPlan: unhandled event type "${event.event_type}" at seq ${event.seq}`);
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
async function readLedgerEventsWithIntegrity(directory) {
|
|
2473
|
-
const ledgerPath = getLedgerPath(directory);
|
|
2474
|
-
if (!fs5.existsSync(ledgerPath)) {
|
|
2475
|
-
return { events: [], truncated: false, badSuffix: null };
|
|
2476
|
-
}
|
|
2477
|
-
try {
|
|
2478
|
-
const content = fs5.readFileSync(ledgerPath, "utf8");
|
|
2479
|
-
const lines = content.split(`
|
|
2480
|
-
`);
|
|
2481
|
-
const events = [];
|
|
2482
|
-
let truncated = false;
|
|
2483
|
-
let badSuffix = null;
|
|
2484
|
-
for (let i = 0;i < lines.length; i++) {
|
|
2485
|
-
const line = lines[i];
|
|
2486
|
-
if (line.trim() === "") {
|
|
2487
|
-
continue;
|
|
2488
|
-
}
|
|
2489
|
-
try {
|
|
2490
|
-
const event = JSON.parse(line);
|
|
2491
|
-
events.push(event);
|
|
2492
|
-
} catch {
|
|
2493
|
-
truncated = true;
|
|
2494
|
-
badSuffix = lines.slice(i).join(`
|
|
2495
|
-
`);
|
|
2496
|
-
break;
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
events.sort((a, b) => a.seq - b.seq);
|
|
2500
|
-
return { events, truncated, badSuffix };
|
|
2501
|
-
} catch {
|
|
2502
|
-
return { events: [], truncated: false, badSuffix: null };
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
async function quarantineLedgerSuffix(directory, badSuffix) {
|
|
2506
|
-
let salvagedCount = 0;
|
|
2507
|
-
for (const line of badSuffix.split(`
|
|
2508
|
-
`)) {
|
|
2509
|
-
if (line.trim() === "")
|
|
2510
|
-
continue;
|
|
2511
|
-
try {
|
|
2512
|
-
JSON.parse(line);
|
|
2513
|
-
salvagedCount++;
|
|
2514
|
-
} catch {}
|
|
2515
|
-
}
|
|
2516
|
-
try {
|
|
2517
|
-
const hash = crypto2.createHash("sha256").update(badSuffix, "utf8").digest("hex").slice(0, 12);
|
|
2518
|
-
const swarmDir = path8.join(directory, ".swarm");
|
|
2519
|
-
try {
|
|
2520
|
-
const existing = fs5.readdirSync(swarmDir).filter((name) => name.startsWith("plan-ledger.quarantine.") && name.endsWith(`.${hash}`));
|
|
2521
|
-
for (const name of existing) {
|
|
2522
|
-
const existingPath = path8.join(swarmDir, name);
|
|
2523
|
-
if (fs5.readFileSync(existingPath, "utf8") === badSuffix) {
|
|
2524
|
-
return { path: existingPath, salvagedCount };
|
|
2525
|
-
}
|
|
2526
|
-
}
|
|
2527
|
-
} catch {}
|
|
2528
|
-
const quarantinePath = path8.join(swarmDir, `plan-ledger.quarantine.${Date.now()}.${hash}`);
|
|
2529
|
-
fs5.writeFileSync(quarantinePath, badSuffix, "utf8");
|
|
2530
|
-
log(`[ledger] Corrupted suffix quarantined to ${path8.relative(directory, quarantinePath)} (salvageable events: ${salvagedCount})`);
|
|
2531
|
-
return { path: quarantinePath, salvagedCount };
|
|
2532
|
-
} catch {
|
|
2533
|
-
return { path: null, salvagedCount };
|
|
2534
|
-
}
|
|
2535
|
-
}
|
|
2536
|
-
async function loadLastApprovedPlan(directory, expectedPlanId) {
|
|
2537
|
-
const events = await readLedgerEvents(directory);
|
|
2538
|
-
return findLastApprovedSnapshot(events, expectedPlanId);
|
|
2539
|
-
}
|
|
2540
|
-
function findLastApprovedSnapshot(events, expectedPlanId, extraFilter) {
|
|
2541
|
-
if (events.length === 0) {
|
|
2542
|
-
return null;
|
|
2543
|
-
}
|
|
2544
|
-
for (let i = events.length - 1;i >= 0; i--) {
|
|
2545
|
-
const event = events[i];
|
|
2546
|
-
if (event.event_type !== "snapshot")
|
|
2547
|
-
continue;
|
|
2548
|
-
if (event.source !== "critic_approved")
|
|
2549
|
-
continue;
|
|
2550
|
-
if (expectedPlanId !== undefined && event.plan_id !== expectedPlanId) {
|
|
2551
|
-
continue;
|
|
2552
|
-
}
|
|
2553
|
-
const payload = event.payload;
|
|
2554
|
-
if (!payload || typeof payload !== "object" || !payload.plan) {
|
|
2555
|
-
continue;
|
|
2556
|
-
}
|
|
2557
|
-
if (expectedPlanId !== undefined) {
|
|
2558
|
-
const payloadPlanId = derivePlanId(payload.plan);
|
|
2559
|
-
if (payloadPlanId !== expectedPlanId) {
|
|
2560
|
-
continue;
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
if (extraFilter && !extraFilter(payload)) {
|
|
2564
|
-
continue;
|
|
2565
|
-
}
|
|
2566
|
-
return {
|
|
2567
|
-
plan: payload.plan,
|
|
2568
|
-
seq: event.seq,
|
|
2569
|
-
timestamp: event.timestamp,
|
|
2570
|
-
approval: payload.approval,
|
|
2571
|
-
payloadHash: payload.payload_hash
|
|
2572
|
-
};
|
|
2573
|
-
}
|
|
2574
|
-
return null;
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
2181
|
// src/plan/manager.ts
|
|
2578
2182
|
class PlanConcurrentModificationError extends Error {
|
|
2579
2183
|
constructor(message) {
|
|
@@ -2735,7 +2339,7 @@ async function getLatestLedgerHash(directory) {
|
|
|
2735
2339
|
}
|
|
2736
2340
|
}
|
|
2737
2341
|
async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
2738
|
-
const resolvedWorkspace =
|
|
2342
|
+
const resolvedWorkspace = path8.resolve(directory);
|
|
2739
2343
|
if (!ledgerStaleWorkspaces.has(resolvedWorkspace)) {
|
|
2740
2344
|
return plan;
|
|
2741
2345
|
}
|
|
@@ -2754,7 +2358,7 @@ async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
|
2754
2358
|
return plan;
|
|
2755
2359
|
}
|
|
2756
2360
|
async function parsePlanJsonCached(directory) {
|
|
2757
|
-
const planJsonPath =
|
|
2361
|
+
const planJsonPath = path8.resolve(directory, ".swarm", "plan.json");
|
|
2758
2362
|
return readCachedParsedFile(planJsonPath, PLAN_JSON_CACHE_NAMESPACE, () => readSwarmFileAsync(directory, "plan.json"), (planJsonContent) => {
|
|
2759
2363
|
if (planJsonContent.includes("\x00") || planJsonContent.includes("\uFFFD")) {
|
|
2760
2364
|
throw new Error("Plan rejected: .swarm/plan.json contains null bytes or invalid encoding");
|
|
@@ -2814,16 +2418,16 @@ async function isPlanMdInSync(directory, plan, cache) {
|
|
|
2814
2418
|
return false;
|
|
2815
2419
|
}
|
|
2816
2420
|
async function regeneratePlanMarkdown(directory, plan) {
|
|
2817
|
-
const swarmDir =
|
|
2421
|
+
const swarmDir = path8.resolve(directory, ".swarm");
|
|
2818
2422
|
const contentHash = computePlanContentHash(plan);
|
|
2819
2423
|
const markdown = derivePlanMarkdown(plan);
|
|
2820
2424
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
2821
2425
|
${markdown}`;
|
|
2822
|
-
const mdPath =
|
|
2823
|
-
const mdTempPath =
|
|
2426
|
+
const mdPath = path8.join(swarmDir, "plan.md");
|
|
2427
|
+
const mdTempPath = path8.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2824
2428
|
try {
|
|
2825
2429
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
2826
|
-
|
|
2430
|
+
renameSync3(mdTempPath, mdPath);
|
|
2827
2431
|
} finally {
|
|
2828
2432
|
try {
|
|
2829
2433
|
unlinkSync3(mdTempPath);
|
|
@@ -2852,7 +2456,7 @@ async function loadPlan(directory, cache) {
|
|
|
2852
2456
|
if (await ledgerExists(directory)) {
|
|
2853
2457
|
const planHash = computePlanHash(validated);
|
|
2854
2458
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
2855
|
-
const resolvedWorkspace =
|
|
2459
|
+
const resolvedWorkspace = path8.resolve(directory);
|
|
2856
2460
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
2857
2461
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
2858
2462
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
@@ -2926,7 +2530,7 @@ async function loadPlan(directory, cache) {
|
|
|
2926
2530
|
if (!preserving) {
|
|
2927
2531
|
try {
|
|
2928
2532
|
const diffInfo = computeSpecDiff(directory);
|
|
2929
|
-
const specStalenessPath =
|
|
2533
|
+
const specStalenessPath = path8.join(directory, ".swarm", "spec-staleness.json");
|
|
2930
2534
|
await fsPromises.writeFile(specStalenessPath, JSON.stringify({
|
|
2931
2535
|
type: "spec_stale_detected",
|
|
2932
2536
|
timestamp: new Date().toISOString(),
|
|
@@ -2940,7 +2544,7 @@ async function loadPlan(directory, cache) {
|
|
|
2940
2544
|
}, null, 2), "utf-8");
|
|
2941
2545
|
} catch {}
|
|
2942
2546
|
try {
|
|
2943
|
-
const eventsPath =
|
|
2547
|
+
const eventsPath = path8.join(directory, ".swarm", "events.jsonl");
|
|
2944
2548
|
const event = {
|
|
2945
2549
|
type: "spec_stale_detected",
|
|
2946
2550
|
timestamp: new Date().toISOString(),
|
|
@@ -3006,7 +2610,7 @@ async function loadPlan(directory, cache) {
|
|
|
3006
2610
|
return migrated;
|
|
3007
2611
|
}
|
|
3008
2612
|
if (await ledgerExists(directory)) {
|
|
3009
|
-
const resolvedDir =
|
|
2613
|
+
const resolvedDir = path8.resolve(directory);
|
|
3010
2614
|
const existingMutex = recoveryMutexes.get(resolvedDir);
|
|
3011
2615
|
if (existingMutex) {
|
|
3012
2616
|
await existingMutex;
|
|
@@ -3135,13 +2739,13 @@ async function savePlan(directory, plan, options) {
|
|
|
3135
2739
|
} else {
|
|
3136
2740
|
const existingEvents = await readLedgerEvents(directory);
|
|
3137
2741
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
3138
|
-
const swarmDir2 =
|
|
3139
|
-
const oldLedgerPath =
|
|
3140
|
-
const oldLedgerBackupPath =
|
|
2742
|
+
const swarmDir2 = path8.resolve(directory, ".swarm");
|
|
2743
|
+
const oldLedgerPath = path8.join(swarmDir2, "plan-ledger.jsonl");
|
|
2744
|
+
const oldLedgerBackupPath = path8.join(swarmDir2, `plan-ledger.backup-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
3141
2745
|
let backupExists = false;
|
|
3142
|
-
if (
|
|
2746
|
+
if (existsSync4(oldLedgerPath)) {
|
|
3143
2747
|
try {
|
|
3144
|
-
|
|
2748
|
+
renameSync3(oldLedgerPath, oldLedgerBackupPath);
|
|
3145
2749
|
backupExists = true;
|
|
3146
2750
|
} catch (renameErr) {
|
|
3147
2751
|
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.`);
|
|
@@ -3156,13 +2760,13 @@ async function savePlan(directory, plan, options) {
|
|
|
3156
2760
|
const errorMessage2 = String(initErr);
|
|
3157
2761
|
if (errorMessage2.includes("already initialized")) {
|
|
3158
2762
|
try {
|
|
3159
|
-
if (
|
|
2763
|
+
if (existsSync4(oldLedgerBackupPath))
|
|
3160
2764
|
unlinkSync3(oldLedgerBackupPath);
|
|
3161
2765
|
} catch {}
|
|
3162
2766
|
} else {
|
|
3163
|
-
if (
|
|
2767
|
+
if (existsSync4(oldLedgerBackupPath)) {
|
|
3164
2768
|
try {
|
|
3165
|
-
|
|
2769
|
+
renameSync3(oldLedgerBackupPath, oldLedgerPath);
|
|
3166
2770
|
} catch {
|
|
3167
2771
|
copyFileSync(oldLedgerBackupPath, oldLedgerPath);
|
|
3168
2772
|
try {
|
|
@@ -3175,32 +2779,32 @@ async function savePlan(directory, plan, options) {
|
|
|
3175
2779
|
}
|
|
3176
2780
|
}
|
|
3177
2781
|
if (initSucceeded && backupExists) {
|
|
3178
|
-
const archivePath =
|
|
2782
|
+
const archivePath = path8.join(swarmDir2, `plan-ledger.archived-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
3179
2783
|
try {
|
|
3180
|
-
|
|
2784
|
+
renameSync3(oldLedgerBackupPath, archivePath);
|
|
3181
2785
|
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived old ledger to ${archivePath} and reinitializing.`);
|
|
3182
2786
|
} catch (renameErr) {
|
|
3183
2787
|
warn(`[savePlan] Could not archive old ledger (rename failed: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}). Old ledger may still exist at ${oldLedgerBackupPath}.`);
|
|
3184
2788
|
try {
|
|
3185
|
-
if (
|
|
2789
|
+
if (existsSync4(oldLedgerBackupPath))
|
|
3186
2790
|
unlinkSync3(oldLedgerBackupPath);
|
|
3187
2791
|
} catch {}
|
|
3188
2792
|
}
|
|
3189
2793
|
} else if (!initSucceeded && backupExists) {
|
|
3190
2794
|
try {
|
|
3191
|
-
if (
|
|
2795
|
+
if (existsSync4(oldLedgerBackupPath))
|
|
3192
2796
|
unlinkSync3(oldLedgerBackupPath);
|
|
3193
2797
|
} catch {}
|
|
3194
2798
|
}
|
|
3195
2799
|
const MAX_ARCHIVED_SIBLINGS = 5;
|
|
3196
2800
|
try {
|
|
3197
|
-
const allFiles =
|
|
2801
|
+
const allFiles = readdirSync(swarmDir2);
|
|
3198
2802
|
const archivedSiblings = allFiles.filter((f) => f.startsWith("plan-ledger.archived-") && f.endsWith(".jsonl")).sort();
|
|
3199
2803
|
if (archivedSiblings.length > MAX_ARCHIVED_SIBLINGS) {
|
|
3200
2804
|
const toRemove = archivedSiblings.slice(0, archivedSiblings.length - MAX_ARCHIVED_SIBLINGS);
|
|
3201
2805
|
for (const file of toRemove) {
|
|
3202
2806
|
try {
|
|
3203
|
-
unlinkSync3(
|
|
2807
|
+
unlinkSync3(path8.join(swarmDir2, file));
|
|
3204
2808
|
} catch {}
|
|
3205
2809
|
}
|
|
3206
2810
|
}
|
|
@@ -3343,19 +2947,19 @@ async function savePlan(directory, plan, options) {
|
|
|
3343
2947
|
source: "savePlan_manager"
|
|
3344
2948
|
});
|
|
3345
2949
|
}
|
|
3346
|
-
const swarmDir =
|
|
3347
|
-
const planPath =
|
|
3348
|
-
const tempPath =
|
|
2950
|
+
const swarmDir = path8.resolve(directory, ".swarm");
|
|
2951
|
+
const planPath = path8.join(swarmDir, "plan.json");
|
|
2952
|
+
const tempPath = path8.join(swarmDir, `plan.json.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3349
2953
|
try {
|
|
3350
2954
|
await bunWrite(tempPath, JSON.stringify(projectedPlan, null, 2));
|
|
3351
|
-
|
|
2955
|
+
renameSync3(tempPath, planPath);
|
|
3352
2956
|
} finally {
|
|
3353
2957
|
try {
|
|
3354
2958
|
unlinkSync3(tempPath);
|
|
3355
2959
|
} catch {}
|
|
3356
2960
|
}
|
|
3357
2961
|
try {
|
|
3358
|
-
const markerPath =
|
|
2962
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
3359
2963
|
const inProgressMarker = JSON.stringify({
|
|
3360
2964
|
source: "plan_manager",
|
|
3361
2965
|
timestamp: new Date().toISOString(),
|
|
@@ -3370,11 +2974,11 @@ async function savePlan(directory, plan, options) {
|
|
|
3370
2974
|
const markdown = derivePlanMarkdown(projectedPlan);
|
|
3371
2975
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3372
2976
|
${markdown}`;
|
|
3373
|
-
const mdPath =
|
|
3374
|
-
const mdTempPath =
|
|
2977
|
+
const mdPath = path8.join(swarmDir, "plan.md");
|
|
2978
|
+
const mdTempPath = path8.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3375
2979
|
try {
|
|
3376
2980
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
3377
|
-
|
|
2981
|
+
renameSync3(mdTempPath, mdPath);
|
|
3378
2982
|
} finally {
|
|
3379
2983
|
try {
|
|
3380
2984
|
unlinkSync3(mdTempPath);
|
|
@@ -3392,7 +2996,7 @@ ${markdown}`;
|
|
|
3392
2996
|
} catch {}
|
|
3393
2997
|
}
|
|
3394
2998
|
try {
|
|
3395
|
-
const markerPath =
|
|
2999
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
3396
3000
|
const tasksCount = projectedPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
3397
3001
|
const marker = JSON.stringify({
|
|
3398
3002
|
source: "plan_manager",
|
|
@@ -3408,23 +3012,23 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
3408
3012
|
const targetPlan = plan ?? await replayFromLedger(directory);
|
|
3409
3013
|
if (!targetPlan)
|
|
3410
3014
|
return null;
|
|
3411
|
-
const swarmDir =
|
|
3412
|
-
const planPath =
|
|
3413
|
-
const mdPath =
|
|
3414
|
-
|
|
3415
|
-
const tempPlanPath =
|
|
3015
|
+
const swarmDir = path8.join(directory, ".swarm");
|
|
3016
|
+
const planPath = path8.join(swarmDir, "plan.json");
|
|
3017
|
+
const mdPath = path8.join(swarmDir, "plan.md");
|
|
3018
|
+
mkdirSync4(swarmDir, { recursive: true });
|
|
3019
|
+
const tempPlanPath = path8.join(swarmDir, `plan.json.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3416
3020
|
{
|
|
3417
|
-
const fd =
|
|
3021
|
+
const fd = openSync(tempPlanPath, "w");
|
|
3418
3022
|
try {
|
|
3419
|
-
|
|
3420
|
-
|
|
3023
|
+
writeFileSync5(fd, JSON.stringify(targetPlan, null, 2), "utf8");
|
|
3024
|
+
fsyncSync(fd);
|
|
3421
3025
|
} finally {
|
|
3422
|
-
|
|
3026
|
+
closeSync(fd);
|
|
3423
3027
|
}
|
|
3424
3028
|
}
|
|
3425
|
-
|
|
3029
|
+
renameSync3(tempPlanPath, planPath);
|
|
3426
3030
|
try {
|
|
3427
|
-
const markerPath =
|
|
3031
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
3428
3032
|
const inProgressMarker = JSON.stringify({
|
|
3429
3033
|
source: "plan_manager",
|
|
3430
3034
|
timestamp: new Date().toISOString(),
|
|
@@ -3439,12 +3043,12 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
3439
3043
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
3440
3044
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3441
3045
|
${markdown}`;
|
|
3442
|
-
const tempMdPath =
|
|
3046
|
+
const tempMdPath = path8.join(swarmDir, `plan.md.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3443
3047
|
await bunWrite(tempMdPath, markdownWithHash);
|
|
3444
|
-
|
|
3048
|
+
renameSync3(tempMdPath, mdPath);
|
|
3445
3049
|
} finally {
|
|
3446
3050
|
try {
|
|
3447
|
-
const markerPath =
|
|
3051
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
3448
3052
|
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
3449
3053
|
const marker = JSON.stringify({
|
|
3450
3054
|
source: "plan_manager",
|
|
@@ -3507,13 +3111,13 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
3507
3111
|
planHashAfter: hashAfter,
|
|
3508
3112
|
source: "close_terminal"
|
|
3509
3113
|
});
|
|
3510
|
-
const swarmDir =
|
|
3511
|
-
const planPath =
|
|
3512
|
-
const tempPlanPath =
|
|
3114
|
+
const swarmDir = path8.join(directory, ".swarm");
|
|
3115
|
+
const planPath = path8.join(swarmDir, "plan.json");
|
|
3116
|
+
const tempPlanPath = path8.join(swarmDir, `plan.json.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3513
3117
|
await bunWrite(tempPlanPath, JSON.stringify(validated, null, 2));
|
|
3514
|
-
|
|
3118
|
+
renameSync3(tempPlanPath, planPath);
|
|
3515
3119
|
try {
|
|
3516
|
-
const markerPath =
|
|
3120
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
3517
3121
|
const inProgressMarker = JSON.stringify({
|
|
3518
3122
|
source: "plan_manager_close",
|
|
3519
3123
|
timestamp: new Date().toISOString(),
|
|
@@ -3524,17 +3128,17 @@ async function closePlanTerminalState(directory, plan, options) {
|
|
|
3524
3128
|
await bunWrite(markerPath, inProgressMarker);
|
|
3525
3129
|
} catch {}
|
|
3526
3130
|
try {
|
|
3527
|
-
const mdPath =
|
|
3131
|
+
const mdPath = path8.join(swarmDir, "plan.md");
|
|
3528
3132
|
const contentHash = computePlanContentHash(validated);
|
|
3529
3133
|
const markdown = derivePlanMarkdown(validated);
|
|
3530
3134
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
3531
3135
|
${markdown}`;
|
|
3532
|
-
const mdTempPath =
|
|
3136
|
+
const mdTempPath = path8.join(swarmDir, `plan.md.close.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
3533
3137
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
3534
|
-
|
|
3138
|
+
renameSync3(mdTempPath, mdPath);
|
|
3535
3139
|
} finally {
|
|
3536
3140
|
try {
|
|
3537
|
-
const markerPath =
|
|
3141
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
3538
3142
|
const tasksCount = validated.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
3539
3143
|
const marker = JSON.stringify({
|
|
3540
3144
|
source: "plan_manager_close",
|
|
@@ -3818,19 +3422,19 @@ function migrateLegacyPlan(planContent, swarmId) {
|
|
|
3818
3422
|
|
|
3819
3423
|
// src/evidence/manager.ts
|
|
3820
3424
|
import {
|
|
3821
|
-
mkdirSync as
|
|
3822
|
-
readdirSync as
|
|
3425
|
+
mkdirSync as mkdirSync5,
|
|
3426
|
+
readdirSync as readdirSync2,
|
|
3823
3427
|
realpathSync as realpathSync2,
|
|
3824
3428
|
rmSync,
|
|
3825
3429
|
statSync as statSync2
|
|
3826
3430
|
} from "fs";
|
|
3827
|
-
import * as
|
|
3431
|
+
import * as fs6 from "fs/promises";
|
|
3828
3432
|
import * as os4 from "os";
|
|
3829
|
-
import * as
|
|
3433
|
+
import * as path12 from "path";
|
|
3830
3434
|
|
|
3831
3435
|
// src/evaluation/retention.ts
|
|
3832
|
-
import * as
|
|
3833
|
-
import * as
|
|
3436
|
+
import * as fs5 from "fs/promises";
|
|
3437
|
+
import * as path11 from "path";
|
|
3834
3438
|
|
|
3835
3439
|
// src/evaluation/contracts.ts
|
|
3836
3440
|
var IdentifierSchema = exports_external.string().min(1).max(160).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
|
|
@@ -4205,13 +3809,13 @@ var TaskSetSnapshotV1Schema = exports_external.object({
|
|
|
4205
3809
|
|
|
4206
3810
|
// src/evaluation/store.ts
|
|
4207
3811
|
import { mkdir, readdir, readFile as readFile2 } from "fs/promises";
|
|
4208
|
-
import * as
|
|
3812
|
+
import * as path10 from "path";
|
|
4209
3813
|
|
|
4210
3814
|
// src/evaluation/hashing.ts
|
|
4211
|
-
import { createHash as
|
|
3815
|
+
import { createHash as createHash2 } from "crypto";
|
|
4212
3816
|
import { lstatSync, realpathSync } from "fs";
|
|
4213
3817
|
import { lstat, opendir, readFile, realpath } from "fs/promises";
|
|
4214
|
-
import * as
|
|
3818
|
+
import * as path9 from "path";
|
|
4215
3819
|
function canonicalize(value, ancestors) {
|
|
4216
3820
|
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
4217
3821
|
return value;
|
|
@@ -4252,7 +3856,7 @@ function canonicalJson(value) {
|
|
|
4252
3856
|
return JSON.stringify(canonicalize(value, new Set));
|
|
4253
3857
|
}
|
|
4254
3858
|
function sha256(value) {
|
|
4255
|
-
return
|
|
3859
|
+
return createHash2("sha256").update(value).digest("hex");
|
|
4256
3860
|
}
|
|
4257
3861
|
function canonicalHash(value) {
|
|
4258
3862
|
return sha256(canonicalJson(value));
|
|
@@ -4302,9 +3906,9 @@ async function hashTree(projectRoot, relativePath, budget) {
|
|
|
4302
3906
|
}
|
|
4303
3907
|
if (!entryAlreadyCharged)
|
|
4304
3908
|
chargeHashEntry(budget);
|
|
4305
|
-
const
|
|
3909
|
+
const relative2 = path9.relative(canonicalRoot, absolutePath).replace(/\\/g, "/");
|
|
4306
3910
|
if (stat.isDirectory()) {
|
|
4307
|
-
entries.push({ path:
|
|
3911
|
+
entries.push({ path: relative2, type: "directory" });
|
|
4308
3912
|
const children = [];
|
|
4309
3913
|
const directory = await _internals9.opendir(absolutePath);
|
|
4310
3914
|
try {
|
|
@@ -4319,12 +3923,12 @@ async function hashTree(projectRoot, relativePath, budget) {
|
|
|
4319
3923
|
await closeDirectory(directory);
|
|
4320
3924
|
}
|
|
4321
3925
|
for (const child of children.sort()) {
|
|
4322
|
-
await visit(
|
|
3926
|
+
await visit(path9.join(absolutePath, child), true);
|
|
4323
3927
|
}
|
|
4324
3928
|
return;
|
|
4325
3929
|
}
|
|
4326
3930
|
if (!stat.isFile()) {
|
|
4327
|
-
throw new Error(`evaluation input must contain only regular files: ${
|
|
3931
|
+
throw new Error(`evaluation input must contain only regular files: ${relative2}`);
|
|
4328
3932
|
}
|
|
4329
3933
|
budget.bytes += stat.size;
|
|
4330
3934
|
if (budget.bytes > budget.limits.maxBytes) {
|
|
@@ -4332,7 +3936,7 @@ async function hashTree(projectRoot, relativePath, budget) {
|
|
|
4332
3936
|
}
|
|
4333
3937
|
const content = await _internals9.readFile(absolutePath);
|
|
4334
3938
|
entries.push({
|
|
4335
|
-
path:
|
|
3939
|
+
path: relative2,
|
|
4336
3940
|
type: "file",
|
|
4337
3941
|
bytes: content.byteLength,
|
|
4338
3942
|
sha256: sha256(content)
|
|
@@ -4362,10 +3966,10 @@ async function computeTaskLineageInputHash(projectRoot, task, limits = {}) {
|
|
|
4362
3966
|
const normalizedTree = async (relativePath) => {
|
|
4363
3967
|
const canonicalRoot = await _internals9.realpath(projectRoot);
|
|
4364
3968
|
const target = await resolveContainedExistingPathAsync(projectRoot, relativePath);
|
|
4365
|
-
const targetRelative =
|
|
3969
|
+
const targetRelative = path9.relative(canonicalRoot, target).replace(/\\/g, "/");
|
|
4366
3970
|
return (await hashTree(projectRoot, relativePath, budget)).map((entry) => ({
|
|
4367
3971
|
...entry,
|
|
4368
|
-
path:
|
|
3972
|
+
path: path9.posix.relative(targetRelative, entry.path) || "."
|
|
4369
3973
|
}));
|
|
4370
3974
|
};
|
|
4371
3975
|
const scorerArgv = [...task.scorer.argv];
|
|
@@ -4394,11 +3998,11 @@ function computeManifestContentHash(manifest) {
|
|
|
4394
3998
|
return contentHashWithout(manifest, ["contentHash"]);
|
|
4395
3999
|
}
|
|
4396
4000
|
function isWithin(root, candidate) {
|
|
4397
|
-
const
|
|
4398
|
-
return
|
|
4001
|
+
const relative2 = path9.relative(root, candidate);
|
|
4002
|
+
return relative2 === "" || !relative2.startsWith("..") && !path9.isAbsolute(relative2);
|
|
4399
4003
|
}
|
|
4400
4004
|
function resolveContainedExistingPath(root, relativePath) {
|
|
4401
|
-
if (
|
|
4005
|
+
if (path9.isAbsolute(relativePath) || /^(?:[A-Za-z]:[\\/]|[\\/]{1,2})/.test(relativePath) || relativePath.includes("\x00")) {
|
|
4402
4006
|
throw new Error("evaluation path must be a non-NUL relative path");
|
|
4403
4007
|
}
|
|
4404
4008
|
const segments = relativePath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
@@ -4410,7 +4014,7 @@ function resolveContainedExistingPath(root, relativePath) {
|
|
|
4410
4014
|
for (const segment of segments) {
|
|
4411
4015
|
if (segment === ".")
|
|
4412
4016
|
continue;
|
|
4413
|
-
cursor =
|
|
4017
|
+
cursor = path9.join(cursor, segment);
|
|
4414
4018
|
const stat = lstatSync(cursor);
|
|
4415
4019
|
if (stat.isSymbolicLink()) {
|
|
4416
4020
|
throw new Error(`evaluation path contains a symlink or reparse point: ${relativePath}`);
|
|
@@ -4423,7 +4027,7 @@ function resolveContainedExistingPath(root, relativePath) {
|
|
|
4423
4027
|
return canonicalCandidate;
|
|
4424
4028
|
}
|
|
4425
4029
|
async function resolveContainedExistingPathAsync(root, relativePath) {
|
|
4426
|
-
if (
|
|
4030
|
+
if (path9.isAbsolute(relativePath) || /^(?:[A-Za-z]:[\\/]|[\\/]{1,2})/.test(relativePath) || relativePath.includes("\x00")) {
|
|
4427
4031
|
throw new Error("evaluation path must be a non-NUL relative path");
|
|
4428
4032
|
}
|
|
4429
4033
|
const segments = relativePath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
@@ -4435,7 +4039,7 @@ async function resolveContainedExistingPathAsync(root, relativePath) {
|
|
|
4435
4039
|
for (const segment of segments) {
|
|
4436
4040
|
if (segment === ".")
|
|
4437
4041
|
continue;
|
|
4438
|
-
cursor =
|
|
4042
|
+
cursor = path9.join(cursor, segment);
|
|
4439
4043
|
const stat = await _internals9.lstat(cursor);
|
|
4440
4044
|
if (stat.isSymbolicLink()) {
|
|
4441
4045
|
throw new Error(`evaluation path contains a symlink or reparse point: ${relativePath}`);
|
|
@@ -4456,8 +4060,8 @@ var _internals9 = {
|
|
|
4456
4060
|
|
|
4457
4061
|
// src/evaluation/store.ts
|
|
4458
4062
|
var AGENT = "evaluation-store";
|
|
4459
|
-
var TEST_LEDGER_RELATIVE =
|
|
4460
|
-
var TASK_SPLIT_REGISTRY_RELATIVE =
|
|
4063
|
+
var TEST_LEDGER_RELATIVE = path10.join("evolution", "test-consumption.jsonl");
|
|
4064
|
+
var TASK_SPLIT_REGISTRY_RELATIVE = path10.join("evolution", "tasks", "split-registry.json");
|
|
4461
4065
|
|
|
4462
4066
|
class EvaluationConflictError extends Error {
|
|
4463
4067
|
constructor(message) {
|
|
@@ -4466,7 +4070,7 @@ class EvaluationConflictError extends Error {
|
|
|
4466
4070
|
}
|
|
4467
4071
|
}
|
|
4468
4072
|
function swarmPath(directory, ...segments) {
|
|
4469
|
-
return
|
|
4073
|
+
return path10.join(directory, ".swarm", ...segments);
|
|
4470
4074
|
}
|
|
4471
4075
|
function serialized(value) {
|
|
4472
4076
|
return `${canonicalJson(value)}
|
|
@@ -4515,7 +4119,7 @@ async function readOptional(filePath) {
|
|
|
4515
4119
|
}
|
|
4516
4120
|
}
|
|
4517
4121
|
async function writeImmutable(options) {
|
|
4518
|
-
await mkdir(
|
|
4122
|
+
await mkdir(path10.dirname(options.filePath), { recursive: true });
|
|
4519
4123
|
return withEvidenceLock(options.directory, options.relativeLockPath, AGENT, options.taskId, async () => {
|
|
4520
4124
|
const desired = serialized(options.value);
|
|
4521
4125
|
const existing = await readOptional(options.filePath);
|
|
@@ -4543,8 +4147,8 @@ function validateTaskInputs(directory, task) {
|
|
|
4543
4147
|
if (!executable)
|
|
4544
4148
|
throw new Error("project scorer requires an executable");
|
|
4545
4149
|
const scorerPath = resolveContainedExistingPath(directory, executable);
|
|
4546
|
-
const
|
|
4547
|
-
if (!
|
|
4150
|
+
const relative3 = path10.relative(environmentRoot, scorerPath);
|
|
4151
|
+
if (!relative3 || relative3 === ".." || relative3.startsWith(`..${path10.sep}`) || path10.isAbsolute(relative3)) {
|
|
4548
4152
|
throw new Error("project scorer executable must be inside the admitted task environment");
|
|
4549
4153
|
}
|
|
4550
4154
|
}
|
|
@@ -4556,10 +4160,10 @@ async function admitEvaluationTask(directory, input, inputRoot = directory) {
|
|
|
4556
4160
|
}
|
|
4557
4161
|
validateTaskInputs(inputRoot, task);
|
|
4558
4162
|
const lineageInputHash = await computeTaskLineageInputHash(inputRoot, task);
|
|
4559
|
-
const
|
|
4163
|
+
const relative3 = path10.join("evolution", "tasks", task.split, `${task.id}.json`);
|
|
4560
4164
|
const admissionLock = TASK_SPLIT_REGISTRY_RELATIVE;
|
|
4561
|
-
const filePath = swarmPath(directory,
|
|
4562
|
-
await mkdir(
|
|
4165
|
+
const filePath = swarmPath(directory, relative3);
|
|
4166
|
+
await mkdir(path10.dirname(filePath), { recursive: true });
|
|
4563
4167
|
return withEvidenceLock(directory, admissionLock, AGENT, task.id, async () => {
|
|
4564
4168
|
const registryPath = swarmPath(directory, TASK_SPLIT_REGISTRY_RELATIVE);
|
|
4565
4169
|
const registry = parseTaskSplitRegistry(await readOptional(registryPath));
|
|
@@ -4647,11 +4251,11 @@ async function saveTaskSetSnapshot(directory, input, inputRoot = directory) {
|
|
|
4647
4251
|
if (computeTaskSetContentHash(snapshot) !== snapshot.contentHash) {
|
|
4648
4252
|
throw new Error("task-set content hash does not match its canonical inputs");
|
|
4649
4253
|
}
|
|
4650
|
-
const
|
|
4254
|
+
const relative3 = path10.join("evolution", "task-sets", `${snapshot.contentHash}.json`);
|
|
4651
4255
|
return writeImmutable({
|
|
4652
4256
|
directory,
|
|
4653
|
-
relativeLockPath:
|
|
4654
|
-
filePath: swarmPath(directory,
|
|
4257
|
+
relativeLockPath: relative3,
|
|
4258
|
+
filePath: swarmPath(directory, relative3),
|
|
4655
4259
|
taskId: snapshot.id,
|
|
4656
4260
|
value: snapshot,
|
|
4657
4261
|
parse: (value) => TaskSetSnapshotV1Schema.parse(value),
|
|
@@ -4660,11 +4264,11 @@ async function saveTaskSetSnapshot(directory, input, inputRoot = directory) {
|
|
|
4660
4264
|
}
|
|
4661
4265
|
async function saveGateAuditResult(directory, input) {
|
|
4662
4266
|
const result = GateAuditResultV1Schema.parse(input);
|
|
4663
|
-
const
|
|
4267
|
+
const relative3 = path10.join("evidence", "gate-audit", result.runId, "results.json");
|
|
4664
4268
|
return writeImmutable({
|
|
4665
4269
|
directory,
|
|
4666
|
-
relativeLockPath:
|
|
4667
|
-
filePath: swarmPath(directory,
|
|
4270
|
+
relativeLockPath: relative3,
|
|
4271
|
+
filePath: swarmPath(directory, relative3),
|
|
4668
4272
|
taskId: result.runId,
|
|
4669
4273
|
value: result,
|
|
4670
4274
|
parse: (value) => GateAuditResultV1Schema.parse(value)
|
|
@@ -4672,11 +4276,11 @@ async function saveGateAuditResult(directory, input) {
|
|
|
4672
4276
|
}
|
|
4673
4277
|
async function saveGateAuditManifest(directory, input) {
|
|
4674
4278
|
const manifest = GateAuditManifestV1Schema.parse(input);
|
|
4675
|
-
const
|
|
4279
|
+
const relative3 = path10.join("evolution", "gate-audit-manifests", `${manifest.id}.json`);
|
|
4676
4280
|
return writeImmutable({
|
|
4677
4281
|
directory,
|
|
4678
|
-
relativeLockPath:
|
|
4679
|
-
filePath: swarmPath(directory,
|
|
4282
|
+
relativeLockPath: relative3,
|
|
4283
|
+
filePath: swarmPath(directory, relative3),
|
|
4680
4284
|
taskId: manifest.id,
|
|
4681
4285
|
value: manifest,
|
|
4682
4286
|
parse: (value) => GateAuditManifestV1Schema.parse(value)
|
|
@@ -4708,7 +4312,7 @@ async function listGateAuditResults(directory) {
|
|
|
4708
4312
|
continue;
|
|
4709
4313
|
}
|
|
4710
4314
|
try {
|
|
4711
|
-
const parsed = GateAuditResultV1Schema.parse(JSON.parse(await readFile2(
|
|
4315
|
+
const parsed = GateAuditResultV1Schema.parse(JSON.parse(await readFile2(path10.join(root, entry.name, "results.json"), "utf8")));
|
|
4712
4316
|
if (parsed.runId !== entry.name)
|
|
4713
4317
|
throw new Error("run id/path mismatch");
|
|
4714
4318
|
results.push(parsed);
|
|
@@ -4741,7 +4345,7 @@ async function getProtectedEvaluationRunIds(directory) {
|
|
|
4741
4345
|
throw error2;
|
|
4742
4346
|
}
|
|
4743
4347
|
for (const filename of files.filter((value) => value.endsWith(".json")).sort()) {
|
|
4744
|
-
const decision = PromotionDecisionV1Schema.parse(JSON.parse(await readFile2(
|
|
4348
|
+
const decision = PromotionDecisionV1Schema.parse(JSON.parse(await readFile2(path10.join(decisionsDirectory, filename), "utf8")));
|
|
4745
4349
|
protectedIds.add(decision.runId);
|
|
4746
4350
|
protectedIds.add(decision.lineage.baselineRunId);
|
|
4747
4351
|
if (decision.lineage.historicalBestRunId !== "unavailable") {
|
|
@@ -4759,10 +4363,10 @@ function validId(value) {
|
|
|
4759
4363
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,159}$/.test(value);
|
|
4760
4364
|
}
|
|
4761
4365
|
async function evaluationRunCandidates(directory) {
|
|
4762
|
-
const root =
|
|
4366
|
+
const root = path11.join(directory, ".swarm", "evolution", "runs");
|
|
4763
4367
|
let entries = [];
|
|
4764
4368
|
try {
|
|
4765
|
-
entries = await
|
|
4369
|
+
entries = await fs5.readdir(root, { withFileTypes: true });
|
|
4766
4370
|
} catch (error2) {
|
|
4767
4371
|
if (error2.code === "ENOENT") {
|
|
4768
4372
|
return { candidates: [], corrupt: [] };
|
|
@@ -4778,9 +4382,9 @@ async function evaluationRunCandidates(directory) {
|
|
|
4778
4382
|
if (!validId(id))
|
|
4779
4383
|
continue;
|
|
4780
4384
|
const artifact = { namespace: "evaluation-run", id };
|
|
4781
|
-
const target =
|
|
4385
|
+
const target = path11.join(root, entry.name);
|
|
4782
4386
|
try {
|
|
4783
|
-
const run = EvaluationRunV1Schema.parse(JSON.parse(await
|
|
4387
|
+
const run = EvaluationRunV1Schema.parse(JSON.parse(await fs5.readFile(target, "utf8")));
|
|
4784
4388
|
if (run.runId !== id)
|
|
4785
4389
|
throw new Error("run id/path mismatch");
|
|
4786
4390
|
candidates.push({
|
|
@@ -4796,10 +4400,10 @@ async function evaluationRunCandidates(directory) {
|
|
|
4796
4400
|
return { candidates, corrupt };
|
|
4797
4401
|
}
|
|
4798
4402
|
async function gateAuditCandidates(directory) {
|
|
4799
|
-
const root =
|
|
4403
|
+
const root = path11.join(directory, ".swarm", "evidence", "gate-audit");
|
|
4800
4404
|
let entries = [];
|
|
4801
4405
|
try {
|
|
4802
|
-
entries = await
|
|
4406
|
+
entries = await fs5.readdir(root, { withFileTypes: true });
|
|
4803
4407
|
} catch (error2) {
|
|
4804
4408
|
if (error2.code === "ENOENT") {
|
|
4805
4409
|
return { candidates: [], corrupt: [] };
|
|
@@ -4812,9 +4416,9 @@ async function gateAuditCandidates(directory) {
|
|
|
4812
4416
|
if (!entry.isDirectory() || !validId(entry.name))
|
|
4813
4417
|
continue;
|
|
4814
4418
|
const artifact = { namespace: "gate-audit", id: entry.name };
|
|
4815
|
-
const target =
|
|
4419
|
+
const target = path11.join(root, entry.name);
|
|
4816
4420
|
try {
|
|
4817
|
-
const result = GateAuditResultV1Schema.parse(JSON.parse(await
|
|
4421
|
+
const result = GateAuditResultV1Schema.parse(JSON.parse(await fs5.readFile(path11.join(target, "results.json"), "utf8")));
|
|
4818
4422
|
if (result.runId !== entry.name)
|
|
4819
4423
|
throw new Error("run id/path mismatch");
|
|
4820
4424
|
candidates.push({
|
|
@@ -4845,17 +4449,17 @@ async function protectedArtifacts(directory) {
|
|
|
4845
4449
|
}
|
|
4846
4450
|
async function removeCandidate(candidate) {
|
|
4847
4451
|
if (candidate.kind === "directory") {
|
|
4848
|
-
await
|
|
4452
|
+
await fs5.rm(candidate.target, { recursive: true, force: true });
|
|
4849
4453
|
return;
|
|
4850
4454
|
}
|
|
4851
|
-
await
|
|
4455
|
+
await fs5.unlink(candidate.target);
|
|
4852
4456
|
}
|
|
4853
4457
|
var _retentionInternals = {
|
|
4854
4458
|
protectedArtifacts,
|
|
4855
4459
|
removeCandidate
|
|
4856
4460
|
};
|
|
4857
4461
|
async function archiveEvaluationArtifacts(args) {
|
|
4858
|
-
return withEvidenceLock(args.directory,
|
|
4462
|
+
return withEvidenceLock(args.directory, path11.join("evolution", "retention-index"), "evaluation-retention", "archive", async () => {
|
|
4859
4463
|
const inventory = await retentionCandidates(args.directory);
|
|
4860
4464
|
let protectedKeys = await _retentionInternals.protectedArtifacts(args.directory);
|
|
4861
4465
|
const protectedRefs = inventory.candidates.filter((candidate) => protectedKeys.has(artifactKey(candidate.artifact))).map((candidate) => candidate.artifact);
|
|
@@ -4942,8 +4546,8 @@ function isWeakConfigContainerRoot(directory) {
|
|
|
4942
4546
|
const resolved = realpathSync2(directory);
|
|
4943
4547
|
return resolved === realpathSync2(os4.tmpdir()) || resolved === realpathSync2(os4.homedir());
|
|
4944
4548
|
} catch {
|
|
4945
|
-
const resolved =
|
|
4946
|
-
return resolved ===
|
|
4549
|
+
const resolved = path12.resolve(directory);
|
|
4550
|
+
return resolved === path12.resolve(os4.tmpdir()) || resolved === path12.resolve(os4.homedir());
|
|
4947
4551
|
}
|
|
4948
4552
|
}
|
|
4949
4553
|
function hasAccessibleProjectIndicator(directory, options = {}) {
|
|
@@ -4953,7 +4557,7 @@ function hasAccessibleProjectIndicator(directory, options = {}) {
|
|
|
4953
4557
|
continue;
|
|
4954
4558
|
}
|
|
4955
4559
|
try {
|
|
4956
|
-
const indicatorStat = statSync2(
|
|
4560
|
+
const indicatorStat = statSync2(path12.join(directory, indicator));
|
|
4957
4561
|
if (indicatorStat.isFile() || indicatorStat.isDirectory()) {
|
|
4958
4562
|
return true;
|
|
4959
4563
|
}
|
|
@@ -4975,14 +4579,14 @@ function validateProjectRoot(directory) {
|
|
|
4975
4579
|
if (depth >= MAX_DEPTH)
|
|
4976
4580
|
break;
|
|
4977
4581
|
depth++;
|
|
4978
|
-
const parent =
|
|
4582
|
+
const parent = path12.dirname(current);
|
|
4979
4583
|
if (parent === current)
|
|
4980
4584
|
break;
|
|
4981
|
-
if (
|
|
4585
|
+
if (path12.dirname(parent) === parent) {
|
|
4982
4586
|
current = parent;
|
|
4983
4587
|
continue;
|
|
4984
4588
|
}
|
|
4985
|
-
const parentSwarm =
|
|
4589
|
+
const parentSwarm = path12.join(parent, ".swarm");
|
|
4986
4590
|
try {
|
|
4987
4591
|
if (statSync2(parentSwarm).isDirectory()) {
|
|
4988
4592
|
if (hasAccessibleProjectIndicator(parent, {
|
|
@@ -5003,12 +4607,12 @@ function validateProjectRoot(directory) {
|
|
|
5003
4607
|
async function saveEvidence(directory, taskId, evidence) {
|
|
5004
4608
|
_internals10.validateProjectRoot(directory);
|
|
5005
4609
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
5006
|
-
const relativePath =
|
|
4610
|
+
const relativePath = path12.join("evidence", sanitizedTaskId, "evidence.json");
|
|
5007
4611
|
validateSwarmPath(directory, relativePath);
|
|
5008
4612
|
_internals10.validateEvidence(evidence);
|
|
5009
4613
|
return withEvidenceLock(directory, relativePath, "evidence-manager", sanitizedTaskId, async () => {
|
|
5010
4614
|
const evidencePath = validateSwarmPath(directory, relativePath);
|
|
5011
|
-
const evidenceDir =
|
|
4615
|
+
const evidenceDir = path12.dirname(evidencePath);
|
|
5012
4616
|
let bundle;
|
|
5013
4617
|
const existingContent = await readSwarmFileAsync(directory, relativePath);
|
|
5014
4618
|
if (existingContent !== null) {
|
|
@@ -5050,11 +4654,11 @@ async function saveEvidence(directory, taskId, evidence) {
|
|
|
5050
4654
|
if (bundleJson.length > EVIDENCE_MAX_JSON_BYTES) {
|
|
5051
4655
|
throw new Error(`Evidence bundle size (${bundleJson.length} bytes) exceeds maximum (${EVIDENCE_MAX_JSON_BYTES} bytes)`);
|
|
5052
4656
|
}
|
|
5053
|
-
|
|
5054
|
-
const tempPath =
|
|
4657
|
+
mkdirSync5(evidenceDir, { recursive: true });
|
|
4658
|
+
const tempPath = path12.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
5055
4659
|
try {
|
|
5056
4660
|
await bunWrite(tempPath, bundleJson);
|
|
5057
|
-
await
|
|
4661
|
+
await fs6.rename(tempPath, evidencePath);
|
|
5058
4662
|
} catch (error2) {
|
|
5059
4663
|
try {
|
|
5060
4664
|
rmSync(tempPath, { force: true });
|
|
@@ -5095,7 +4699,7 @@ function wrapFlatRetrospective(flatEntry, taskId) {
|
|
|
5095
4699
|
}
|
|
5096
4700
|
async function loadEvidence(directory, taskId) {
|
|
5097
4701
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
5098
|
-
const relativePath =
|
|
4702
|
+
const relativePath = path12.join("evidence", sanitizedTaskId, "evidence.json");
|
|
5099
4703
|
if (relativePath.length > 4096) {
|
|
5100
4704
|
return { status: "not_found" };
|
|
5101
4705
|
}
|
|
@@ -5116,12 +4720,12 @@ async function loadEvidence(directory, taskId) {
|
|
|
5116
4720
|
const validated = EvidenceBundleSchema.parse(wrappedBundle);
|
|
5117
4721
|
try {
|
|
5118
4722
|
await withEvidenceLock(directory, relativePath, "evidence-loader", sanitizedTaskId, async () => {
|
|
5119
|
-
const evidenceDir =
|
|
4723
|
+
const evidenceDir = path12.dirname(evidencePath);
|
|
5120
4724
|
const bundleJson = JSON.stringify(validated);
|
|
5121
|
-
const tempPath =
|
|
4725
|
+
const tempPath = path12.join(evidenceDir, `evidence.json.tmp.${Date.now()}.${process.pid}`);
|
|
5122
4726
|
try {
|
|
5123
4727
|
await bunWrite(tempPath, bundleJson);
|
|
5124
|
-
await
|
|
4728
|
+
await fs6.rename(tempPath, evidencePath);
|
|
5125
4729
|
} catch (writeError) {
|
|
5126
4730
|
try {
|
|
5127
4731
|
rmSync(tempPath, { force: true });
|
|
@@ -5157,13 +4761,13 @@ async function listEvidenceTaskIds(directory) {
|
|
|
5157
4761
|
}
|
|
5158
4762
|
let entries;
|
|
5159
4763
|
try {
|
|
5160
|
-
entries =
|
|
4764
|
+
entries = readdirSync2(evidenceBasePath);
|
|
5161
4765
|
} catch {
|
|
5162
4766
|
return [];
|
|
5163
4767
|
}
|
|
5164
4768
|
const taskIds = [];
|
|
5165
4769
|
for (const entry of entries) {
|
|
5166
|
-
const entryPath =
|
|
4770
|
+
const entryPath = path12.join(evidenceBasePath, entry);
|
|
5167
4771
|
try {
|
|
5168
4772
|
const stats = statSync2(entryPath);
|
|
5169
4773
|
if (!stats.isDirectory()) {
|
|
@@ -5181,7 +4785,7 @@ async function listEvidenceTaskIds(directory) {
|
|
|
5181
4785
|
}
|
|
5182
4786
|
async function deleteEvidence(directory, taskId) {
|
|
5183
4787
|
const sanitizedTaskId = sanitizeTaskId2(taskId);
|
|
5184
|
-
const relativePath =
|
|
4788
|
+
const relativePath = path12.join("evidence", sanitizedTaskId);
|
|
5185
4789
|
const evidenceDir = validateSwarmPath(directory, relativePath);
|
|
5186
4790
|
try {
|
|
5187
4791
|
statSync2(evidenceDir);
|
|
@@ -5197,10 +4801,10 @@ async function deleteEvidence(directory, taskId) {
|
|
|
5197
4801
|
}
|
|
5198
4802
|
}
|
|
5199
4803
|
async function checkRequirementCoverage(phase, directory) {
|
|
5200
|
-
const relativePath =
|
|
5201
|
-
const absolutePath =
|
|
4804
|
+
const relativePath = path12.join("evidence", `req-coverage-phase-${phase}.json`);
|
|
4805
|
+
const absolutePath = path12.resolve(directory, ".swarm", relativePath);
|
|
5202
4806
|
try {
|
|
5203
|
-
await
|
|
4807
|
+
await fs6.access(absolutePath);
|
|
5204
4808
|
return { exists: true, path: absolutePath };
|
|
5205
4809
|
} catch {
|
|
5206
4810
|
return { exists: false, path: absolutePath };
|
|
@@ -5399,4 +5003,4 @@ function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
|
|
|
5399
5003
|
return merged;
|
|
5400
5004
|
}
|
|
5401
5005
|
|
|
5402
|
-
export { getExecutor, init_executor, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, getDefaultBaseBranch, resetToRemoteBranch, resetToMainAfterMerge, initDurableStatusPath, getAllWorktreeMergeFailures, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, computeSpecHash,
|
|
5006
|
+
export { getExecutor, init_executor, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, getDefaultBaseBranch, resetToRemoteBranch, resetToMainAfterMerge, initDurableStatusPath, getAllWorktreeMergeFailures, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, computeSpecHash, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, IdentifierSchema, EvaluationTaskV1Schema, GateNameSchema, GateAuditManifestV1Schema, GateAuditResultV1Schema, canonicalJson, canonicalHash, computeTaskInputContentHash, computeTaskSetContentHash, computeManifestContentHash, resolveContainedExistingPath, admitEvaluationTask, saveTaskSetSnapshot, saveGateAuditResult, saveGateAuditManifest, readGateAuditManifest, readGateAuditResult, listGateAuditResults, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|