opencode-swarm 7.189.0 → 7.190.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.
Files changed (177) hide show
  1. package/README.md +42 -6
  2. package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
  3. package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
  4. package/dist/agents/architect.d.ts +1 -1
  5. package/dist/background/completion-observer.d.ts +6 -0
  6. package/dist/cli/{auto-checkpoint-m7c4x8bk.js → auto-checkpoint-26m3h5f4.js} +9 -9
  7. package/dist/cli/{checkpoint-m7q9886q.js → checkpoint-7a0achpv.js} +18 -20
  8. package/dist/cli/{coder-settlement-hh81z2f7.js → coder-settlement-dsb1t39t.js} +13 -13
  9. package/dist/cli/{config-doctor-0rexy3xt.js → config-doctor-9txawzng.js} +23 -20
  10. package/dist/cli/{core-ays6stz4.js → core-kv7jz9ty.js} +4 -4
  11. package/dist/cli/{curation-policy-65ckmmd5.js → curation-policy-6p6fcfjb.js} +6 -6
  12. package/dist/cli/{curator-dfzkfgs0.js → curator-26yzg8wp.js} +44 -44
  13. package/dist/cli/{curator-drift-nts5atks.js → curator-drift-kxn5sk3z.js} +9 -9
  14. package/dist/cli/curator-llm-factory-hs2y9kmw.js +82 -0
  15. package/dist/cli/{doctor-knowledge-receipt-phase-id-ec40k5hn.js → doctor-knowledge-receipt-phase-id-z8wea03e.js} +9 -9
  16. package/dist/cli/{evidence-summary-service-vgtm72g5.js → evidence-summary-service-bg5zq78s.js} +21 -22
  17. package/dist/cli/{gate-evidence-vmn65kxe.js → gate-evidence-0beg9c7r.js} +8 -8
  18. package/dist/cli/guardrail-explain-n8bm0hpm.js +83 -0
  19. package/dist/cli/{guardrail-log-hc8fjm4x.js → guardrail-log-r2zazmbn.js} +8 -8
  20. package/dist/cli/{guardrail-reset-ct00w300.js → guardrail-reset-0q54t44w.js} +44 -44
  21. package/dist/cli/{hive-promoter-2xax6van.js → hive-promoter-7dr5b9zv.js} +44 -44
  22. package/dist/cli/{index-9gqb5nkj.js → index-0gve0mkj.js} +4 -4
  23. package/dist/cli/{index-34vsyj15.js → index-0yrg10jx.js} +5 -5
  24. package/dist/cli/{index-mfxef20k.js → index-0z8vdkvg.js} +4 -4
  25. package/dist/cli/{index-2nexf5vz.js → index-1f084tqn.js} +22223 -15395
  26. package/dist/cli/{index-qx3g1bbe.js → index-26e2s6r1.js} +1 -1
  27. package/dist/cli/{index-kqegvngt.js → index-2c47efpt.js} +4 -4
  28. package/dist/cli/{index-9tvjwv01.js → index-2fpv2wmc.js} +1 -1
  29. package/dist/cli/{index-8q8783g0.js → index-3zwhxk98.js} +1 -1
  30. package/dist/cli/{index-9j3ybyfs.js → index-4a15347d.js} +7 -7
  31. package/dist/cli/{index-5cfhwd65.js → index-67a6mk20.js} +2 -2
  32. package/dist/cli/{index-d3rjvm9e.js → index-7vnr5cmp.js} +2 -2
  33. package/dist/cli/{index-twb3f4tk.js → index-8bx6vhzk.js} +1 -1
  34. package/dist/cli/{index-0gejy4cx.js → index-ay8ggtgr.js} +2 -2
  35. package/dist/cli/{index-8xabhfwr.js → index-b2j231jy.js} +2 -2
  36. package/dist/cli/{index-cbqkag7q.js → index-bcwmn4d7.js} +3 -3
  37. package/dist/cli/{index-fbgjj7h5.js → index-bfyh3gpe.js} +101 -13
  38. package/dist/cli/{index-skdpgjar.js → index-cewpxcwm.js} +862 -1221
  39. package/dist/cli/{index-bzbxn24k.js → index-dbdkh7pq.js} +5 -5
  40. package/dist/cli/{index-w4zscz6p.js → index-dnp4qtpf.js} +5 -5
  41. package/dist/cli/{index-waxvpwbv.js → index-efztf4yw.js} +127 -13
  42. package/dist/cli/{index-4p3jh14t.js → index-enq166kw.js} +4 -4
  43. package/dist/cli/{index-g03m5jhw.js → index-f46dbyew.js} +3 -3
  44. package/dist/cli/{index-ntda6tje.js → index-feaxsjrt.js} +10 -2
  45. package/dist/cli/{index-89f56ej4.js → index-fthhppnc.js} +46 -46
  46. package/dist/cli/{index-wqa31xnf.js → index-gqb8etv4.js} +7 -7
  47. package/dist/cli/{index-1cwmj1gs.js → index-ha0w0a7t.js} +77 -16
  48. package/dist/cli/{index-nhsafbrh.js → index-hx1fmywk.js} +5 -5
  49. package/dist/cli/{index-kaqdzhj3.js → index-kfevm0n5.js} +2 -2
  50. package/dist/cli/{index-z29hksyw.js → index-m28dzyy8.js} +26 -10
  51. package/dist/cli/{index-x1eh2636.js → index-mcv0v2dp.js} +1 -1
  52. package/dist/cli/{index-9czp2dcg.js → index-mjfpaxw3.js} +7 -7
  53. package/dist/cli/{index-f6exny9m.js → index-n8q43eff.js} +2 -2
  54. package/dist/cli/{index-7hr5m7yp.js → index-nkgzkt7c.js} +1 -1
  55. package/dist/cli/{index-p2tqzwh6.js → index-qs9myayh.js} +5 -10
  56. package/dist/cli/{index-hh7pa3yk.js → index-r9yvpr2m.js} +2 -2
  57. package/dist/cli/{index-cdjxzs2r.js → index-rjzn7hf0.js} +7 -7
  58. package/dist/cli/{index-zfbxfm2m.js → index-s8b67e6h.js} +33 -12
  59. package/dist/cli/{index-bskex45z.js → index-s9cgnd2z.js} +2 -2
  60. package/dist/cli/{index-cj4mp0fm.js → index-sc7t1tev.js} +6 -6
  61. package/dist/cli/{index-3dx0c7ve.js → index-sfjpbnqs.js} +44 -44
  62. package/dist/cli/{index-16m2m4ah.js → index-spgfs5y7.js} +1 -1
  63. package/dist/cli/{index-q9kefh33.js → index-sxj143a4.js} +1 -1
  64. package/dist/cli/{index-zsrtt42s.js → index-v3bmwmqw.js} +25 -1
  65. package/dist/cli/{index-5v09zw1q.js → index-v3j9fk16.js} +18 -8
  66. package/dist/cli/{index-2btybj8w.js → index-xdq6mak1.js} +4 -4
  67. package/dist/cli/{index-k8sarme1.js → index-y9v41185.js} +1 -1
  68. package/dist/cli/{index-4a7j5f60.js → index-yy5yyyst.js} +4 -4
  69. package/dist/cli/{index-1xa8wq76.js → index-z3ahy9c2.js} +2 -2
  70. package/dist/cli/{index-wrdktgpy.js → index-zk6ttjkt.js} +2 -2
  71. package/dist/cli/index.js +45 -45
  72. package/dist/cli/{knowledge-escalator-v3c8w86g.js → knowledge-escalator-hdrqt5pj.js} +15 -15
  73. package/dist/cli/{knowledge-events-kagaccj9.js → knowledge-events-ttebc8gn.js} +13 -13
  74. package/dist/cli/{knowledge-link-h4pdcnya.js → knowledge-link-6hw3z6wn.js} +5 -5
  75. package/dist/cli/{knowledge-store-ywxjk5pb.js → knowledge-store-8cxv6w2n.js} +6 -6
  76. package/dist/cli/{knowledge-validator-210gkpec.js → knowledge-validator-kr5xmqpa.js} +8 -8
  77. package/dist/cli/{mcp-58hgx6av.js → mcp-jtk79ygp.js} +1 -1
  78. package/dist/cli/{model-preflight-e69b48n0.js → model-preflight-d6tt7479.js} +44 -44
  79. package/dist/cli/{pending-delegations-g24d3qa9.js → pending-delegations-ttk09f50.js} +12 -12
  80. package/dist/cli/{pr-subscriptions-qprh2zsm.js → pr-subscriptions-2kbeezsr.js} +12 -12
  81. package/dist/cli/{pr-workflow-gate-pj7dhkv4.js → pr-workflow-gate-3ra6p7k4.js} +44 -44
  82. package/dist/cli/{runner-4hfmp1ag.js → runner-ssnsje35.js} +6 -6
  83. package/dist/cli/{scan-cursor-s5wsxxd1.js → scan-cursor-zzccy117.js} +7 -7
  84. package/dist/cli/{schema-smgngq9m.js → schema-9prswg1x.js} +14 -2
  85. package/dist/cli/{scope-persistence-tbv2ar4w.js → scope-persistence-eqm8vwnx.js} +16 -16
  86. package/dist/cli/{server-7p2ds5w2.js → server-swjre81h.js} +45 -45
  87. package/dist/cli/{skill-generator-r4hsne71.js → skill-generator-d8dk9vps.js} +17 -17
  88. package/dist/cli/{snapshot-coordination-init-ydjwh56d.js → snapshot-coordination-init-f49y0zr0.js} +44 -44
  89. package/dist/cli/{speckit-checkoff-aj75vmjq.js → speckit-checkoff-pnb6q9gd.js} +5 -5
  90. package/dist/cli/{telemetry-kvhe94gs.js → telemetry-9szfdfqv.js} +1 -1
  91. package/dist/cli/{workspace-snapshot-30kdf018.js → workspace-snapshot-9f12p1s1.js} +3 -3
  92. package/dist/cli/{worktree-collision-ownership-0bxj9f0t.js → worktree-collision-ownership-3c7xyc4p.js} +14 -14
  93. package/dist/cli/{worktree-isolation-h0c22pm2.js → worktree-isolation-jggdf28m.js} +46 -44
  94. package/dist/commands/coupling.d.ts +21 -4
  95. package/dist/commands/epic-learning.d.ts +28 -0
  96. package/dist/commands/epic.d.ts +81 -36
  97. package/dist/commands/registry.d.ts +8 -8
  98. package/dist/commands/turbo.d.ts +16 -1
  99. package/dist/config/constants.d.ts +11 -1
  100. package/dist/config/loader.d.ts +2 -0
  101. package/dist/config/schema.d.ts +291 -77
  102. package/dist/db/coordination-store.d.ts +6 -0
  103. package/dist/epic/close.d.ts +209 -0
  104. package/dist/{turbo/epic → epic}/cochange-conflict.d.ts +5 -4
  105. package/dist/{turbo/epic → epic}/cochange-source.d.ts +10 -1
  106. package/dist/epic/components.d.ts +238 -0
  107. package/dist/epic/config-gate.d.ts +25 -0
  108. package/dist/epic/config.d.ts +101 -0
  109. package/dist/{turbo/epic → epic}/coupling-report.d.ts +3 -1
  110. package/dist/epic/declared-scopes.d.ts +37 -0
  111. package/dist/epic/epic-branch.d.ts +157 -0
  112. package/dist/epic/gate-policy.d.ts +114 -0
  113. package/dist/epic/git-once.d.ts +26 -0
  114. package/dist/epic/learning-store.d.ts +176 -0
  115. package/dist/epic/learning.d.ts +198 -0
  116. package/dist/epic/legacy-migration.d.ts +26 -0
  117. package/dist/epic/lifecycle.d.ts +437 -0
  118. package/dist/epic/markers.d.ts +123 -0
  119. package/dist/epic/merge-epoch.d.ts +90 -0
  120. package/dist/epic/next-wave-format.d.ts +101 -0
  121. package/dist/epic/next-wave.d.ts +102 -0
  122. package/dist/epic/phase-readiness.d.ts +221 -0
  123. package/dist/epic/plan-key.d.ts +104 -0
  124. package/dist/epic/plan-shaping-seam.d.ts +112 -0
  125. package/dist/epic/planning-signals.d.ts +38 -0
  126. package/dist/epic/report.d.ts +152 -0
  127. package/dist/epic/residue-commit.d.ts +151 -0
  128. package/dist/epic/scorecard.d.ts +130 -0
  129. package/dist/epic/shaping-sizing.d.ts +108 -0
  130. package/dist/epic/shaping-suggestions.d.ts +172 -0
  131. package/dist/epic/shaping.d.ts +112 -0
  132. package/dist/epic/sizing.d.ts +60 -0
  133. package/dist/epic/start.d.ts +187 -0
  134. package/dist/epic/task-landing.d.ts +83 -0
  135. package/dist/epic/wave-close.d.ts +112 -0
  136. package/dist/epic/wave-select.d.ts +95 -0
  137. package/dist/evaluation/contracts.d.ts +1 -1
  138. package/dist/evaluation/public-api.d.ts +1 -1
  139. package/dist/full-auto/policy.d.ts +12 -1
  140. package/dist/hooks/delegation-gate/worktree-isolation.d.ts +16 -0
  141. package/dist/hooks/delegation-gate.d.ts +11 -0
  142. package/dist/hooks/guardrails/index.d.ts +7 -0
  143. package/dist/hooks/promotion-evidence-store.d.ts +2 -2
  144. package/dist/index.js +814 -807
  145. package/dist/plan/manager.d.ts +6 -8
  146. package/dist/plan/parallel-verdict.d.ts +13 -2
  147. package/dist/retention/caps.d.ts +1 -1
  148. package/dist/review/contracts.d.ts +8 -1
  149. package/dist/services/config-doctor.d.ts +38 -0
  150. package/dist/session/snapshot-reader.d.ts +2 -0
  151. package/dist/session/snapshot-writer.d.ts +13 -2
  152. package/dist/state.d.ts +13 -18
  153. package/dist/tools/epic-next-wave.d.ts +20 -0
  154. package/dist/tools/epic-phase-review.d.ts +43 -0
  155. package/dist/tools/index.d.ts +2 -3
  156. package/dist/tools/manifest.d.ts +2 -3
  157. package/dist/tools/phase-complete.d.ts +5 -1
  158. package/dist/tools/save-plan.d.ts +7 -0
  159. package/dist/tools/tool-metadata.d.ts +4 -8
  160. package/dist/worktree/merge.d.ts +14 -1
  161. package/opencode-swarm.schema.json +273 -28
  162. package/package.json +1 -1
  163. package/dist/cli/curator-llm-factory-nrn6n2p1.js +0 -82
  164. package/dist/cli/guardrail-explain-9jmr8ezt.js +0 -83
  165. package/dist/tools/epic-plan-waves.d.ts +0 -79
  166. package/dist/tools/epic-record-divergence.d.ts +0 -73
  167. package/dist/tools/epic-run-phase.d.ts +0 -179
  168. package/dist/turbo/epic/activation.d.ts +0 -193
  169. package/dist/turbo/epic/calibration-engine.d.ts +0 -88
  170. package/dist/turbo/epic/calibration.d.ts +0 -80
  171. package/dist/turbo/epic/divergence-recorder.d.ts +0 -123
  172. package/dist/turbo/epic/index.d.ts +0 -24
  173. package/dist/turbo/epic/promotion-evidence.d.ts +0 -42
  174. package/dist/turbo/epic/state.d.ts +0 -69
  175. package/dist/turbo/epic/task-commit.d.ts +0 -110
  176. package/dist/turbo/epic/upstream-commits.d.ts +0 -82
  177. package/dist/turbo/epic/wave-planner.d.ts +0 -83
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Epic v2 C3 — git refs as the epic's durable markers.
3
+ *
4
+ * Under `refs/swarm/epics/<epicKey>/` an open git epic keeps:
5
+ *
6
+ * - `base` → the commit the epic started from (written at start);
7
+ * - `waves/<seq>` → HEAD when wave `<seq>` closed;
8
+ * - `tasks/<id>` → the commit proving task `<id>`'s work is on the epic
9
+ * branch (its newest `swarm(task <id>):` landing or
10
+ * residue commit for this plan inside its wave, else
11
+ * the wave's close HEAD).
12
+ *
13
+ * The epic record is the source of truth (`EpicTaskOutcome.marker`,
14
+ * `EpicWaveRecord.closeHead`, `git.baseCommit`); the refs mirror it
15
+ * ({@link syncEpicRefs}): a missing ref is created with the create-only form
16
+ * `git update-ref <ref> <sha> ""`, an existing ref with the recorded value is
17
+ * left alone (idempotent), and a ref with another value (left over by an
18
+ * earlier epic of the same key with `retain_refs`, or a raced write) is
19
+ * compare-and-swapped to the recorded value. The refs are gc roots for the
20
+ * epic's commits while it is open, survive in every linked worktree (refs
21
+ * are shared), and are never pushed or fetched by default (`git push` /
22
+ * `git clone` carry only branches and tags; `--mirror` copies them).
23
+ *
24
+ * Predecessor evidence ({@link isCommitAncestorOfHead}): a dependency
25
+ * completed in an earlier wave is satisfied only when its task ref exists
26
+ * and is an ancestor of HEAD — a rebase or amend that dropped the commit
27
+ * makes the dependent `predecessor-missing` until `/swarm epic status
28
+ * --repair-refs` ({@link repairEpicTaskRefs}) re-adopts the task's commit.
29
+ *
30
+ * At `/swarm epic close` the report captures every ref and the refs are
31
+ * deleted ({@link deleteEpicRefs}) unless `epic.retain_refs: true`.
32
+ *
33
+ * Subprocess discipline (AGENTS.md #3): reads use `gitExec`
34
+ * (`src/git/branch.ts`); every ref write/delete is ONE attempt
35
+ * (`git-once.ts`) — a timed-out `update-ref` is judged by re-reading the ref.
36
+ */
37
+ import { gitExecOnce, gitProbeExitCode } from './git-once.js';
38
+ import type { EpicRecordV1 } from './lifecycle.js';
39
+ /** Root namespace of every epic's refs. */
40
+ export declare const EPIC_REFS_ROOT = "refs/swarm/epics";
41
+ /** Bound on the commits a marker lookup or repair walks. */
42
+ export declare const MARKER_LOOKUP_MAX_COMMITS = 2000;
43
+ /** DI seam (AGENTS.md invariant 7). Restore in `afterEach`. */
44
+ export declare const _internals: {
45
+ gitExec: (args: string[], cwd: string) => string;
46
+ gitExecOnce: typeof gitExecOnce;
47
+ gitProbeExitCode: typeof gitProbeExitCode;
48
+ };
49
+ export declare function isFullSha(value: unknown): value is string;
50
+ /**
51
+ * A task id as one ref path component: strict-looking ids (`1.2`, `2.10.3`,
52
+ * `setup-db`) verbatim; anything else (or a `.lock` suffix) hex-encoded
53
+ * behind `_x`, which no verbatim component can start with.
54
+ */
55
+ export declare function taskRefComponent(taskId: string): string;
56
+ export declare function epicRefPrefix(epicKey: string): string;
57
+ export declare function epicBaseRef(epicKey: string): string;
58
+ export declare function epicWaveRef(epicKey: string, seq: number): string;
59
+ export declare function epicTaskRef(epicKey: string, taskId: string): string;
60
+ /**
61
+ * Every ref under the epic's namespace (full name → sha). One bounded
62
+ * `git for-each-ref`. Throws when git fails.
63
+ */
64
+ export declare function readEpicRefs(directory: string, epicKey: string): Map<string, string>;
65
+ /**
66
+ * Point `ref` at `sha`: create-only when missing (`update-ref <ref> <sha> ""`),
67
+ * no-op when already there, compare-and-swap from `existing` otherwise. One
68
+ * attempt; on failure the ref is re-read and the call succeeds only when it
69
+ * now holds `sha`. Throws otherwise.
70
+ */
71
+ export declare function writeEpicRef(directory: string, ref: string, sha: string, existing: string | null): 'created' | 'unchanged' | 'updated';
72
+ /** The refs the epic record says should exist (full name → sha). */
73
+ export declare function desiredEpicRefs(record: EpicRecordV1): Map<string, string>;
74
+ /**
75
+ * Make the epic's refs mirror its record (see the module header). Returns
76
+ * the refs after the sync. Throws when git fails.
77
+ */
78
+ export declare function syncEpicRefs(directory: string, record: EpicRecordV1): Map<string, string>;
79
+ /** `git merge-base --is-ancestor <sha> HEAD`. Throws when git fails. */
80
+ export declare function isCommitAncestorOfHead(directory: string, sha: string): boolean;
81
+ /**
82
+ * Delete every ref under the epic's namespace (each `update-ref -d` guarded
83
+ * by its current value, one attempt each). Never throws; failures are
84
+ * returned for the close report.
85
+ */
86
+ export declare function deleteEpicRefs(directory: string, epicKey: string): {
87
+ deleted: string[];
88
+ failed: string[];
89
+ };
90
+ /**
91
+ * The newest `swarm(task <id>):` commit of each task in `taskIds` whose
92
+ * `Swarm-Plan:` trailer is `planKey`, among `range` (e.g. `<base>..HEAD`).
93
+ * One bounded `git log` (merge commits included — a landing is a merge).
94
+ * Throws when git fails.
95
+ */
96
+ export declare function findTaskCommits(directory: string, range: string, planKey: string, taskIds: readonly string[]): Map<string, string>;
97
+ /** The range an epic's own commits live in: `<base>..HEAD`, or all of HEAD. */
98
+ export declare function epicCommitRange(record: EpicRecordV1): string;
99
+ /** One task's repair verdict (`/swarm epic status --repair-refs`). */
100
+ export interface EpicRefRepair {
101
+ taskId: string;
102
+ status: 'ok' | 'repaired' | 'needs-attention';
103
+ sha: string | null;
104
+ detail: string;
105
+ }
106
+ /**
107
+ * Plan `/swarm epic status --repair-refs` (MINOR 11). For every completed
108
+ * task the epic recorded, and every task in `unrecorded` (completed in the
109
+ * plan during the epic but outside a wave), the task's commit must be
110
+ * reachable from HEAD. When it is not (a rebase or amend rewrote it, or no
111
+ * ref was ever written) the task's commit is re-adopted: its newest
112
+ * `swarm(task <id>):` commit for this plan in the epic's range, else (a
113
+ * recorded task) the newest commit in that range touching its declared
114
+ * files; neither ⇒ `needs-attention`. Returns the verdicts and the adopted
115
+ * commits — `recorded` ones the caller CASes into the epic record (then
116
+ * {@link syncEpicRefs}), `unrecorded` ones it writes as refs directly.
117
+ * Throws when git fails.
118
+ */
119
+ export declare function planEpicTaskRefRepair(directory: string, record: EpicRecordV1, unrecorded: readonly string[]): {
120
+ repairs: EpicRefRepair[];
121
+ recorded: Map<string, string>;
122
+ unrecorded: Map<string, string>;
123
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Plan-epoch filter over the shared worktree merge-back status registry
3
+ * (Epic v2 C0).
4
+ *
5
+ * `.swarm/worktree-merge-status.json` is keyed by bare task id and is never
6
+ * cleaned automatically (it also serves `/swarm lanes` and orphan recovery,
7
+ * so it is NOT re-keyed here). Epic consults it where a task's work never
8
+ * landing matters: `epic_next_wave` does not close a wave while one of its
9
+ * completed tasks has a failure recorded since the wave was issued, and
10
+ * `update_task_status` skips the #2582 auto-checkpoint for such a task
11
+ * ({@link epicMergeFailureSkipsCheckpoint}). Without an epoch filter, a
12
+ * failure recorded for task `1.1` of a PREVIOUS plan would block the current
13
+ * plan's `1.1` forever.
14
+ *
15
+ * Most writers stamp `completedAt` and/or `queuedAt`, but NOT all: the
16
+ * delegation-gate `task-result` failure (a cancelled/denied task) records no
17
+ * timestamp. A failure recorded before the current plan's root timestamp
18
+ * belongs to an earlier plan and is ignored. A failure with NO timestamp
19
+ * cannot be dated, so it is treated as relevant (fail closed), surfaced by
20
+ * `/swarm epic status`, and can be cleared explicitly with
21
+ * `/swarm epic clear-merge-failure <taskId> --confirm` (which calls the
22
+ * registry's own `clearWorktreeMergeStatus`, the same function a clean
23
+ * re-dispatch merge-back uses).
24
+ */
25
+ import { clearWorktreeMergeStatus as clearWorktreeMergeStatus_import, getWorktreeMergeFailure as getWorktreeMergeFailure_import, initDurableStatusPath as initDurableStatusPath_import, scanWorktreeMergeFailuresForRecovery as scanWorktreeMergeFailuresForRecovery_import, type WorktreeMergeFailure } from '../hooks/delegation-gate/worktree-merge-status.js';
26
+ import { epicSentinelExists as epicSentinelExists_import, getOpenEpic as getOpenEpic_import } from './lifecycle.js';
27
+ /** How a recorded merge failure relates to the current plan. */
28
+ export type MergeFailureRelevance = 'current' | 'undated' | 'stale';
29
+ /** Classify one failure against the plan root time (`sinceMs`). */
30
+ export declare function classifyMergeFailure(failure: WorktreeMergeFailure, sinceMs: number): MergeFailureRelevance;
31
+ /**
32
+ * The recorded merge-back failure for `taskId` iff it is relevant to the
33
+ * plan that started at `sinceMs`: recorded at/after `sinceMs`
34
+ * (`completedAt ?? queuedAt`), or undated (fail closed). Pass `sinceMs = 0`
35
+ * when the plan root is unknown — every failure is then relevant.
36
+ */
37
+ export declare function relevantMergeFailure(taskId: string, sinceMs: number): WorktreeMergeFailure | undefined;
38
+ /**
39
+ * {@link relevantMergeFailure} for one project, used by `epic_next_wave`
40
+ * (wave advance rule). The shared registry is process-global and keyed by
41
+ * bare task id, so its in-memory map is trusted only while it is bound to
42
+ * THIS project's durable file (then every record/clear is persisted to that
43
+ * file synchronously). Bound to another project, or unbound, the in-memory
44
+ * map may hold another project's `1.1`: only this project's durable file is
45
+ * read (read-only scan). An unreadable durable file adds nothing; `/swarm
46
+ * epic status` reports it.
47
+ */
48
+ export declare function relevantMergeFailureForProject(directory: string, taskId: string, sinceMs: number): WorktreeMergeFailure | undefined;
49
+ /**
50
+ * Epic v2 C3 seam before the #2582 auto-checkpoint in `updateTaskStatus`
51
+ * (MINOR 3): true — skip the checkpoint, with a critical warning — when an
52
+ * epic is open and `taskId` has a merge-back failure recorded since the epic
53
+ * started (or undated): its work is not on the epic branch, so a checkpoint
54
+ * of HEAD would exclude it. One `existsSync` and `false` when no epic is
55
+ * open; unreadable lifecycle state counts as no epic. Never throws.
56
+ */
57
+ export declare function epicMergeFailureSkipsCheckpoint(directory: string, taskId: string): boolean;
58
+ /**
59
+ * `/swarm epic status` lines for recorded merge failures (read-only scan of
60
+ * the durable file). Empty when there are none. `sinceMs` null ⇒ plan root
61
+ * unknown, every failure is reported as blocking.
62
+ */
63
+ export declare function describeMergeFailuresForStatus(directory: string, sinceMs: number | null): string[];
64
+ /**
65
+ * `/swarm epic clear-merge-failure <taskId> [--confirm]` — Epic-owned escape
66
+ * hatch for a merge failure that blocks an epic wave but no longer reflects
67
+ * reality (work landed by hand, undated record from an earlier plan).
68
+ *
69
+ * Only a task id that currently HAS a recorded failure (durable file or
70
+ * in-memory registry) can be cleared. Without `--confirm` the call is
71
+ * read-only and prints what would be cleared. With it, the registry's own
72
+ * `clearWorktreeMergeStatus` removes the record (in memory + durable file),
73
+ * after `initDurableStatusPath(directory)` binds the registry to this
74
+ * project (a no-op when the delegation gate already did).
75
+ */
76
+ export declare function clearMergeFailureCommand(directory: string, args: string[]): string;
77
+ /**
78
+ * DI seam (AGENTS.md invariant 7). The shared registry module is consumed
79
+ * through its exported API only, never modified.
80
+ */
81
+ export declare const _internals: {
82
+ epicSentinelExists: typeof epicSentinelExists_import;
83
+ getOpenEpic: typeof getOpenEpic_import;
84
+ clearWorktreeMergeStatus: typeof clearWorktreeMergeStatus_import;
85
+ initDurableStatusPath: typeof initDurableStatusPath_import;
86
+ getWorktreeMergeFailure: typeof getWorktreeMergeFailure_import;
87
+ scanWorktreeMergeFailuresForRecovery: typeof scanWorktreeMergeFailuresForRecovery_import;
88
+ /** Throws when the shared registry is unbound (no project path given). */
89
+ getBoundStatusPath: () => string;
90
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Result shapes and architect-facing text of `epic_next_wave` (Epic v2 C2).
3
+ * Pure: no I/O. The procedure lives in these responses (the banner only
4
+ * says "call epic_next_wave and do exactly what its status says").
5
+ */
6
+ import type { Plan } from '../config/plan-schema.js';
7
+ import type { EpicPhaseRecord, EpicTaskOutcome, EpicWaveRecord } from './lifecycle.js';
8
+ import type { EpicPredecessorProblem } from './wave-select.js';
9
+ export interface EpicWaveView {
10
+ seq: number;
11
+ phase: number;
12
+ kind: EpicWaveRecord['kind'];
13
+ taskIds: string[];
14
+ files: Record<string, string[]>;
15
+ tasks: {
16
+ taskId: string;
17
+ description: string;
18
+ files: string[];
19
+ }[];
20
+ issuedAt: string;
21
+ }
22
+ /** What the call just closed (present on results that follow a close). */
23
+ export interface EpicClosedWaveSummary {
24
+ seq: number;
25
+ phase: number;
26
+ taskIds: string[];
27
+ resolutions: Record<string, EpicTaskOutcome['resolution']>;
28
+ closeHead: string | null;
29
+ /** Tasks with undeclared writes (session or single-task git attribution). */
30
+ divergence: {
31
+ taskId: string;
32
+ undeclared: string[];
33
+ }[];
34
+ /** Wave-level undeclared files no task's attribution explains. */
35
+ waveUndeclared: string[];
36
+ }
37
+ export type EpicNextWaveRefusal = 'no-open-epic' | 'epic-orphaned' | 'epic-state-unreadable' | 'epic-disabled-by-config';
38
+ export type EpicNextWaveBlockReason = 'task-blocked' | 'merge-failed' | 'landing-index-dirty' | 'git-failed' | 'dirty-baseline' | 'predecessor-missing' | 'plan-revised' | 'task-reopened' | 'epic-branch-mismatch';
39
+ type WithClosed = {
40
+ closedWave?: EpicClosedWaveSummary;
41
+ };
42
+ export type EpicNextWaveResult = ({
43
+ status: 'dispatch';
44
+ wave: EpicWaveView;
45
+ instructions: string;
46
+ } & WithClosed) | ({
47
+ status: 'declare-scopes';
48
+ phase: number;
49
+ tasks: {
50
+ taskId: string;
51
+ suggestedFiles: string[];
52
+ }[];
53
+ message: string;
54
+ } & WithClosed) | ({
55
+ status: 'in-progress';
56
+ wave: EpicWaveView;
57
+ waitingOn: {
58
+ taskId: string;
59
+ state: string;
60
+ }[];
61
+ message: string;
62
+ } & WithClosed) | ({
63
+ status: 'blocked';
64
+ reason: EpicNextWaveBlockReason;
65
+ details: Record<string, unknown>;
66
+ message: string;
67
+ } & WithClosed) | ({
68
+ status: 'phase-ready-for-review';
69
+ phase: number;
70
+ message: string;
71
+ } & WithClosed) | ({
72
+ status: 'epic-complete';
73
+ message: string;
74
+ } & WithClosed) | {
75
+ status: 'refused';
76
+ reason: EpicNextWaveRefusal;
77
+ message: string;
78
+ };
79
+ /**
80
+ * How review findings are fixed while an epic is open (C4): coders run only
81
+ * through waves, so a fix is a NEW pending task of the phase — `epic_next_wave`
82
+ * then runs it as a fix wave (scopes, landing commit, refs, outcomes) and
83
+ * returns `phase-ready-for-review` again.
84
+ */
85
+ export declare function epicPhaseFixPath(phase: number): string;
86
+ /** The fix-task steps of {@link epicPhaseFixPath} on their own. */
87
+ export declare function epicPhaseFixSteps(phase: number): string;
88
+ export declare function refused(reason: EpicNextWaveRefusal, message: string): EpicNextWaveResult;
89
+ export declare function toWaveView(wave: EpicWaveRecord, plan: Plan): EpicWaveView;
90
+ /** The step-by-step text returned with every `dispatch`. */
91
+ export declare function buildDispatchInstructions(wave: EpicWaveView): string;
92
+ export declare function summarizeClosedWave(wave: EpicWaveRecord, outcomes: EpicTaskOutcome[]): EpicClosedWaveSummary;
93
+ export declare function predecessorMessage(problems: EpicPredecessorProblem[], planKey: string): string;
94
+ /**
95
+ * Phase bookkeeping: the current phase takes `currentStatus` (`active` while
96
+ * waves run, `review` once every task is resolved). A phase recorded
97
+ * `complete` (by `phase_complete`) is never downgraded. Returns the SAME
98
+ * object when nothing changes (callers skip the write).
99
+ */
100
+ export declare function syncPhaseRecords(phases: Record<string, EpicPhaseRecord>, currentPhase: number, currentStatus: 'active' | 'review'): Record<string, EpicPhaseRecord>;
101
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Epic v2 C2 — `epic_next_wave`: the single, idempotent way forward while an
3
+ * epic is open. It replaces `epic_decide_phase`, `epic_plan_waves` and
4
+ * `epic_record_divergence`.
5
+ *
6
+ * Each call, in order:
7
+ * 1. refuses unless Epic is enabled by config and an epic is open for the
8
+ * current plan (`epic-disabled-by-config`, `epic-state-unreadable`,
9
+ * `epic-orphaned`, `no-open-epic`);
10
+ * 2. blocks `epic-branch-mismatch` when HEAD left the epic branch (M-e);
11
+ * 3. with a wave active — advance rule: the wave closes when every task is
12
+ * resolved (plan status completed — already gated by
13
+ * `update_task_status` — or closed, or removed from the plan) and none
14
+ * has a merge-back failure recorded since the wave was issued.
15
+ * Otherwise: `blocked` (`task-blocked`, `merge-failed`, `plan-revised`
16
+ * when an unresolved task moved to another phase — the wave is aborted)
17
+ * or `in-progress` (the same wave; idempotent). A closable wave is
18
+ * closed (`wave-close.ts`) and the call continues to the next wave;
19
+ * 4. phases are iterations: the current phase is the first phase not
20
+ * recorded complete by `phase_complete`; a pending task in a complete
21
+ * phase
22
+ * blocks (`task-reopened` when the epic had resolved it, else
23
+ * `plan-revised`);
24
+ * 5. every current-phase task resolved → `phase-ready-for-review` (then
25
+ * `epic_phase_review` → `phase_complete`); every phase complete →
26
+ * `epic-complete`;
27
+ * 6. otherwise selects the next wave (`wave-select.ts` over the component
28
+ * planner `components.ts`, with the planning signals of
29
+ * `planning-signals.ts`; past waves of the phase age its components):
30
+ * `declare-scopes`,
31
+ * `blocked` (`predecessor-missing`, `task-blocked`, `git-failed`,
32
+ * `dirty-baseline`), or `dispatch` — a multi-task wave must pass THE
33
+ * wave verdict (`gate-policy.ts`, the call the delegation gate repeats
34
+ * at dispatch); the wave (frozen declared scopes, base HEAD) is
35
+ * CAS-written into the epic record (token-guarded) and returned with
36
+ * dispatch instructions.
37
+ *
38
+ * Git epics (Epic v2 C3): every task's work is committed before the task
39
+ * completes (its coder's worktree lands as a merge commit; non-coder writes
40
+ * become residue commits). Closing a wave first commits any residue still
41
+ * attributed to its tasks, then records each task's commit and mirrors it to
42
+ * `refs/swarm/epics/<epicKey>/tasks/<id>` (`markers.ts`). A completed
43
+ * dependency outside the batch is satisfied by that ref being an ancestor of
44
+ * HEAD (or by having been completed before the epic started). Before a new
45
+ * wave, TRACKED changes block `dirty-baseline`; untracked files are only
46
+ * reported.
47
+ */
48
+ import { loadPluginConfigWithMeta as loadPluginConfigWithMeta_import } from '../config/index.js';
49
+ import type { Plan } from '../config/plan-schema.js';
50
+ import { readLedgerEvents as readLedgerEvents_import } from '../plan/ledger.js';
51
+ import { loadPlanJsonOnly as loadPlanJsonOnly_import } from '../plan/manager.js';
52
+ import { getCoChangeData as getCoChangeData_import } from './cochange-source.js';
53
+ import { resolveEpicDeclaredScopes as resolveEpicDeclaredScopes_import } from './declared-scopes.js';
54
+ import { checkEpicBranch as checkEpicBranch_import } from './epic-branch.js';
55
+ import { computeEpicWaveVerdict as computeEpicWaveVerdict_import } from './gate-policy.js';
56
+ import { loadEpicLearningView as loadEpicLearningView_import } from './learning-store.js';
57
+ import { type EpicRecordV1, getOpenEpic as getOpenEpic_import, inspectEpic as inspectEpic_import, updateEpicRecord as updateEpicRecord_import } from './lifecycle.js';
58
+ import { isCommitAncestorOfHead as isCommitAncestorOfHead_import, syncEpicRefs as syncEpicRefs_import } from './markers.js';
59
+ import { relevantMergeFailureForProject as relevantMergeFailureForProject_import } from './merge-epoch.js';
60
+ import { type EpicNextWaveResult } from './next-wave-format.js';
61
+ import { commitTaskResidue as commitTaskResidue_import, listDirtyEntries as listDirtyEntries_import } from './residue-commit.js';
62
+ import { collectTaskAttribution as collectTaskAttribution_import, computeWaveClose as computeWaveClose_import, recordEpicWaveLearning as recordEpicWaveLearning_import, releaseWaveAttribution as releaseWaveAttribution_import } from './wave-close.js';
63
+ export type { EpicNextWaveResult } from './next-wave-format.js';
64
+ /** DI seam (AGENTS.md invariant 7). Restore in `afterEach`. */
65
+ export declare const _internals: {
66
+ loadPluginConfigWithMeta: typeof loadPluginConfigWithMeta_import;
67
+ loadPlanJsonOnly: typeof loadPlanJsonOnly_import;
68
+ readLedgerEvents: typeof readLedgerEvents_import;
69
+ getOpenEpic: typeof getOpenEpic_import;
70
+ inspectEpic: typeof inspectEpic_import;
71
+ updateEpicRecord: typeof updateEpicRecord_import;
72
+ checkEpicBranch: typeof checkEpicBranch_import;
73
+ listDirtyEntries: typeof listDirtyEntries_import;
74
+ commitTaskResidue: typeof commitTaskResidue_import;
75
+ collectTaskAttribution: typeof collectTaskAttribution_import;
76
+ syncEpicRefs: typeof syncEpicRefs_import;
77
+ isCommitAncestorOfHead: typeof isCommitAncestorOfHead_import;
78
+ /** One writer of the primary checkout's index at a time (merge-back queue). */
79
+ serializeWithMergeBacks: <T>(task: () => T | Promise<T>) => Promise<T>;
80
+ readHead: (directory: string) => string | null;
81
+ resolveEpicDeclaredScopes: typeof resolveEpicDeclaredScopes_import;
82
+ relevantMergeFailureForProject: typeof relevantMergeFailureForProject_import;
83
+ loadEpicLearningView: typeof loadEpicLearningView_import;
84
+ getCoChangeData: typeof getCoChangeData_import;
85
+ computeWaveClose: typeof computeWaveClose_import;
86
+ recordEpicWaveLearning: typeof recordEpicWaveLearning_import;
87
+ releaseWaveAttribution: typeof releaseWaveAttribution_import;
88
+ computeEpicWaveVerdict: typeof computeEpicWaveVerdict_import;
89
+ now: () => number;
90
+ };
91
+ /** Run one `epic_next_wave` step for `directory` (see the module header). */
92
+ export declare function runEpicNextWave(directory: string, sessionID: string | undefined): Promise<EpicNextWaveResult>;
93
+ /**
94
+ * Tasks completed BEFORE the epic started. `/swarm epic start` refuses a
95
+ * dirty tree, so their work is in HEAD even though the epic recorded no
96
+ * commit (task ref) for them — they need none as predecessor evidence. A task
97
+ * counts when the epic never resolved it in a wave and either its phase was
98
+ * already finished at start, or its last transition to `completed` in the
99
+ * plan ledger predates the start (or it was saved completed, with no
100
+ * transition at all).
101
+ */
102
+ export declare function completedBeforeEpic(directory: string, epic: EpicRecordV1, plan: Plan): Promise<Set<string>>;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Epic Mode phase readiness — phase reviewer + phase critic gate.
3
+ *
4
+ * Epic Mode replaces Lean Turbo's `lean_turbo_readiness` gate in
5
+ * `phase_complete` (Lean readiness is keyed on Lean lane state, which Epic's
6
+ * visible-`Task` wave flow never produces). This module is the Epic-owned
7
+ * replacement: before an Epic phase may close, an integrated phase reviewer
8
+ * AND a phase critic must both have APPROVED the phase's combined result.
9
+ *
10
+ * ## Evidence integrity
11
+ *
12
+ * Verdicts are never self-reported by the architect. `runEpicPhaseReview`
13
+ * dispatches the reviewer and then the critic itself, through the plugin's
14
+ * read-only ephemeral review dispatcher (the same pattern `lean_turbo_review` /
15
+ * `lean_turbo_critic` use), parses each verdict from the agent's own response
16
+ * text, and persists the result to
17
+ * `.swarm/evidence/{phase}/epic-phase-review.json`. An agent therefore cannot
18
+ * obtain an APPROVED record without a real reviewer and critic dispatch.
19
+ *
20
+ * ## Freshness binding
21
+ *
22
+ * The evidence binds to:
23
+ * - the epic instance (`epic_key`), so a review recorded under one epic can
24
+ * never satisfy phase_complete under another — e.g. after the epic was
25
+ * closed and a new one started on the same plan,
26
+ * - the plan identity (`plan_id`) and status-free plan structure hash,
27
+ * - the phase's task ids + statuses (`phase_tasks_digest`), and
28
+ * - the byte content of every phase task's `.swarm/evidence/{taskId}.json`
29
+ * (`task_evidence_digest`), so any post-review rework that re-records
30
+ * reviewer/test_engineer gate evidence invalidates the approval,
31
+ * plus a 24h TTL with a bounded forward-skew rejection (the Full-Auto phase
32
+ * approval precedent). Any mismatch blocks with a STALE code and the
33
+ * architect re-runs `epic_phase_review`.
34
+ */
35
+ import type { RuntimePlan } from '../config/plan-schema';
36
+ import { type ReadOnlyToolDenials } from '../evaluation/ephemeral-agent-dispatcher';
37
+ import type { ReviewModelDispatcher } from '../review/contracts';
38
+ import { type ReviewAgentModelRegistry } from '../review/runtime';
39
+ import { type EpicRecordV1, getOpenEpic } from './lifecycle';
40
+ /** Evidence filename under `.swarm/evidence/{phase}/`. */
41
+ export declare const EPIC_PHASE_REVIEW_FILENAME = "epic-phase-review.json";
42
+ /** Approval lifetime; older evidence is stale (Full-Auto phase-approval precedent). */
43
+ export declare const EPIC_PHASE_REVIEW_TTL_MS: number;
44
+ /** Tolerated forward clock skew before a timestamp is treated as forged. */
45
+ export declare const EPIC_PHASE_REVIEW_FORWARD_SKEW_MS: number;
46
+ /** Default per-role dispatch timeout (`epic.phase_review.timeout_ms`). */
47
+ export declare const EPIC_PHASE_REVIEW_DISPATCH_TIMEOUT_MS = 300000;
48
+ /**
49
+ * The configured per-role timeout (`epic.phase_review.timeout_ms`), or the
50
+ * default when it is unset or the config cannot be read (fail-open to the
51
+ * default: a timeout is a budget, not a safety gate).
52
+ */
53
+ export declare function resolveEpicPhaseReviewTimeoutMs(directory: string): number;
54
+ /** Recovery tool name advertised in every block message. */
55
+ export declare const EPIC_PHASE_REVIEW_TOOL = "epic_phase_review";
56
+ /**
57
+ * Tool map for the one empty-response retry: the shared read-only map with
58
+ * only `bash` left enabled. OpenCode Zen's free tier answers a request that
59
+ * disables `bash` with HTTP 403 ("free tier can only be used from within
60
+ * OpenCode"), and the shared dispatcher (which disables `bash` along with
61
+ * every mutating and plugin tool) surfaces ANY provider-side error as a
62
+ * `completed` response with no text (or, where it reads the assistant
63
+ * message's `info.error`, as an `error` with a `providerError`) — so the
64
+ * retry fires on every empty completion and every structured provider
65
+ * refusal, not only the Zen 403; a second refusal stays a fail-closed
66
+ * REJECTED. Every other tool stays denied, so this profile is
67
+ * still narrower than the standard per-task reviewer/critic agents (whose
68
+ * own config denies only write/edit/patch among the built-ins). Their shell
69
+ * commands pass the same guardrails, which for a scope-less non-coder role
70
+ * block unresolvable, inline-eval and deny-listed (none by default) writes;
71
+ * that is not a read-only sandbox, so the retry also tells the model not to
72
+ * change anything ({@link EPIC_PHASE_REVIEW_BASH_RETRY_NOTE}).
73
+ */
74
+ export declare const EPIC_PHASE_REVIEW_BASH_RETRY_NOTE = "You have a `bash` tool for this review. Use it only to read and inspect (for example cat, ls, grep, git log, git diff, git show). Never run a command that creates, modifies or deletes files, changes git state (checkout, stash, reset, commit), installs packages, or runs tests.";
75
+ export declare const EPIC_PHASE_REVIEW_BASH_RETRY_TOOLS: ReadOnlyToolDenials;
76
+ export type EpicPhaseVerdict = 'APPROVED' | 'NEEDS_REVISION' | 'REJECTED';
77
+ export type EpicReviewToolProfile = 'read-only' | 'read-only-with-bash';
78
+ export interface EpicRoleVerdict {
79
+ role: 'reviewer' | 'critic';
80
+ /** Resolved agent name that was dispatched (e.g. `reviewer`, `mega_critic`). */
81
+ agent: string;
82
+ verdict: EpicPhaseVerdict;
83
+ reason: string | null;
84
+ /**
85
+ * `completed` — the agent responded and a single verdict was parsed;
86
+ * `unparseable` — the agent responded without one unambiguous verdict;
87
+ * `failed` — dispatch failed after fallbacks. Both non-`completed`
88
+ * outcomes are recorded fail-closed as REJECTED.
89
+ */
90
+ dispatch: 'completed' | 'unparseable' | 'failed';
91
+ /**
92
+ * Tool profile of the dispatch that produced this verdict: `read-only`
93
+ * (the shared map) or `read-only-with-bash` (the provider-refusal retry,
94
+ * see {@link EPIC_PHASE_REVIEW_BASH_RETRY_TOOLS}). Absent on evidence
95
+ * written before the retry existed.
96
+ */
97
+ tool_profile?: EpicReviewToolProfile;
98
+ model?: string;
99
+ duration_ms?: number;
100
+ dispatched_at: string;
101
+ }
102
+ export interface EpicPhaseBinding {
103
+ /**
104
+ * Key of the open epic the review ran under (null if none was readable).
105
+ * Absent on evidence written before the field existed, which is then
106
+ * treated as stale rather than malformed.
107
+ */
108
+ epic_key?: string | null;
109
+ plan_id: string;
110
+ plan_structure_hash: string;
111
+ phase_task_ids: string[];
112
+ phase_tasks_digest: string;
113
+ task_evidence_digest: string;
114
+ }
115
+ export interface EpicPhaseReviewEvidence {
116
+ schema_version: 1;
117
+ kind: 'epic_phase_review';
118
+ phase: number;
119
+ reviewed_at: string;
120
+ /** Architect session that ran `epic_phase_review` (dispatch parent). */
121
+ parent_session_id: string;
122
+ binding: EpicPhaseBinding;
123
+ reviewer: EpicRoleVerdict;
124
+ /** null when the critic was not dispatched because the reviewer did not approve. */
125
+ critic: EpicRoleVerdict | null;
126
+ }
127
+ export type EpicPhaseReadinessCode = 'EPIC_PHASE_REVIEW_MISSING' | 'EPIC_PHASE_REVIEW_INVALID' | 'EPIC_PHASE_REVIEWER_NOT_APPROVED' | 'EPIC_PHASE_CRITIC_MISSING' | 'EPIC_PHASE_CRITIC_NOT_APPROVED' | 'EPIC_PHASE_REVIEW_STALE' | 'EPIC_PHASE_PLAN_UNREADABLE' | 'EPIC_PHASE_WAVES_OPEN';
128
+ export type EpicPhaseReadinessResult = {
129
+ ok: true;
130
+ evidence: EpicPhaseReviewEvidence;
131
+ } | {
132
+ ok: false;
133
+ code: EpicPhaseReadinessCode;
134
+ reason: string;
135
+ };
136
+ declare function taskEvidenceFingerprint(directory: string, taskId: string): string;
137
+ /**
138
+ * Compute the freshness binding for `phase` of `plan`. Returns the ids of
139
+ * tasks that are not yet `completed`/`closed` so the review tool can refuse a
140
+ * premature review.
141
+ */
142
+ export declare function computeEpicPhaseBinding(directory: string, plan: RuntimePlan, phase: number): {
143
+ ok: true;
144
+ binding: EpicPhaseBinding;
145
+ incompleteTaskIds: string[];
146
+ } | {
147
+ ok: false;
148
+ reason: string;
149
+ };
150
+ /** Strict shape check for persisted evidence (fail-closed on any drift). */
151
+ export declare function parseEpicPhaseReviewEvidence(raw: unknown): EpicPhaseReviewEvidence | null;
152
+ type EvidenceRead = {
153
+ status: 'missing';
154
+ } | {
155
+ status: 'invalid';
156
+ detail: string;
157
+ } | {
158
+ status: 'found';
159
+ evidence: EpicPhaseReviewEvidence;
160
+ };
161
+ declare function readEpicPhaseReviewEvidence(directory: string, phase: number): EvidenceRead;
162
+ /** Seqs of the epic's waves for `phase` that are issued and not closed. */
163
+ export declare function openEpicWavesForPhase(record: Pick<EpicRecordV1, 'waves'>, phase: number): number[];
164
+ /**
165
+ * Epic v2 C2: a phase is reviewable / completable only when every wave
166
+ * `epic_next_wave` issued for it is closed. Null when that holds (or no
167
+ * epic is open); otherwise the blocking reason. An unreadable epic record
168
+ * fails closed.
169
+ */
170
+ export declare function describeOpenEpicWaves(directory: string, phase: number): string | null;
171
+ /**
172
+ * Verify that `phase` has fresh, APPROVED phase-reviewer and phase-critic
173
+ * evidence and (Epic v2 C2) no open wave. Observational only: never
174
+ * dispatches or writes.
175
+ */
176
+ export declare function verifyEpicPhaseReadiness(directory: string, phase: number, nowMs: number): Promise<EpicPhaseReadinessResult>;
177
+ /**
178
+ * Parse exactly one verdict from agent response text. Every `VERDICT:` line
179
+ * is collected; zero or conflicting verdicts are unparseable (fail-closed),
180
+ * so a template echo such as `VERDICT: APPROVED | REJECTED` cannot count.
181
+ */
182
+ export declare function parseEpicPhaseVerdict(text: string): {
183
+ verdict: EpicPhaseVerdict;
184
+ reason: string | null;
185
+ } | null;
186
+ export interface EpicPhaseReviewOptions {
187
+ dispatcher?: ReviewModelDispatcher;
188
+ generatedAgentNames?: readonly string[];
189
+ activeAgentName?: string;
190
+ agentModelRegistry?: ReviewAgentModelRegistry;
191
+ timeoutMs?: number;
192
+ }
193
+ export type EpicPhaseReviewRunResult = {
194
+ success: true;
195
+ phase: number;
196
+ ready: boolean;
197
+ reviewer: EpicRoleVerdict;
198
+ critic: EpicRoleVerdict | null;
199
+ evidencePath: string;
200
+ message: string;
201
+ } | {
202
+ success: false;
203
+ phase: number;
204
+ reason: 'dispatcher-unavailable' | 'no-plan' | 'no-phase' | 'tasks-incomplete' | 'agent-resolution-failed' | 'persist-failed';
205
+ message: string;
206
+ };
207
+ /**
208
+ * Dispatch the phase reviewer, then (only when it APPROVES) the phase critic,
209
+ * and persist the bound verdicts for the phase_complete Epic readiness gate.
210
+ */
211
+ export declare function runEpicPhaseReview(directory: string, phase: number, parentSessionId: string, options?: EpicPhaseReviewOptions): Promise<EpicPhaseReviewRunResult>;
212
+ /** Dependency-injection seam (AGENTS.md invariant 7); restore in afterEach. */
213
+ export declare const _internals: {
214
+ loadPlan: (directory: string) => Promise<RuntimePlan | null>;
215
+ readEvidence: typeof readEpicPhaseReviewEvidence;
216
+ taskEvidenceFingerprint: typeof taskEvidenceFingerprint;
217
+ writeEvidence: (target: string, content: string) => Promise<void>;
218
+ now: () => number;
219
+ getOpenEpic: typeof getOpenEpic;
220
+ };
221
+ export {};