opencode-swarm 7.183.1 → 7.184.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/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
- package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
- package/dist/background/delegation-health.d.ts +1 -1
- package/dist/background/index.d.ts +1 -0
- package/dist/background/pr-event-delivery.d.ts +29 -14
- package/dist/background/pr-event-subscribers.d.ts +6 -5
- package/dist/background/pr-feedback-event-queue.d.ts +19 -2
- package/dist/background/pr-feedback-loop-runtime.d.ts +75 -0
- package/dist/background/pr-feedback-loop.d.ts +47 -12
- package/dist/cli/{coder-settlement-rserb0hm.js → coder-settlement-yvpewr52.js} +15 -12
- package/dist/cli/{config-doctor-r4fmzprn.js → config-doctor-qgwpvrbz.js} +7 -3
- package/dist/cli/{core-rzek3e2n.js → core-9qt388hq.js} +4 -4
- package/dist/cli/{curation-policy-g0jjwrg5.js → curation-policy-49f8jx48.js} +7 -6
- package/dist/cli/{curator-drift-a0r5krfx.js → curator-drift-wf0sm2te.js} +9 -9
- package/dist/cli/{curator-v84tfqcr.js → curator-ffz5pxgc.js} +40 -39
- package/dist/cli/curator-llm-factory-drs36v1q.js +75 -0
- package/dist/cli/{dispatch-p5zjg5e6.js → dispatch-cv89p2y1.js} +1 -1
- package/dist/cli/{evidence-summary-service-t4csk176.js → evidence-summary-service-8dfzjnxe.js} +21 -20
- package/dist/cli/{gate-evidence-3z04zprn.js → gate-evidence-aa5q30f4.js} +14 -7
- package/dist/cli/guardrail-explain-9g5y2gjm.js +76 -0
- package/dist/cli/{guardrail-log-cbzdh4qj.js → guardrail-log-0g51p94w.js} +8 -8
- package/dist/cli/{guardrail-reset-5e75rz44.js → guardrail-reset-dcbmt2wr.js} +40 -39
- package/dist/cli/{hive-promoter-f54nrhqn.js → hive-promoter-2g8nga0y.js} +40 -39
- package/dist/cli/{index-31e105jf.js → index-046psajf.js} +80 -2
- package/dist/cli/{index-rhw6m7s9.js → index-0874kghs.js} +9 -3
- package/dist/cli/{index-6h81yvyc.js → index-131ay7x1.js} +3 -3
- package/dist/cli/{index-raws06d3.js → index-2c91cxdv.js} +4 -4
- package/dist/cli/{index-9f930sy8.js → index-32vszgv0.js} +3 -3
- package/dist/cli/{index-0bpr8vag.js → index-3n5zsp31.js} +2 -2
- package/dist/cli/{index-b26hje53.js → index-3skvygca.js} +5 -5
- package/dist/cli/{index-w81jmdkt.js → index-456awy1p.js} +2 -2
- package/dist/cli/{index-hb1zqz50.js → index-475bkab8.js} +2 -2
- package/dist/cli/{index-80qhm8aq.js → index-49ptc9xx.js} +10 -4
- package/dist/cli/{index-bsjwchw7.js → index-57swfxyp.js} +8 -8
- package/dist/cli/{index-y5hhkx1n.js → index-5ppx86w1.js} +38 -18
- package/dist/cli/{index-jb7ywm4w.js → index-6b427dy8.js} +6 -6
- package/dist/cli/{index-3qy7v4k1.js → index-6cvdk98y.js} +2 -2
- package/dist/cli/{index-hsvsz3mj.js → index-6g664sy8.js} +1 -1
- package/dist/cli/{index-8me6m6ew.js → index-76abqm8d.js} +2 -2
- package/dist/cli/{index-z425p51d.js → index-7hvy33gc.js} +271 -80
- package/dist/cli/{index-4xr4gtwd.js → index-7p4ym4q3.js} +1 -1
- package/dist/cli/{index-j8ty457v.js → index-aghd9kcx.js} +2 -2
- package/dist/cli/{index-h01p5ygy.js → index-ar2gsn6b.js} +8 -8
- package/dist/cli/{index-ak1dd17g.js → index-dbshccrr.js} +3 -3
- package/dist/cli/{index-908k7jhw.js → index-depfky1j.js} +158 -27
- package/dist/cli/{index-5t08vc6m.js → index-earq3mda.js} +1 -1
- package/dist/cli/{index-6dm6ettv.js → index-ejb7ejhd.js} +1 -1
- package/dist/cli/{index-3rb5gs4w.js → index-f0nj3d8z.js} +1 -1
- package/dist/cli/{index-qjzbs971.js → index-gsgy1psq.js} +4 -4
- package/dist/cli/{index-jk05g11c.js → index-jc8pcf0r.js} +7 -7
- package/dist/cli/{index-mt0s67hg.js → index-jkxef52n.js} +2 -2
- package/dist/cli/{index-wpn7ftw1.js → index-jvk95e6y.js} +2 -2
- package/dist/cli/{index-5cve9g2b.js → index-k53a69vb.js} +6 -6
- package/dist/cli/{index-1xnse449.js → index-mc72pk74.js} +1 -1
- package/dist/cli/{index-w196wb3a.js → index-mz4zz59c.js} +42 -41
- package/dist/cli/{index-4hnk9npg.js → index-n107956q.js} +5 -5
- package/dist/cli/{index-gaykghrm.js → index-qczxr0wh.js} +12 -10
- package/dist/cli/{index-nazzb4em.js → index-s7bseqza.js} +3220 -1893
- package/dist/cli/{index-dg3f2sqq.js → index-t7hmqgjy.js} +90 -141
- package/dist/cli/{index-08x7gsnf.js → index-w9hxzcz4.js} +2 -2
- package/dist/cli/{index-bqphhrwf.js → index-wfdx7yw2.js} +39 -15
- package/dist/cli/{index-xtysafzj.js → index-wsb0wbw6.js} +40 -39
- package/dist/cli/{index-nmh0s6dg.js → index-wy3zpwjy.js} +1 -1
- package/dist/cli/{index-tt705bap.js → index-x1gyqgze.js} +7 -7
- package/dist/cli/index-xf69xty9.js +105 -0
- package/dist/cli/{index-ydefv5fn.js → index-ya32wfkp.js} +4 -2
- package/dist/cli/{index-1hnknn34.js → index-yk0r42nr.js} +4 -4
- package/dist/cli/{index-77650pyg.js → index-zzvxj02e.js} +3 -3
- package/dist/cli/index.js +41 -40
- package/dist/cli/{knowledge-escalator-cs7fjgy0.js → knowledge-escalator-pt21q3eh.js} +14 -13
- package/dist/cli/{knowledge-events-262c1jg2.js → knowledge-events-hp96b55k.js} +12 -11
- package/dist/cli/{knowledge-link-8kcms7pj.js → knowledge-link-ap2274vm.js} +6 -5
- package/dist/cli/{knowledge-store-yjrkfens.js → knowledge-store-rnw1e7zp.js} +7 -6
- package/dist/cli/{knowledge-validator-t98cvpt5.js → knowledge-validator-xy7r6eyx.js} +9 -8
- package/dist/cli/{mcp-wj6143vj.js → mcp-3nd85kww.js} +15 -1
- package/dist/cli/mcp.d.ts +1 -0
- package/dist/cli/{model-preflight-tz4ar9ys.js → model-preflight-xe0chjad.js} +40 -39
- package/dist/cli/{pending-delegations-vvbkqe24.js → pending-delegations-2pdngdzq.js} +13 -12
- package/dist/cli/{pr-subscriptions-maeppcws.js → pr-subscriptions-hxxwszyr.js} +13 -12
- package/dist/cli/{pr-workflow-gate-en54gabv.js → pr-workflow-gate-m37ynr6h.js} +40 -39
- package/dist/cli/{runner-618229m8.js → runner-yz3ysehf.js} +7 -6
- package/dist/cli/{scan-cursor-sxwh1ax8.js → scan-cursor-be9hbq1v.js} +8 -7
- package/dist/cli/{schema-fb8vm14k.js → schema-j17hv98z.js} +3 -1
- package/dist/cli/{scope-persistence-sc2zhr15.js → scope-persistence-rmt56rma.js} +16 -15
- package/dist/cli/{server-7ksq9wr7.js → server-wzrb59mh.js} +40 -39
- package/dist/cli/{skill-generator-ccmq47cm.js → skill-generator-vsfya55z.js} +16 -15
- package/dist/cli/{snapshot-coordination-init-h8eg88w2.js → snapshot-coordination-init-pc8ykvh8.js} +40 -39
- package/dist/cli/{speckit-checkoff-vkqn317p.js → speckit-checkoff-j407j8ew.js} +4 -4
- package/dist/cli/{telemetry-vqyy5nha.js → telemetry-39zmx4fp.js} +2 -2
- package/dist/cli/{workspace-snapshot-t22m6gbd.js → workspace-snapshot-0q1n1xq5.js} +4 -4
- package/dist/cli/{worktree-collision-ownership-8hr7aycc.js → worktree-collision-ownership-je4fb407.js} +14 -13
- package/dist/cli/{worktree-isolation-gwq3y9tf.js → worktree-isolation-27rt584e.js} +40 -39
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/config/schema.d.ts +15 -0
- package/dist/context/role-filter.d.ts +2 -0
- package/dist/db/qa-gate-session-override.d.ts +12 -12
- package/dist/gate-evidence.d.ts +43 -1
- package/dist/hooks/final-context-accounting.d.ts +3 -2
- package/dist/hooks/guardrails/index.d.ts +13 -7
- package/dist/hooks/host-boundary.d.ts +10 -0
- package/dist/hooks/system-enhancer.d.ts +9 -2
- package/dist/hooks/system-guidance-carrier.d.ts +18 -0
- package/dist/index.js +520 -514
- package/dist/plan/ledger.d.ts +20 -4
- package/dist/plan/manager.d.ts +20 -2
- package/dist/services/config-doctor.d.ts +24 -0
- package/dist/services/injection-budget.d.ts +56 -8
- package/dist/session/hydration-ownership.d.ts +39 -12
- package/dist/session/snapshot-coordination-init.d.ts +11 -4
- package/dist/session/snapshot-reader.d.ts +9 -4
- package/dist/session/snapshot-writer.d.ts +2 -3
- package/dist/state.d.ts +35 -5
- package/dist/tools/phase-complete.d.ts +1 -0
- package/dist/tools/save-plan.d.ts +12 -0
- package/dist/tools/update-task-status.d.ts +1 -1
- package/dist/utils/project-boundary.d.ts +32 -1
- package/dist/workflow/coder-settlement.d.ts +8 -0
- package/dist/workflow/task-terminal.d.ts +2 -1
- package/dist/workflow/workflow-wal-schema.d.ts +2 -0
- package/opencode-swarm.schema.json +8 -0
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-xp4hgd8r.js +0 -74
- package/dist/cli/guardrail-explain-j2y5sj4d.js +0 -75
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
EvaluationRunV1Schema,
|
|
4
4
|
GateAuditResultV1Schema,
|
|
5
5
|
getProtectedEvaluationRunIds
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-ya32wfkp.js";
|
|
7
7
|
import {
|
|
8
8
|
readEffectiveSpecSync
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-7p4ym4q3.js";
|
|
10
10
|
import {
|
|
11
11
|
validateWorkspaceRoot
|
|
12
12
|
} from "./index-6b295cas.js";
|
|
@@ -16,20 +16,23 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
GitBinaryMissingError,
|
|
18
18
|
GitSpawnCwdError,
|
|
19
|
+
TASK_WORKFLOW_SCHEMA_MARKER,
|
|
20
|
+
deriveApplicableGateSet,
|
|
19
21
|
isGitBinaryMissing,
|
|
20
22
|
isSpawnCwdMissing,
|
|
21
23
|
isSpawnCwdUnreadable,
|
|
22
24
|
isValidTaskId,
|
|
25
|
+
readCurrentTaskDeclaredFiles,
|
|
23
26
|
readTaskEvidence,
|
|
24
27
|
readTaskEvidenceRaw,
|
|
25
28
|
sanitizeTaskId
|
|
26
|
-
} from "./index-
|
|
29
|
+
} from "./index-depfky1j.js";
|
|
27
30
|
import {
|
|
28
31
|
estimateCharsForTokens,
|
|
29
32
|
estimateTokens,
|
|
30
33
|
readSwarmFileAsync,
|
|
31
34
|
validateSwarmPath
|
|
32
|
-
} from "./index-
|
|
35
|
+
} from "./index-wy3zpwjy.js";
|
|
33
36
|
import {
|
|
34
37
|
bunHash,
|
|
35
38
|
bunWrite,
|
|
@@ -58,10 +61,10 @@ import {
|
|
|
58
61
|
replayFromLedgerWithStatus,
|
|
59
62
|
takeSnapshotEvent,
|
|
60
63
|
takeSnapshotWithRetry
|
|
61
|
-
} from "./index-
|
|
64
|
+
} from "./index-wfdx7yw2.js";
|
|
62
65
|
import {
|
|
63
66
|
appendCoreEventSync
|
|
64
|
-
} from "./index-
|
|
67
|
+
} from "./index-jvk95e6y.js";
|
|
65
68
|
import {
|
|
66
69
|
derivePlanId,
|
|
67
70
|
derivePlanIdentityHash,
|
|
@@ -69,23 +72,25 @@ import {
|
|
|
69
72
|
importCoordinationOnce,
|
|
70
73
|
listCoordinationStates,
|
|
71
74
|
transitionCoordinationState
|
|
72
|
-
} from "./index-
|
|
75
|
+
} from "./index-f0nj3d8z.js";
|
|
73
76
|
import {
|
|
74
77
|
DURABILITY_CLASSES,
|
|
75
78
|
getProjectDb,
|
|
76
79
|
projectDbExists,
|
|
77
80
|
withDurabilityClass
|
|
78
|
-
} from "./index-
|
|
81
|
+
} from "./index-w9hxzcz4.js";
|
|
79
82
|
import {
|
|
80
|
-
assertProjectRoot,
|
|
81
83
|
withEvidenceLock
|
|
82
|
-
} from "./index-
|
|
84
|
+
} from "./index-xf69xty9.js";
|
|
85
|
+
import {
|
|
86
|
+
assertProjectRoot
|
|
87
|
+
} from "./index-t7hmqgjy.js";
|
|
83
88
|
import {
|
|
84
89
|
emit
|
|
85
|
-
} from "./index-
|
|
90
|
+
} from "./index-x1gyqgze.js";
|
|
86
91
|
import {
|
|
87
92
|
canonicalRootKeyFresh
|
|
88
|
-
} from "./index-
|
|
93
|
+
} from "./index-mc72pk74.js";
|
|
89
94
|
import {
|
|
90
95
|
tryAcquireLock
|
|
91
96
|
} from "./index-ejhxgyvm.js";
|
|
@@ -93,7 +98,7 @@ import {
|
|
|
93
98
|
atomicWriteSwarmFileSync,
|
|
94
99
|
invalidateCachedArtifact,
|
|
95
100
|
readCachedParsedFile
|
|
96
|
-
} from "./index-
|
|
101
|
+
} from "./index-k53a69vb.js";
|
|
97
102
|
import {
|
|
98
103
|
PlanSchema,
|
|
99
104
|
normalizeCurrentPhaseInPlace,
|
|
@@ -1723,6 +1728,13 @@ class PlanConcurrentModificationError extends Error {
|
|
|
1723
1728
|
}
|
|
1724
1729
|
}
|
|
1725
1730
|
|
|
1731
|
+
class PlanRecoverySupersededError extends Error {
|
|
1732
|
+
constructor(message = "plan recovery superseded by a newer generation") {
|
|
1733
|
+
super(message);
|
|
1734
|
+
this.name = "PlanRecoverySupersededError";
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1726
1738
|
class PlanTaskRemovalNotAcknowledgedError extends Error {
|
|
1727
1739
|
missingTasks;
|
|
1728
1740
|
constructor(missingTasks) {
|
|
@@ -1742,6 +1754,7 @@ var _internals5 = {
|
|
|
1742
1754
|
readPlanJsonUtf8,
|
|
1743
1755
|
readPlanFileUtf8,
|
|
1744
1756
|
verifyWrittenPlanJson,
|
|
1757
|
+
writeRebuildPlanMarkdown,
|
|
1745
1758
|
ledgerExists,
|
|
1746
1759
|
replayFromLedger,
|
|
1747
1760
|
loadLastApprovedPlan,
|
|
@@ -1825,7 +1838,8 @@ async function retryCasWithBackoff(directory, eventInput, options) {
|
|
|
1825
1838
|
try {
|
|
1826
1839
|
return await appendLedgerEvent(directory, eventInput, {
|
|
1827
1840
|
expectedHash: currentExpected,
|
|
1828
|
-
planHashAfter: options.planHashAfter
|
|
1841
|
+
planHashAfter: options.planHashAfter,
|
|
1842
|
+
preCommitCheck: options.preCommitCheck
|
|
1829
1843
|
});
|
|
1830
1844
|
} catch (error2) {
|
|
1831
1845
|
if (!(error2 instanceof LedgerStaleWriterError) || attempt >= maxRetries) {
|
|
@@ -1882,7 +1896,7 @@ async function getLatestLedgerHash(directory) {
|
|
|
1882
1896
|
return "";
|
|
1883
1897
|
}
|
|
1884
1898
|
}
|
|
1885
|
-
async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
1899
|
+
async function surfaceLedgerStaleIfPersisted(directory, plan, options) {
|
|
1886
1900
|
const resolvedWorkspace = canonicalRootKeyFresh(directory);
|
|
1887
1901
|
if (!ledgerStaleWorkspaces.has(resolvedWorkspace)) {
|
|
1888
1902
|
return plan;
|
|
@@ -1891,10 +1905,14 @@ async function surfaceLedgerStaleIfPersisted(directory, plan) {
|
|
|
1891
1905
|
const planHash = computePlanLedgerHash(plan);
|
|
1892
1906
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
1893
1907
|
if (ledgerHash !== "" && planHash === ledgerHash) {
|
|
1908
|
+
options?.preCommitCheck?.();
|
|
1894
1909
|
ledgerStaleWorkspaces.delete(resolvedWorkspace);
|
|
1895
1910
|
return plan;
|
|
1896
1911
|
}
|
|
1897
|
-
} catch {
|
|
1912
|
+
} catch (error2) {
|
|
1913
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
1914
|
+
throw error2;
|
|
1915
|
+
}
|
|
1898
1916
|
plan._ledgerReplayStale = true;
|
|
1899
1917
|
if (typeof plan._ledgerReplayStaleReason !== "string" || plan._ledgerReplayStaleReason.length === 0) {
|
|
1900
1918
|
plan._ledgerReplayStaleReason = "plan.json still hash-mismatches the ledger after a startup ledger-replay failure (replay could not be applied and no critic-approved snapshot was available). Run /swarm reset-session if this persists.";
|
|
@@ -1968,7 +1986,7 @@ async function isPlanMdInSync(directory, plan, cache) {
|
|
|
1968
1986
|
}
|
|
1969
1987
|
return false;
|
|
1970
1988
|
}
|
|
1971
|
-
async function regeneratePlanMarkdown(directory, plan) {
|
|
1989
|
+
async function regeneratePlanMarkdown(directory, plan, options) {
|
|
1972
1990
|
assertProjectRoot(directory);
|
|
1973
1991
|
const swarmDir = path3.resolve(directory, ".swarm");
|
|
1974
1992
|
const contentHash = computePlanContentHash(plan);
|
|
@@ -1979,6 +1997,7 @@ ${markdown}`;
|
|
|
1979
1997
|
const mdTempPath = path3.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
1980
1998
|
try {
|
|
1981
1999
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
2000
|
+
options?.preCommitCheck?.();
|
|
1982
2001
|
renameSync2(mdTempPath, mdPath);
|
|
1983
2002
|
} finally {
|
|
1984
2003
|
try {
|
|
@@ -1987,8 +2006,10 @@ ${markdown}`;
|
|
|
1987
2006
|
}
|
|
1988
2007
|
invalidateCachedArtifact(mdPath);
|
|
1989
2008
|
}
|
|
1990
|
-
async function loadPlan(directory, cache) {
|
|
2009
|
+
async function loadPlan(directory, cache, options) {
|
|
2010
|
+
options?.preCommitCheck?.();
|
|
1991
2011
|
let planJsonContent = null;
|
|
2012
|
+
let claimedStartupWorkspace = null;
|
|
1992
2013
|
try {
|
|
1993
2014
|
planJsonContent = await _internals5.readPlanJsonUtf8(directory);
|
|
1994
2015
|
} catch (error2) {
|
|
@@ -2006,8 +2027,12 @@ async function loadPlan(directory, cache) {
|
|
|
2006
2027
|
const inSync = await isPlanMdInSync(directory, validated, cache);
|
|
2007
2028
|
if (!inSync) {
|
|
2008
2029
|
try {
|
|
2009
|
-
await _internals5.regeneratePlanMarkdown(directory, validated
|
|
2030
|
+
await _internals5.regeneratePlanMarkdown(directory, validated, {
|
|
2031
|
+
preCommitCheck: options?.preCommitCheck
|
|
2032
|
+
});
|
|
2010
2033
|
} catch (regenError) {
|
|
2034
|
+
if (regenError instanceof PlanRecoverySupersededError)
|
|
2035
|
+
throw regenError;
|
|
2011
2036
|
warn(`Failed to regenerate plan.md: ${regenError instanceof Error ? regenError.message : String(regenError)}. Proceeding with plan.json only.`);
|
|
2012
2037
|
}
|
|
2013
2038
|
}
|
|
@@ -2016,7 +2041,9 @@ async function loadPlan(directory, cache) {
|
|
|
2016
2041
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
2017
2042
|
const resolvedWorkspace = canonicalRootKeyFresh(directory);
|
|
2018
2043
|
if (!startupLedgerCheckedWorkspaces.has(resolvedWorkspace)) {
|
|
2044
|
+
options?.preCommitCheck?.();
|
|
2019
2045
|
startupLedgerCheckedWorkspaces.add(resolvedWorkspace);
|
|
2046
|
+
claimedStartupWorkspace = resolvedWorkspace;
|
|
2020
2047
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
2021
2048
|
const currentPlanId = derivePlanId(validated);
|
|
2022
2049
|
const ledgerEvents = await readLedgerEvents(directory);
|
|
@@ -2026,46 +2053,60 @@ async function loadPlan(directory, cache) {
|
|
|
2026
2053
|
} else {
|
|
2027
2054
|
warn("[loadPlan] plan.json is stale (hash mismatch with ledger) \u2014 rebuilding from ledger. If this recurs, run /swarm reset-session to clear stale session state.");
|
|
2028
2055
|
try {
|
|
2029
|
-
const { plan: rebuilt, truncated } = await replayFromLedgerWithStatus(directory
|
|
2056
|
+
const { plan: rebuilt, truncated } = await replayFromLedgerWithStatus(directory, {
|
|
2057
|
+
preCommitCheck: options?.preCommitCheck
|
|
2058
|
+
});
|
|
2030
2059
|
if (truncated) {
|
|
2031
2060
|
const runtimeStale = validated;
|
|
2032
2061
|
runtimeStale._ledgerReplayStale = true;
|
|
2033
2062
|
runtimeStale._ledgerReplayStaleReason = "Ledger truncated: a malformed line stopped replay before the tail, so the ledger could only be reconstructed up to the corruption. plan.json was PRESERVED (not rolled back to the prefix-only projection) and the corrupted suffix was quarantined to .swarm/plan-ledger.quarantine.*. Verify state, then run /swarm reset-session if this persists.";
|
|
2063
|
+
options?.preCommitCheck?.();
|
|
2034
2064
|
ledgerStaleWorkspaces.add(resolvedWorkspace);
|
|
2035
2065
|
criticalWarn("[loadPlan] Ledger truncated (poison line detected) \u2014 preserving plan.json instead of rolling back to the prefix-only ledger projection. Durable post-poison events remain in plan.json. Corrupted suffix quarantined to .swarm/plan-ledger.quarantine.*. Run /swarm reset-session after verifying state if this persists.");
|
|
2036
2066
|
return runtimeStale;
|
|
2037
2067
|
}
|
|
2038
2068
|
if (rebuilt) {
|
|
2039
2069
|
await rebuildPlan(directory, rebuilt, {
|
|
2040
|
-
reason: "ledger_hash_mismatch_recovery"
|
|
2070
|
+
reason: "ledger_hash_mismatch_recovery",
|
|
2071
|
+
preCommitCheck: options?.preCommitCheck
|
|
2041
2072
|
});
|
|
2042
2073
|
warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at .swarm/plan-export/SWARM_PLAN.md if it exists.");
|
|
2043
2074
|
return rebuilt;
|
|
2044
2075
|
}
|
|
2045
2076
|
} catch (replayError) {
|
|
2077
|
+
if (replayError instanceof PlanRecoverySupersededError)
|
|
2078
|
+
throw replayError;
|
|
2046
2079
|
try {
|
|
2047
2080
|
const approved = await loadLastApprovedPlan(directory, currentPlanId);
|
|
2048
2081
|
if (approved) {
|
|
2049
2082
|
await rebuildPlan(directory, approved.plan, {
|
|
2050
|
-
reason: "approved_snapshot_fallback"
|
|
2083
|
+
reason: "approved_snapshot_fallback",
|
|
2084
|
+
preCommitCheck: options?.preCommitCheck
|
|
2051
2085
|
});
|
|
2052
2086
|
try {
|
|
2053
2087
|
await takeSnapshotEvent(directory, approved.plan, {
|
|
2054
2088
|
source: "recovery_from_approved_snapshot",
|
|
2055
|
-
approvalMetadata: approved.approval
|
|
2089
|
+
approvalMetadata: approved.approval,
|
|
2090
|
+
preCommitCheck: options?.preCommitCheck
|
|
2056
2091
|
});
|
|
2057
2092
|
} catch (healError) {
|
|
2093
|
+
if (healError instanceof PlanRecoverySupersededError)
|
|
2094
|
+
throw healError;
|
|
2058
2095
|
warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
|
|
2059
2096
|
}
|
|
2060
2097
|
const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
|
|
2061
2098
|
warn(`[loadPlan] Ledger replay failed (${replayError instanceof Error ? replayError.message : String(replayError)}) \u2014 recovered from critic-approved snapshot seq=${approved.seq} (approval phase=${approvedPhase ?? "unknown"}, timestamp=${approved.timestamp}). This may roll the plan back to an earlier phase \u2014 verify before continuing.`);
|
|
2062
2099
|
return approved.plan;
|
|
2063
2100
|
}
|
|
2064
|
-
} catch {
|
|
2101
|
+
} catch (recoveryError) {
|
|
2102
|
+
if (recoveryError instanceof PlanRecoverySupersededError)
|
|
2103
|
+
throw recoveryError;
|
|
2104
|
+
}
|
|
2065
2105
|
{
|
|
2066
2106
|
const runtimeStale = validated;
|
|
2067
2107
|
runtimeStale._ledgerReplayStale = true;
|
|
2068
2108
|
runtimeStale._ledgerReplayStaleReason = `Ledger replay failed during hash-mismatch rebuild and no approved snapshot was available: ${replayError instanceof Error ? replayError.message : String(replayError)}`;
|
|
2109
|
+
options?.preCommitCheck?.();
|
|
2069
2110
|
ledgerStaleWorkspaces.add(resolvedWorkspace);
|
|
2070
2111
|
}
|
|
2071
2112
|
warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check .swarm/plan-export/SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
|
|
@@ -2090,7 +2131,7 @@ async function loadPlan(directory, cache) {
|
|
|
2090
2131
|
assertProjectRoot(directory);
|
|
2091
2132
|
const diffInfo = computeSpecDiff(directory);
|
|
2092
2133
|
const specStalenessPath = path3.join(directory, ".swarm", "spec-staleness.json");
|
|
2093
|
-
await
|
|
2134
|
+
await commitAsyncPreparedFile(specStalenessPath, JSON.stringify({
|
|
2094
2135
|
type: "spec_stale_detected",
|
|
2095
2136
|
timestamp: new Date().toISOString(),
|
|
2096
2137
|
phase: validated.current_phase ?? 1,
|
|
@@ -2100,9 +2141,12 @@ async function loadPlan(directory, cache) {
|
|
|
2100
2141
|
planTitle: validated.title,
|
|
2101
2142
|
diff: diffInfo?.diff ?? null,
|
|
2102
2143
|
changedSections: diffInfo?.changedSections ?? []
|
|
2103
|
-
}, null, 2), "
|
|
2144
|
+
}, null, 2), options?.preCommitCheck, "spec-staleness");
|
|
2104
2145
|
invalidateCachedArtifact(specStalenessPath);
|
|
2105
|
-
} catch {
|
|
2146
|
+
} catch (error2) {
|
|
2147
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2148
|
+
throw error2;
|
|
2149
|
+
}
|
|
2106
2150
|
try {
|
|
2107
2151
|
assertProjectRoot(directory);
|
|
2108
2152
|
const event = {
|
|
@@ -2114,22 +2158,35 @@ async function loadPlan(directory, cache) {
|
|
|
2114
2158
|
reason: staleResult.reason ?? "unknown",
|
|
2115
2159
|
planTitle: validated.title
|
|
2116
2160
|
};
|
|
2161
|
+
options?.preCommitCheck?.();
|
|
2117
2162
|
appendCoreEventSync(directory, { ...event });
|
|
2118
|
-
} catch {
|
|
2163
|
+
} catch (error2) {
|
|
2164
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2165
|
+
throw error2;
|
|
2166
|
+
}
|
|
2119
2167
|
}
|
|
2120
2168
|
}
|
|
2121
2169
|
}
|
|
2122
|
-
return await surfaceLedgerStaleIfPersisted(directory, validated);
|
|
2170
|
+
return await surfaceLedgerStaleIfPersisted(directory, validated, options);
|
|
2123
2171
|
}
|
|
2124
2172
|
} catch (error2) {
|
|
2173
|
+
if (error2 instanceof PlanRecoverySupersededError) {
|
|
2174
|
+
if (claimedStartupWorkspace !== null) {
|
|
2175
|
+
startupLedgerCheckedWorkspaces.delete(claimedStartupWorkspace);
|
|
2176
|
+
}
|
|
2177
|
+
throw error2;
|
|
2178
|
+
}
|
|
2125
2179
|
warn(`[loadPlan] plan.json validation failed: ${error2 instanceof Error ? error2.message : String(error2)}. Attempting rebuild from ledger. If rebuild fails, check .swarm/plan-export/SWARM_PLAN.md for a checkpoint.`);
|
|
2126
2180
|
let rawPlanId = null;
|
|
2181
|
+
let rawPlanJsonParseFailed = false;
|
|
2127
2182
|
try {
|
|
2128
2183
|
const rawParsed = JSON.parse(planJsonContent);
|
|
2129
2184
|
if (typeof rawParsed?.swarm === "string" && typeof rawParsed?.title === "string") {
|
|
2130
2185
|
rawPlanId = derivePlanId(rawParsed);
|
|
2131
2186
|
}
|
|
2132
|
-
} catch {
|
|
2187
|
+
} catch {
|
|
2188
|
+
rawPlanJsonParseFailed = true;
|
|
2189
|
+
}
|
|
2133
2190
|
if (await _internals5.ledgerExists(directory)) {
|
|
2134
2191
|
const catchIntegrity = await _internals5.readLedgerEventsWithIntegrity(directory);
|
|
2135
2192
|
const ledgerEventsForCatch = catchIntegrity.events;
|
|
@@ -2137,16 +2194,21 @@ async function loadPlan(directory, cache) {
|
|
|
2137
2194
|
const identityMatch = rawPlanId === null || catchFirstEvent === null || catchFirstEvent.plan_id === rawPlanId;
|
|
2138
2195
|
if (!identityMatch) {
|
|
2139
2196
|
warn(`[loadPlan] Ledger identity mismatch in validation-failure path (ledger: ${catchFirstEvent?.plan_id}, plan: ${rawPlanId}) \u2014 skipping ledger rebuild (migration detected).`);
|
|
2140
|
-
} else if (catchFirstEvent !== null && rawPlanId !== null) {
|
|
2197
|
+
} else if (catchFirstEvent !== null && (rawPlanId !== null || rawPlanJsonParseFailed && !catchIntegrity.truncated)) {
|
|
2141
2198
|
let rebuilt = null;
|
|
2142
2199
|
try {
|
|
2143
|
-
rebuilt = await _internals5.replayFromLedger(directory
|
|
2200
|
+
rebuilt = await _internals5.replayFromLedger(directory, {
|
|
2201
|
+
preCommitCheck: options?.preCommitCheck
|
|
2202
|
+
});
|
|
2144
2203
|
} catch (replayError) {
|
|
2204
|
+
if (replayError instanceof PlanRecoverySupersededError)
|
|
2205
|
+
throw replayError;
|
|
2145
2206
|
warn(`[loadPlan] Ledger replay threw in validation-failure path: ${replayError instanceof Error ? replayError.message : String(replayError)}. Falling back to critic-approved snapshot before legacy migration.`);
|
|
2146
2207
|
}
|
|
2147
2208
|
if (rebuilt) {
|
|
2148
2209
|
await rebuildPlan(directory, rebuilt, {
|
|
2149
|
-
reason: "validation_failure_recovery"
|
|
2210
|
+
reason: "validation_failure_recovery",
|
|
2211
|
+
preCommitCheck: options?.preCommitCheck
|
|
2150
2212
|
});
|
|
2151
2213
|
warn("[loadPlan] Rebuilt plan from ledger after validation failure. Projection was stale.");
|
|
2152
2214
|
return rebuilt;
|
|
@@ -2154,7 +2216,7 @@ async function loadPlan(directory, cache) {
|
|
|
2154
2216
|
try {
|
|
2155
2217
|
const approved = await _internals5.loadLastApprovedPlan(directory, catchFirstEvent.plan_id);
|
|
2156
2218
|
if (approved) {
|
|
2157
|
-
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, approved.plan, "load_plan_recovery_from_approved_snapshot", "restore from critic-approved snapshot");
|
|
2219
|
+
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, approved.plan, "load_plan_recovery_from_approved_snapshot", "restore from critic-approved snapshot", { preCommitCheck: options?.preCommitCheck });
|
|
2158
2220
|
if (removedCount > 0) {
|
|
2159
2221
|
approved.plan._midLoadRemovals = {
|
|
2160
2222
|
count: removedCount,
|
|
@@ -2164,15 +2226,20 @@ async function loadPlan(directory, cache) {
|
|
|
2164
2226
|
try {
|
|
2165
2227
|
await takeSnapshotEvent(directory, approved.plan, {
|
|
2166
2228
|
source: "recovery_from_approved_snapshot",
|
|
2167
|
-
approvalMetadata: approved.approval
|
|
2229
|
+
approvalMetadata: approved.approval,
|
|
2230
|
+
preCommitCheck: options?.preCommitCheck
|
|
2168
2231
|
});
|
|
2169
2232
|
} catch (healError) {
|
|
2233
|
+
if (healError instanceof PlanRecoverySupersededError)
|
|
2234
|
+
throw healError;
|
|
2170
2235
|
warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
|
|
2171
2236
|
}
|
|
2172
2237
|
warn(`[loadPlan] Recovered from critic-approved snapshot seq=${approved.seq} after validation failure with exhausted ledger replay (approved snapshot beats lossy Markdown migration).`);
|
|
2173
2238
|
return approved.plan;
|
|
2174
2239
|
}
|
|
2175
2240
|
} catch (approvedError) {
|
|
2241
|
+
if (approvedError instanceof PlanRecoverySupersededError)
|
|
2242
|
+
throw approvedError;
|
|
2176
2243
|
warn(`[loadPlan] Approved-snapshot recovery failed in validation-failure path: ${approvedError instanceof Error ? approvedError.message : String(approvedError)}`);
|
|
2177
2244
|
}
|
|
2178
2245
|
}
|
|
@@ -2180,14 +2247,14 @@ async function loadPlan(directory, cache) {
|
|
|
2180
2247
|
const planMdContent2 = await readSwarmFileAsync(directory, "plan.md", cache);
|
|
2181
2248
|
if (planMdContent2 !== null) {
|
|
2182
2249
|
const migrated = migrateLegacyPlan(planMdContent2);
|
|
2183
|
-
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, migrated, "load_plan_migration_from_md", "migrate legacy plan.md to plan.json");
|
|
2250
|
+
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, migrated, "load_plan_migration_from_md", "migrate legacy plan.md to plan.json", { preCommitCheck: options?.preCommitCheck });
|
|
2184
2251
|
if (removedCount > 0) {
|
|
2185
2252
|
migrated._midLoadRemovals = {
|
|
2186
2253
|
count: removedCount,
|
|
2187
2254
|
source: "load_plan_migration_from_md"
|
|
2188
2255
|
};
|
|
2189
2256
|
}
|
|
2190
|
-
await appendMigrationProvenanceEvent(directory, migrated);
|
|
2257
|
+
await appendMigrationProvenanceEvent(directory, migrated, options?.preCommitCheck);
|
|
2191
2258
|
return migrated;
|
|
2192
2259
|
}
|
|
2193
2260
|
}
|
|
@@ -2210,12 +2277,16 @@ async function loadPlan(directory, cache) {
|
|
|
2210
2277
|
try {
|
|
2211
2278
|
let rebuilt = null;
|
|
2212
2279
|
try {
|
|
2213
|
-
rebuilt = await _internals5.replayFromLedger(directory
|
|
2280
|
+
rebuilt = await _internals5.replayFromLedger(directory, {
|
|
2281
|
+
preCommitCheck: options?.preCommitCheck
|
|
2282
|
+
});
|
|
2214
2283
|
} catch (replayError) {
|
|
2284
|
+
if (replayError instanceof PlanRecoverySupersededError)
|
|
2285
|
+
throw replayError;
|
|
2215
2286
|
warn(`[loadPlan] Ledger replay threw in missing-projection path: ${replayError instanceof Error ? replayError.message : String(replayError)}. Falling back to critic-approved snapshot before legacy migration.`);
|
|
2216
2287
|
}
|
|
2217
2288
|
if (rebuilt) {
|
|
2218
|
-
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, rebuilt, "load_plan_rebuild_from_ledger", "rebuild plan from ledger replay");
|
|
2289
|
+
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, rebuilt, "load_plan_rebuild_from_ledger", "rebuild plan from ledger replay", { preCommitCheck: options?.preCommitCheck });
|
|
2219
2290
|
if (removedCount > 0) {
|
|
2220
2291
|
rebuilt._midLoadRemovals = {
|
|
2221
2292
|
count: removedCount,
|
|
@@ -2236,7 +2307,7 @@ async function loadPlan(directory, cache) {
|
|
|
2236
2307
|
if (approved) {
|
|
2237
2308
|
const approvedPhase = approved.approval && typeof approved.approval === "object" && "phase" in approved.approval ? approved.approval.phase : undefined;
|
|
2238
2309
|
warn(`[loadPlan] Ledger replay returned no plan \u2014 recovered from critic-approved snapshot seq=${approved.seq} timestamp=${approved.timestamp} (approval phase=${approvedPhase ?? "unknown"}). This may roll the plan back to an earlier phase \u2014 verify before continuing.`);
|
|
2239
|
-
const { removedCount: snapshotRemovedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, approved.plan, "load_plan_recovery_from_approved_snapshot", "restore from critic-approved snapshot");
|
|
2310
|
+
const { removedCount: snapshotRemovedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, approved.plan, "load_plan_recovery_from_approved_snapshot", "restore from critic-approved snapshot", { preCommitCheck: options?.preCommitCheck });
|
|
2240
2311
|
if (snapshotRemovedCount > 0) {
|
|
2241
2312
|
approved.plan._midLoadRemovals = {
|
|
2242
2313
|
count: snapshotRemovedCount,
|
|
@@ -2246,14 +2317,19 @@ async function loadPlan(directory, cache) {
|
|
|
2246
2317
|
try {
|
|
2247
2318
|
await takeSnapshotEvent(directory, approved.plan, {
|
|
2248
2319
|
source: "recovery_from_approved_snapshot",
|
|
2249
|
-
approvalMetadata: approved.approval
|
|
2320
|
+
approvalMetadata: approved.approval,
|
|
2321
|
+
preCommitCheck: options?.preCommitCheck
|
|
2250
2322
|
});
|
|
2251
2323
|
} catch (healError) {
|
|
2324
|
+
if (healError instanceof PlanRecoverySupersededError)
|
|
2325
|
+
throw healError;
|
|
2252
2326
|
warn(`[loadPlan] Recovery-heal snapshot append failed: ${healError instanceof Error ? healError.message : String(healError)}. Next loadPlan may re-enter recovery path.`);
|
|
2253
2327
|
}
|
|
2254
2328
|
return approved.plan;
|
|
2255
2329
|
}
|
|
2256
2330
|
} catch (recoveryError) {
|
|
2331
|
+
if (recoveryError instanceof PlanRecoverySupersededError)
|
|
2332
|
+
throw recoveryError;
|
|
2257
2333
|
warn(`[loadPlan] Approved-snapshot recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
2258
2334
|
}
|
|
2259
2335
|
} finally {
|
|
@@ -2264,14 +2340,14 @@ async function loadPlan(directory, cache) {
|
|
|
2264
2340
|
const planMdContent = await readSwarmFileAsync(directory, "plan.md", cache);
|
|
2265
2341
|
if (planMdContent !== null) {
|
|
2266
2342
|
const migrated = migrateLegacyPlan(planMdContent);
|
|
2267
|
-
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, migrated, "load_plan_migration_from_md", "migrate legacy plan.md to plan.json");
|
|
2343
|
+
const { removedCount } = await savePlanWithAutoAcknowledgedRemovals(directory, migrated, "load_plan_migration_from_md", "migrate legacy plan.md to plan.json", { preCommitCheck: options?.preCommitCheck });
|
|
2268
2344
|
if (removedCount > 0) {
|
|
2269
2345
|
migrated._midLoadRemovals = {
|
|
2270
2346
|
count: removedCount,
|
|
2271
2347
|
source: "load_plan_migration_from_md"
|
|
2272
2348
|
};
|
|
2273
2349
|
}
|
|
2274
|
-
await appendMigrationProvenanceEvent(directory, migrated);
|
|
2350
|
+
await appendMigrationProvenanceEvent(directory, migrated, options?.preCommitCheck);
|
|
2275
2351
|
return migrated;
|
|
2276
2352
|
}
|
|
2277
2353
|
return null;
|
|
@@ -2347,8 +2423,9 @@ async function verifyWrittenPlanJson(directory, expected) {
|
|
|
2347
2423
|
throw new PlanWriteVerificationError("PLAN_WRITE_VERIFICATION_FAILED: plan.json read-back content differs from the projected plan");
|
|
2348
2424
|
}
|
|
2349
2425
|
}
|
|
2350
|
-
async function appendMigrationProvenanceEvent(directory, plan) {
|
|
2426
|
+
async function appendMigrationProvenanceEvent(directory, plan, preCommitCheck) {
|
|
2351
2427
|
try {
|
|
2428
|
+
preCommitCheck?.();
|
|
2352
2429
|
await appendLedgerEvent(directory, {
|
|
2353
2430
|
event_type: "plan_rebuilt",
|
|
2354
2431
|
source: "load_plan_migration_from_md",
|
|
@@ -2358,8 +2435,10 @@ async function appendMigrationProvenanceEvent(directory, plan) {
|
|
|
2358
2435
|
phases_count: plan.phases.length,
|
|
2359
2436
|
tasks_count: plan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0)
|
|
2360
2437
|
}
|
|
2361
|
-
});
|
|
2438
|
+
}, { preCommitCheck });
|
|
2362
2439
|
} catch (error2) {
|
|
2440
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2441
|
+
throw error2;
|
|
2363
2442
|
warn(`[loadPlan] Markdown-migration provenance event append failed (plan remains migrated): ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
2364
2443
|
}
|
|
2365
2444
|
}
|
|
@@ -2420,7 +2499,9 @@ async function savePlan(directory, plan, options) {
|
|
|
2420
2499
|
if (!await ledgerExists(directory)) {
|
|
2421
2500
|
try {
|
|
2422
2501
|
options?.preCommitCheck?.();
|
|
2423
|
-
await initLedger(directory, planId, planHashForInit, validated
|
|
2502
|
+
await initLedger(directory, planId, planHashForInit, validated, {
|
|
2503
|
+
preCommitCheck: options?.preCommitCheck
|
|
2504
|
+
});
|
|
2424
2505
|
} catch (initErr) {
|
|
2425
2506
|
const msg = initErr instanceof Error ? initErr.message : String(initErr);
|
|
2426
2507
|
if (!/already initialized/i.test(msg)) {
|
|
@@ -2431,7 +2512,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2431
2512
|
const existingEvents = await readLedgerEvents(directory);
|
|
2432
2513
|
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
2433
2514
|
options?.preCommitCheck?.();
|
|
2434
|
-
await replacePlanLedgerWithRoot(directory, validated, "savePlan_identity_migration");
|
|
2515
|
+
await replacePlanLedgerWithRoot(directory, validated, "savePlan_identity_migration", { preCommitCheck: options?.preCommitCheck });
|
|
2435
2516
|
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived the prior exact history and committed a new root.`);
|
|
2436
2517
|
}
|
|
2437
2518
|
}
|
|
@@ -2530,6 +2611,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2530
2611
|
await retryCasWithBackoff(directory, eventInput, {
|
|
2531
2612
|
expectedHash: currentHash,
|
|
2532
2613
|
planHashAfter: hashAfter,
|
|
2614
|
+
preCommitCheck: options?.preCommitCheck,
|
|
2533
2615
|
verifyValid: async () => {
|
|
2534
2616
|
const onDisk = await _internals5.loadPlanJsonOnly(directory);
|
|
2535
2617
|
if (!onDisk)
|
|
@@ -2569,6 +2651,7 @@ async function savePlan(directory, plan, options) {
|
|
|
2569
2651
|
await retryCasWithBackoff(directory, eventInput, {
|
|
2570
2652
|
expectedHash: currentHash,
|
|
2571
2653
|
planHashAfter: hashAfter,
|
|
2654
|
+
preCommitCheck: options?.preCommitCheck,
|
|
2572
2655
|
verifyValid: async () => {
|
|
2573
2656
|
const onDisk = await _internals5.loadPlanJsonOnly(directory);
|
|
2574
2657
|
if (!onDisk)
|
|
@@ -2593,13 +2676,16 @@ async function savePlan(directory, plan, options) {
|
|
|
2593
2676
|
}
|
|
2594
2677
|
}
|
|
2595
2678
|
const ledgerStatusTaskIds = collectLedgerStatusTaskIds(await readLedgerEvents(directory));
|
|
2596
|
-
const replayedBeforeProjection = await _internals5.replayFromLedger(directory
|
|
2679
|
+
const replayedBeforeProjection = await _internals5.replayFromLedger(directory, {
|
|
2680
|
+
preCommitCheck: options?.preCommitCheck
|
|
2681
|
+
});
|
|
2597
2682
|
const projectionCandidate = replayedBeforeProjection ? mergeStatusesTakingPrecedence(validated, replayedBeforeProjection, ledgerStatusTaskIds) : validated;
|
|
2598
2683
|
if (replayedBeforeProjection && computePlanLedgerHash(replayedBeforeProjection) !== computePlanLedgerHash(projectionCandidate)) {
|
|
2599
2684
|
options?.preCommitCheck?.();
|
|
2600
2685
|
await takeSnapshotEvent(directory, projectionCandidate, {
|
|
2601
2686
|
planHashAfter: computePlanLedgerHash(projectionCandidate),
|
|
2602
|
-
source: "savePlan_structural_projection"
|
|
2687
|
+
source: "savePlan_structural_projection",
|
|
2688
|
+
preCommitCheck: options?.preCommitCheck
|
|
2603
2689
|
});
|
|
2604
2690
|
}
|
|
2605
2691
|
const projectedPlan = projectionCandidate;
|
|
@@ -2634,8 +2720,11 @@ async function savePlan(directory, plan, options) {
|
|
|
2634
2720
|
tasks_count: projectedPlan.phases.reduce((sum, p) => sum + p.tasks.length, 0),
|
|
2635
2721
|
in_progress: true
|
|
2636
2722
|
});
|
|
2637
|
-
await
|
|
2638
|
-
} catch {
|
|
2723
|
+
await commitAsyncPreparedFile(markerPath, inProgressMarker, options?.preCommitCheck, "plan-write-marker");
|
|
2724
|
+
} catch (error2) {
|
|
2725
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2726
|
+
throw error2;
|
|
2727
|
+
}
|
|
2639
2728
|
let mdWriteError;
|
|
2640
2729
|
try {
|
|
2641
2730
|
const contentHash = computePlanContentHash(projectedPlan);
|
|
@@ -2646,6 +2735,7 @@ ${markdown}`;
|
|
|
2646
2735
|
const mdTempPath = path3.join(swarmDir, `plan.md.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2647
2736
|
try {
|
|
2648
2737
|
await bunWrite(mdTempPath, markdownWithHash);
|
|
2738
|
+
options?.preCommitCheck?.();
|
|
2649
2739
|
renameSync2(mdTempPath, mdPath);
|
|
2650
2740
|
} finally {
|
|
2651
2741
|
try {
|
|
@@ -2654,6 +2744,8 @@ ${markdown}`;
|
|
|
2654
2744
|
}
|
|
2655
2745
|
invalidateCachedArtifact(mdPath);
|
|
2656
2746
|
} catch (mdError) {
|
|
2747
|
+
if (mdError instanceof PlanRecoverySupersededError)
|
|
2748
|
+
throw mdError;
|
|
2657
2749
|
const message = mdError instanceof Error ? mdError.message : String(mdError);
|
|
2658
2750
|
mdWriteError = message;
|
|
2659
2751
|
warn(`[savePlan] plan.md write failed (non-fatal, plan.json is authoritative): ${message}`);
|
|
@@ -2675,8 +2767,11 @@ ${markdown}`;
|
|
|
2675
2767
|
tasks_count: tasksCount,
|
|
2676
2768
|
in_progress: false
|
|
2677
2769
|
});
|
|
2678
|
-
await
|
|
2679
|
-
} catch {
|
|
2770
|
+
await commitAsyncPreparedFile(markerPath, marker, options?.preCommitCheck, "plan-write-marker");
|
|
2771
|
+
} catch (error2) {
|
|
2772
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2773
|
+
throw error2;
|
|
2774
|
+
}
|
|
2680
2775
|
if (currentPlan) {
|
|
2681
2776
|
try {
|
|
2682
2777
|
const oldIdentityHash = derivePlanIdentityHash(currentPlan);
|
|
@@ -2695,16 +2790,20 @@ ${markdown}`;
|
|
|
2695
2790
|
for (const [taskId, oldStatus] of oldStatuses) {
|
|
2696
2791
|
const newStatus = newStatuses.get(taskId);
|
|
2697
2792
|
if (oldStatus === "completed" && newStatus !== "completed") {
|
|
2793
|
+
options?.preCommitCheck?.();
|
|
2698
2794
|
advanceTaskCheckpointReceiptGeneration(directory, oldIdentityHash, taskId);
|
|
2699
2795
|
}
|
|
2700
2796
|
}
|
|
2701
2797
|
for (const [taskId, newStatus] of newStatuses) {
|
|
2702
2798
|
if (newStatus === "completed" && oldStatuses.get(taskId) !== "completed") {
|
|
2799
|
+
options?.preCommitCheck?.();
|
|
2703
2800
|
repairTaskCheckpointReceiptForCompletion(directory, newIdentityHash, taskId);
|
|
2704
2801
|
}
|
|
2705
2802
|
}
|
|
2706
2803
|
}
|
|
2707
2804
|
} catch (receiptError) {
|
|
2805
|
+
if (receiptError instanceof PlanRecoverySupersededError)
|
|
2806
|
+
throw receiptError;
|
|
2708
2807
|
warn(`[savePlan] task checkpoint receipt lifecycle sync failed (plan remains authoritative): ${receiptError instanceof Error ? receiptError.message : String(receiptError)}`);
|
|
2709
2808
|
}
|
|
2710
2809
|
}
|
|
@@ -2717,27 +2816,53 @@ ${markdown}`;
|
|
|
2717
2816
|
}
|
|
2718
2817
|
return { durability: "complete", degraded_surfaces: [] };
|
|
2719
2818
|
}
|
|
2819
|
+
async function commitAsyncPreparedFile(targetPath, content, preCommitCheck, tempLabel = "atomic") {
|
|
2820
|
+
const tempPath = `${targetPath}.${tempLabel}.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
2821
|
+
try {
|
|
2822
|
+
await bunWrite(tempPath, content);
|
|
2823
|
+
preCommitCheck?.();
|
|
2824
|
+
renameSync2(tempPath, targetPath);
|
|
2825
|
+
} catch (error2) {
|
|
2826
|
+
try {
|
|
2827
|
+
unlinkSync(tempPath);
|
|
2828
|
+
} catch {}
|
|
2829
|
+
throw error2;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
async function writeRebuildPlanMarkdown(tempPath, content) {
|
|
2833
|
+
await bunWrite(tempPath, content);
|
|
2834
|
+
}
|
|
2720
2835
|
async function rebuildPlan(directory, plan, options) {
|
|
2721
2836
|
assertProjectRoot(directory);
|
|
2722
|
-
const targetPlan = plan ?? await replayFromLedger(directory
|
|
2837
|
+
const targetPlan = plan ?? await replayFromLedger(directory, {
|
|
2838
|
+
preCommitCheck: options?.preCommitCheck
|
|
2839
|
+
});
|
|
2723
2840
|
if (!targetPlan)
|
|
2724
2841
|
return null;
|
|
2842
|
+
options?.preCommitCheck?.();
|
|
2725
2843
|
const swarmDir = path3.join(directory, ".swarm");
|
|
2726
2844
|
const planPath = path3.join(swarmDir, "plan.json");
|
|
2727
2845
|
const mdPath = path3.join(swarmDir, "plan.md");
|
|
2728
2846
|
mkdirSync2(swarmDir, { recursive: true });
|
|
2729
2847
|
const tempPlanPath = path3.join(swarmDir, `plan.json.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2730
|
-
{
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2848
|
+
try {
|
|
2849
|
+
{
|
|
2850
|
+
const fd = openSync(tempPlanPath, "w");
|
|
2851
|
+
try {
|
|
2852
|
+
writeFileSync(fd, JSON.stringify(targetPlan, null, 2), "utf8");
|
|
2853
|
+
fsyncSync(fd);
|
|
2854
|
+
} finally {
|
|
2855
|
+
closeSync(fd);
|
|
2856
|
+
}
|
|
2737
2857
|
}
|
|
2858
|
+
options?.preCommitCheck?.();
|
|
2859
|
+
renameSync2(tempPlanPath, planPath);
|
|
2860
|
+
invalidateCachedArtifact(planPath);
|
|
2861
|
+
} finally {
|
|
2862
|
+
try {
|
|
2863
|
+
unlinkSync(tempPlanPath);
|
|
2864
|
+
} catch {}
|
|
2738
2865
|
}
|
|
2739
|
-
renameSync2(tempPlanPath, planPath);
|
|
2740
|
-
invalidateCachedArtifact(planPath);
|
|
2741
2866
|
try {
|
|
2742
2867
|
const markerPath = path3.join(swarmDir, ".plan-write-marker");
|
|
2743
2868
|
const inProgressMarker = JSON.stringify({
|
|
@@ -2747,17 +2872,33 @@ async function rebuildPlan(directory, plan, options) {
|
|
|
2747
2872
|
tasks_count: targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0),
|
|
2748
2873
|
in_progress: true
|
|
2749
2874
|
});
|
|
2750
|
-
await
|
|
2751
|
-
} catch {
|
|
2875
|
+
await commitAsyncPreparedFile(markerPath, inProgressMarker, options?.preCommitCheck, "rebuild");
|
|
2876
|
+
} catch (error2) {
|
|
2877
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2878
|
+
throw error2;
|
|
2879
|
+
}
|
|
2880
|
+
let markdownWriteFailed = false;
|
|
2881
|
+
let markdownWriteError;
|
|
2882
|
+
let markerSupersededError;
|
|
2752
2883
|
try {
|
|
2753
2884
|
const contentHash = computePlanContentHash(targetPlan);
|
|
2754
2885
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
2755
2886
|
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
2756
2887
|
${markdown}`;
|
|
2757
2888
|
const tempMdPath = path3.join(swarmDir, `plan.md.rebuild.${Date.now()}.${Math.floor(Math.random() * 1e9)}`);
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2889
|
+
try {
|
|
2890
|
+
await _internals5.writeRebuildPlanMarkdown(tempMdPath, markdownWithHash);
|
|
2891
|
+
options?.preCommitCheck?.();
|
|
2892
|
+
renameSync2(tempMdPath, mdPath);
|
|
2893
|
+
invalidateCachedArtifact(mdPath);
|
|
2894
|
+
} finally {
|
|
2895
|
+
try {
|
|
2896
|
+
unlinkSync(tempMdPath);
|
|
2897
|
+
} catch {}
|
|
2898
|
+
}
|
|
2899
|
+
} catch (error2) {
|
|
2900
|
+
markdownWriteFailed = true;
|
|
2901
|
+
markdownWriteError = error2;
|
|
2761
2902
|
} finally {
|
|
2762
2903
|
try {
|
|
2763
2904
|
const markerPath = path3.join(swarmDir, ".plan-write-marker");
|
|
@@ -2769,10 +2910,23 @@ ${markdown}`;
|
|
|
2769
2910
|
tasks_count: tasksCount,
|
|
2770
2911
|
in_progress: false
|
|
2771
2912
|
});
|
|
2772
|
-
await
|
|
2773
|
-
} catch {
|
|
2913
|
+
await commitAsyncPreparedFile(markerPath, marker, options?.preCommitCheck, "rebuild");
|
|
2914
|
+
} catch (error2) {
|
|
2915
|
+
if (error2 instanceof PlanRecoverySupersededError) {
|
|
2916
|
+
markerSupersededError = error2;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
if (markerSupersededError)
|
|
2921
|
+
throw markerSupersededError;
|
|
2922
|
+
if (markdownWriteFailed) {
|
|
2923
|
+
if (markdownWriteError instanceof PlanRecoverySupersededError)
|
|
2924
|
+
throw markdownWriteError;
|
|
2925
|
+
const message = markdownWriteError instanceof Error ? markdownWriteError.message : String(markdownWriteError);
|
|
2926
|
+
warn(`[rebuildPlan] plan.md projection write failed (non-fatal; plan.json is authoritative): ${message}`.slice(0, 512));
|
|
2774
2927
|
}
|
|
2775
2928
|
try {
|
|
2929
|
+
options?.preCommitCheck?.();
|
|
2776
2930
|
const planId = derivePlanId(targetPlan);
|
|
2777
2931
|
const planHashAfter = computePlanLedgerHash(targetPlan);
|
|
2778
2932
|
await appendLedgerEvent(directory, {
|
|
@@ -2784,8 +2938,11 @@ ${markdown}`;
|
|
|
2784
2938
|
phases_count: targetPlan.phases.length,
|
|
2785
2939
|
tasks_count: targetPlan.phases.reduce((sum, p) => sum + p.tasks.length, 0)
|
|
2786
2940
|
}
|
|
2787
|
-
}, { planHashAfter });
|
|
2788
|
-
} catch {
|
|
2941
|
+
}, { planHashAfter, preCommitCheck: options?.preCommitCheck });
|
|
2942
|
+
} catch (error2) {
|
|
2943
|
+
if (error2 instanceof PlanRecoverySupersededError)
|
|
2944
|
+
throw error2;
|
|
2945
|
+
}
|
|
2789
2946
|
return targetPlan;
|
|
2790
2947
|
}
|
|
2791
2948
|
async function closePlanTerminalState(directory, plan, options) {
|
|
@@ -2940,7 +3097,7 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
2940
3097
|
if (status === "completed") {
|
|
2941
3098
|
try {
|
|
2942
3099
|
const completedTask = updatedPlan.phases.flatMap((phase) => phase.tasks).find((candidate) => candidate.id === taskId);
|
|
2943
|
-
const { maybePropagateSpeckitCheckoff } = await import("./speckit-checkoff-
|
|
3100
|
+
const { maybePropagateSpeckitCheckoff } = await import("./speckit-checkoff-j407j8ew.js");
|
|
2944
3101
|
await maybePropagateSpeckitCheckoff(directory, {
|
|
2945
3102
|
taskId,
|
|
2946
3103
|
frRefs: completedTask?.fr_refs ?? [],
|
|
@@ -2965,7 +3122,7 @@ async function updateTaskStatus(directory, taskId, status, options) {
|
|
|
2965
3122
|
break;
|
|
2966
3123
|
}
|
|
2967
3124
|
}
|
|
2968
|
-
const { readDeclaredScopeFilesFromBindings } = await import("./scope-persistence-
|
|
3125
|
+
const { readDeclaredScopeFilesFromBindings } = await import("./scope-persistence-rmt56rma.js");
|
|
2969
3126
|
const canonicalScope = readDeclaredScopeFilesFromBindings({
|
|
2970
3127
|
directory,
|
|
2971
3128
|
taskId,
|
|
@@ -4082,7 +4239,7 @@ function getDurableGateEvidenceStatus(evidence) {
|
|
|
4082
4239
|
invalid: false
|
|
4083
4240
|
};
|
|
4084
4241
|
}
|
|
4085
|
-
if (!Array.isArray(evidence.required_gates)
|
|
4242
|
+
if (!Array.isArray(evidence.required_gates)) {
|
|
4086
4243
|
return {
|
|
4087
4244
|
isComplete: false,
|
|
4088
4245
|
missingGates: ["required_gates"],
|
|
@@ -4090,10 +4247,10 @@ function getDurableGateEvidenceStatus(evidence) {
|
|
|
4090
4247
|
invalid: false
|
|
4091
4248
|
};
|
|
4092
4249
|
}
|
|
4093
|
-
const
|
|
4250
|
+
const derivedGates = deriveApplicableGateSet(evidence);
|
|
4094
4251
|
return {
|
|
4095
|
-
isComplete: missingGates.length === 0,
|
|
4096
|
-
missingGates,
|
|
4252
|
+
isComplete: derivedGates.missingGates.length === 0,
|
|
4253
|
+
missingGates: derivedGates.missingGates,
|
|
4097
4254
|
evidenceExists: true,
|
|
4098
4255
|
invalid: false
|
|
4099
4256
|
};
|
|
@@ -4108,7 +4265,7 @@ async function getDurableGateEvidenceStatusForTask(directory, taskId) {
|
|
|
4108
4265
|
};
|
|
4109
4266
|
}
|
|
4110
4267
|
try {
|
|
4111
|
-
return
|
|
4268
|
+
return getDurableGateEvidenceStatusWithCurrentScope(readTaskEvidenceRaw(directory, taskId), directory, taskId);
|
|
4112
4269
|
} catch {
|
|
4113
4270
|
return {
|
|
4114
4271
|
isComplete: false,
|
|
@@ -4118,6 +4275,40 @@ async function getDurableGateEvidenceStatusForTask(directory, taskId) {
|
|
|
4118
4275
|
};
|
|
4119
4276
|
}
|
|
4120
4277
|
}
|
|
4278
|
+
function getDurableGateEvidenceStatusWithCurrentScope(evidence, directory, taskId) {
|
|
4279
|
+
if (!evidence?.gates || typeof evidence.gates !== "object") {
|
|
4280
|
+
return getDurableGateEvidenceStatus(evidence);
|
|
4281
|
+
}
|
|
4282
|
+
if (!Array.isArray(evidence.required_gates)) {
|
|
4283
|
+
return getDurableGateEvidenceStatus(evidence);
|
|
4284
|
+
}
|
|
4285
|
+
if (!evidence.workflow || evidence.workflow.schema !== TASK_WORKFLOW_SCHEMA_MARKER) {
|
|
4286
|
+
if (evidence.required_gates.length === 0) {
|
|
4287
|
+
return {
|
|
4288
|
+
isComplete: false,
|
|
4289
|
+
missingGates: ["required_gates"],
|
|
4290
|
+
evidenceExists: true,
|
|
4291
|
+
invalid: false
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
const missingGates = evidence.required_gates.filter((gate) => evidence.gates[gate] == null);
|
|
4295
|
+
return {
|
|
4296
|
+
isComplete: missingGates.length === 0,
|
|
4297
|
+
missingGates,
|
|
4298
|
+
evidenceExists: true,
|
|
4299
|
+
invalid: false
|
|
4300
|
+
};
|
|
4301
|
+
}
|
|
4302
|
+
const derivedGates = deriveApplicableGateSet(evidence, {
|
|
4303
|
+
currentDeclaredFiles: readCurrentTaskDeclaredFiles(directory, taskId)
|
|
4304
|
+
});
|
|
4305
|
+
return {
|
|
4306
|
+
isComplete: derivedGates.missingGates.length === 0,
|
|
4307
|
+
missingGates: derivedGates.missingGates,
|
|
4308
|
+
evidenceExists: true,
|
|
4309
|
+
invalid: false
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4121
4312
|
function gateEvidenceToEntry(taskId, gate, type, evidence) {
|
|
4122
4313
|
const gateRecord = evidence.gates[gate];
|
|
4123
4314
|
if (!gateRecord) {
|
|
@@ -4171,4 +4362,4 @@ function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
|
|
|
4171
4362
|
return merged;
|
|
4172
4363
|
}
|
|
4173
4364
|
|
|
4174
|
-
export { buildTaskCompletionDescriptor, ensureTaskCheckpointReceipt, updateTaskCheckpointReceipt, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, readLaneEnvFileFromDiskSync, getGitRepositoryStatus, isGitRepo, getDefaultBaseBranch, detectDefaultRemoteBranch, resetToRemoteBranch, resetToMainAfterMerge, sanitizeContextText, getRunMemorySummary, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, loadPlanJsonOnly, loadPlan, withPlanLifecycleLock, savePlan, closePlanTerminalState, updateTaskStatus, derivePlanMarkdown, getCurrentTaskId, isValidEvidenceType, isSecretscanEvidence, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, _internals7 as _internals, readDurableGateEvidence,
|
|
4365
|
+
export { buildTaskCompletionDescriptor, ensureTaskCheckpointReceipt, updateTaskCheckpointReceipt, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, readLaneEnvFileFromDiskSync, getGitRepositoryStatus, isGitRepo, getDefaultBaseBranch, detectDefaultRemoteBranch, resetToRemoteBranch, resetToMainAfterMerge, sanitizeContextText, getRunMemorySummary, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, PlanRecoverySupersededError, loadPlanJsonOnly, loadPlan, withPlanLifecycleLock, savePlan, closePlanTerminalState, updateTaskStatus, derivePlanMarkdown, getCurrentTaskId, isValidEvidenceType, isSecretscanEvidence, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, _internals7 as _internals, readDurableGateEvidence, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
|