opencode-swarm 7.189.0 → 7.190.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-qvk04fya.js} +9 -9
  7. package/dist/cli/{checkpoint-m7q9886q.js → checkpoint-9q2r5c3j.js} +18 -20
  8. package/dist/cli/{coder-settlement-hh81z2f7.js → coder-settlement-xcparbf3.js} +13 -13
  9. package/dist/cli/{config-doctor-0rexy3xt.js → config-doctor-by0ak49m.js} +23 -20
  10. package/dist/cli/{core-ays6stz4.js → core-b1b3kxzy.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-d7b88mzr.js} +44 -44
  13. package/dist/cli/{curator-drift-nts5atks.js → curator-drift-6v604220.js} +9 -9
  14. package/dist/cli/curator-llm-factory-yvbktznk.js +82 -0
  15. package/dist/cli/{doctor-knowledge-receipt-phase-id-ec40k5hn.js → doctor-knowledge-receipt-phase-id-kwrvnps9.js} +9 -9
  16. package/dist/cli/{evidence-summary-service-vgtm72g5.js → evidence-summary-service-mzr2q4bn.js} +21 -22
  17. package/dist/cli/{gate-evidence-vmn65kxe.js → gate-evidence-78jes9kq.js} +8 -8
  18. package/dist/cli/guardrail-explain-8kt2mxmw.js +83 -0
  19. package/dist/cli/{guardrail-log-hc8fjm4x.js → guardrail-log-2sthykag.js} +8 -8
  20. package/dist/cli/{guardrail-reset-ct00w300.js → guardrail-reset-2n746rzv.js} +44 -44
  21. package/dist/cli/{hive-promoter-2xax6van.js → hive-promoter-s7a5gwhz.js} +44 -44
  22. package/dist/cli/{index-cbqkag7q.js → index-0qc7a88d.js} +3 -3
  23. package/dist/cli/{index-g03m5jhw.js → index-1s95jrgc.js} +3 -3
  24. package/dist/cli/{index-9tvjwv01.js → index-2fpv2wmc.js} +1 -1
  25. package/dist/cli/{index-2btybj8w.js → index-2kpqqcch.js} +4 -4
  26. package/dist/cli/{index-3dx0c7ve.js → index-3t9t9q2q.js} +44 -44
  27. package/dist/cli/{index-8q8783g0.js → index-3zwhxk98.js} +1 -1
  28. package/dist/cli/{index-34vsyj15.js → index-49e65y51.js} +5 -5
  29. package/dist/cli/{index-5v09zw1q.js → index-69tgdm9t.js} +18 -8
  30. package/dist/cli/{index-d3rjvm9e.js → index-7vnr5cmp.js} +2 -2
  31. package/dist/cli/{index-twb3f4tk.js → index-8bx6vhzk.js} +1 -1
  32. package/dist/cli/{index-nhsafbrh.js → index-9gcqmgbc.js} +5 -5
  33. package/dist/cli/{index-4p3jh14t.js → index-9xxqdzp4.js} +4 -4
  34. package/dist/cli/{index-qx3g1bbe.js → index-a5ca62vt.js} +1 -1
  35. package/dist/cli/{index-bskex45z.js → index-ajzgeb2s.js} +2 -2
  36. package/dist/cli/{index-8xabhfwr.js → index-b2j231jy.js} +2 -2
  37. package/dist/cli/{index-cj4mp0fm.js → index-dmq7p8s9.js} +6 -6
  38. package/dist/cli/{index-bzbxn24k.js → index-ej1hdw16.js} +5 -5
  39. package/dist/cli/{index-waxvpwbv.js → index-ejk34dnw.js} +127 -13
  40. package/dist/cli/{index-skdpgjar.js → index-fb5s32c2.js} +862 -1221
  41. package/dist/cli/{index-ntda6tje.js → index-feaxsjrt.js} +10 -2
  42. package/dist/cli/{index-2nexf5vz.js → index-ffgcdf49.js} +22147 -15396
  43. package/dist/cli/{index-kqegvngt.js → index-h0xhec3d.js} +4 -4
  44. package/dist/cli/{index-kaqdzhj3.js → index-kfevm0n5.js} +2 -2
  45. package/dist/cli/{index-4a7j5f60.js → index-kp8k95v9.js} +4 -4
  46. package/dist/cli/{index-x1eh2636.js → index-mcv0v2dp.js} +1 -1
  47. package/dist/cli/{index-9czp2dcg.js → index-mjfpaxw3.js} +7 -7
  48. package/dist/cli/{index-cdjxzs2r.js → index-mp1s7b0n.js} +7 -7
  49. package/dist/cli/{index-z29hksyw.js → index-mseerehd.js} +26 -10
  50. package/dist/cli/{index-f6exny9m.js → index-n8q43eff.js} +2 -2
  51. package/dist/cli/{index-7hr5m7yp.js → index-nkgzkt7c.js} +1 -1
  52. package/dist/cli/{index-9j3ybyfs.js → index-npe10823.js} +7 -7
  53. package/dist/cli/{index-16m2m4ah.js → index-p69k8v0e.js} +1 -1
  54. package/dist/cli/{index-p2tqzwh6.js → index-qs9myayh.js} +5 -10
  55. package/dist/cli/{index-hh7pa3yk.js → index-r9yvpr2m.js} +2 -2
  56. package/dist/cli/{index-w4zscz6p.js → index-rgkzcw2a.js} +5 -5
  57. package/dist/cli/{index-zfbxfm2m.js → index-s8b67e6h.js} +33 -12
  58. package/dist/cli/{index-9gqb5nkj.js → index-s92a5ta3.js} +4 -4
  59. package/dist/cli/{index-89f56ej4.js → index-sm01zpqe.js} +46 -46
  60. package/dist/cli/{index-q9kefh33.js → index-sxj143a4.js} +1 -1
  61. package/dist/cli/{index-1cwmj1gs.js → index-t0gj8s46.js} +77 -16
  62. package/dist/cli/{index-0gejy4cx.js → index-t73adn9b.js} +2 -2
  63. package/dist/cli/{index-fbgjj7h5.js → index-tkhenmms.js} +101 -13
  64. package/dist/cli/{index-zsrtt42s.js → index-v3bmwmqw.js} +25 -1
  65. package/dist/cli/{index-wqa31xnf.js → index-vhn4e6rm.js} +7 -7
  66. package/dist/cli/{index-5cfhwd65.js → index-xyvrfram.js} +2 -2
  67. package/dist/cli/{index-k8sarme1.js → index-y9v41185.js} +1 -1
  68. package/dist/cli/{index-1xa8wq76.js → index-z3ahy9c2.js} +2 -2
  69. package/dist/cli/{index-wrdktgpy.js → index-zk6ttjkt.js} +2 -2
  70. package/dist/cli/{index-mfxef20k.js → index-zyg1m9d0.js} +4 -4
  71. package/dist/cli/index.js +45 -45
  72. package/dist/cli/{knowledge-escalator-v3c8w86g.js → knowledge-escalator-n1ezv1zp.js} +15 -15
  73. package/dist/cli/{knowledge-events-kagaccj9.js → knowledge-events-nyqk3mr7.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-mnxqcyet.js} +6 -6
  76. package/dist/cli/{knowledge-validator-210gkpec.js → knowledge-validator-z30113mp.js} +8 -8
  77. package/dist/cli/{mcp-58hgx6av.js → mcp-mnbqw2s5.js} +1 -1
  78. package/dist/cli/{model-preflight-e69b48n0.js → model-preflight-ex482fsy.js} +44 -44
  79. package/dist/cli/{pending-delegations-g24d3qa9.js → pending-delegations-j18afz5z.js} +12 -12
  80. package/dist/cli/{pr-subscriptions-qprh2zsm.js → pr-subscriptions-yngda8mz.js} +12 -12
  81. package/dist/cli/{pr-workflow-gate-pj7dhkv4.js → pr-workflow-gate-0vt614f0.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-8pwfg5g9.js} +7 -7
  84. package/dist/cli/{schema-smgngq9m.js → schema-e58ytmqt.js} +14 -2
  85. package/dist/cli/{scope-persistence-tbv2ar4w.js → scope-persistence-9qfx6ext.js} +16 -16
  86. package/dist/cli/{server-7p2ds5w2.js → server-wxcfwsdx.js} +45 -45
  87. package/dist/cli/{skill-generator-r4hsne71.js → skill-generator-tbd4tq9r.js} +17 -17
  88. package/dist/cli/{snapshot-coordination-init-ydjwh56d.js → snapshot-coordination-init-sckyzb94.js} +44 -44
  89. package/dist/cli/{speckit-checkoff-aj75vmjq.js → speckit-checkoff-k5jm0abh.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-3wwfc72t.js} +14 -14
  93. package/dist/cli/{worktree-isolation-h0c22pm2.js → worktree-isolation-zfk7zk6a.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,104 @@
1
+ /**
2
+ * Plan identity for Epic commits (Epic v2 C0, reshaped in C3).
3
+ *
4
+ * Every commit Epic writes for a task — the worktree LANDING merge commit of
5
+ * its coder and the RESIDUE commit of a non-coder writer (test_engineer,
6
+ * docs, …) — has the subject `swarm(task <id>): …` and a final
7
+ * `Swarm-Plan: <planKey>` trailer, where
8
+ * `planKey = sha256(planIdentityHash + '|' + (planEpoch ?? '')).slice(0, 16)`.
9
+ * The plan epoch is minted per ledger root, so two consecutive plans with the
10
+ * same title (every plan has a `1.1`) get different keys. Task ids repeat
11
+ * across plans, so a commit is attributed to a task only when its trailer
12
+ * names the current plan.
13
+ *
14
+ * Predecessor evidence no longer greps history: wave close records each
15
+ * task's commit in the epic record and mirrors it to the ref
16
+ * `refs/swarm/epics/<epicKey>/tasks/<id>` (`markers.ts`). The marker
17
+ * subject + trailer remain the way wave close and `/swarm epic status
18
+ * --repair-refs` find a task's commit inside the epic's own commit range.
19
+ *
20
+ * Records are NUL-separated (`git log -z`); git refuses NUL bytes in commit
21
+ * messages, so a message body cannot forge a record boundary.
22
+ *
23
+ * Subprocess discipline: no git call is made here; readers go through
24
+ * `src/git/branch.ts` `gitExec` (AGENTS.md #3).
25
+ */
26
+ import type { Plan } from '../config/plan-schema.js';
27
+ import { readLedgerEvents as readLedgerEvents_import, readPlanEpochIdentity as readPlanEpochIdentity_import } from '../plan/ledger.js';
28
+ /** The commit-message trailer key binding a marker to its plan. */
29
+ export declare const SWARM_PLAN_TRAILER_KEY = "Swarm-Plan";
30
+ /** Identity of the plan whose markers may be honored. */
31
+ export interface PlanMarkerScope {
32
+ /** 16-hex-char key written into the `Swarm-Plan:` trailer. */
33
+ planKey: string;
34
+ /**
35
+ * Earliest plan-ledger event time (ms since epoch), or null when the
36
+ * ledger is absent / carries no parseable timestamp.
37
+ */
38
+ rootTimestampMs: number | null;
39
+ }
40
+ /** One `swarm(task <id>):` commit read back from git. */
41
+ export interface ParsedTaskMarker {
42
+ /** Full commit id (`%H`). */
43
+ sha: string;
44
+ taskId: string;
45
+ /** Last `Swarm-Plan:` trailer value; null when the commit carries none. */
46
+ planKey: string | null;
47
+ }
48
+ /** Subject shape produced by {@link formatEpicTaskCommitMessage}. */
49
+ export declare const SWARM_TASK_SUBJECT_RE: RegExp;
50
+ /**
51
+ * `git log -z --format` producing `<sha> US <raw message>` per commit,
52
+ * records separated by NUL (`-z`).
53
+ */
54
+ export declare const MARKER_LOG_FORMAT = "--format=%H%x1f%B";
55
+ /** Bound on scope resolution (ledger reads) so status never hangs. */
56
+ export declare const PLAN_SCOPE_RESOLVE_TIMEOUT_MS = 10000;
57
+ /** Max (directory, planKey) entries in the root-timestamp cache (FIFO). */
58
+ export declare const MAX_ROOT_TS_CACHE_ENTRIES = 16;
59
+ /**
60
+ * Defensive taskId scrubber for commit-subject and grep-pattern use (Phase
61
+ * 17 C.H2). Plan-ledger dep ids flow here from LLM-authored plans without
62
+ * re-validation; a `)` or newline would corrupt the subject regex. Keeps
63
+ * only characters safe in both a git subject and an ERE literal.
64
+ */
65
+ export declare function scrubTaskIdForGitSubject(taskId: string): string;
66
+ /** `sha256(planIdentityHash + '|' + (planEpoch ?? '')).slice(0, 16)`. */
67
+ export declare function computePlanKey(planIdentityHash: string, planEpoch: string | null): string;
68
+ /** The trailer line closing every Epic task commit message. */
69
+ export declare function formatSwarmPlanTrailer(planKey: string): string;
70
+ /**
71
+ * The message of an Epic task commit (landing merge or residue): subject
72
+ * `swarm(task <id>): <summary>` — the summary whitespace-collapsed and
73
+ * truncated to keep the subject within git's conventional 72 columns — and a
74
+ * final `Swarm-Plan: <planKey>` trailer. The task id is scrubbed by
75
+ * {@link scrubTaskIdForGitSubject}. Treat the shape as a stable contract.
76
+ */
77
+ export declare function formatEpicTaskCommitMessage(taskId: string, planKey: string, summary?: string): string;
78
+ /**
79
+ * Resolve the plan scope (planKey + plan root time) for `plan` — used by
80
+ * `/swarm epic status` to date recorded merge failures. Throws when the
81
+ * ledger identity is invalid/conflicting or the read exceeds
82
+ * {@link PLAN_SCOPE_RESOLVE_TIMEOUT_MS}; the caller then reports the root as
83
+ * unknown (every failure blocking).
84
+ */
85
+ export declare function resolvePlanMarkerScope(directory: string, plan: Plan): Promise<PlanMarkerScope>;
86
+ /**
87
+ * Parse `git log -z` output produced with {@link MARKER_LOG_FORMAT}. Records
88
+ * whose subject is not a `swarm(task <id>):` marker are dropped (a squash
89
+ * message may quote a marker in its body).
90
+ */
91
+ export declare function parseTaskMarkerLog(output: string): ParsedTaskMarker[];
92
+ /** Test-only: number of cached root timestamps. */
93
+ export declare function _rootTimestampCacheSizeForTest(): number;
94
+ /** Test-only: clear the root-timestamp cache. */
95
+ export declare function _resetRootTimestampCacheForTest(): void;
96
+ /**
97
+ * DI seam (AGENTS.md invariant 7) — tests substitute ledger readers and the
98
+ * resolution deadline without `mock.module`. Restore in `afterEach`.
99
+ */
100
+ export declare const _internals: {
101
+ readLedgerEvents: typeof readLedgerEvents_import;
102
+ readPlanEpochIdentity: typeof readPlanEpochIdentity_import;
103
+ resolveTimeoutMs: number;
104
+ };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Epic v2 C7 — the `save_plan` plan-shaping seam (Epic side).
3
+ *
4
+ * `save_plan` calls {@link computeSavePlanEpicShaping} ONLY when the config
5
+ * it already loaded has `epic.mode.enabled === true`, AFTER the plan
6
+ * lock is released, inside its own try/catch (a throw ⇒ the save still
7
+ * succeeds, without `epic_shaping`). With Epic off nothing here runs: no
8
+ * config read, no I/O, no await (the seam is in `src/tools/save-plan.ts`).
9
+ *
10
+ * What it does (Epic on):
11
+ * - nothing while an epic is open for the project: shaping is pre-start
12
+ * advice; a plan revised mid-epic (e.g. fix tasks) is planned by
13
+ * `epic_next_wave`;
14
+ * - inputs as `/swarm epic start` sizes the plan: live declared scopes,
15
+ * learned signals from the project prior, the wave width
16
+ * (`max_parallel_coders` in a git project, 1 otherwise — detected by a
17
+ * `.git` entry in the project root or an ancestor, no git subprocess),
18
+ * and co-change from the WARM in-memory cache only
19
+ * (`peekCoChangeData`; no git scan on a plan save). Cold ⇒ path-only,
20
+ * flagged `cochange: 'cold'` (r2 critic MINOR 12: the cache is filled by
21
+ * `/swarm epic start`, `/swarm coupling` and `epic_next_wave`, so a
22
+ * save_plan before any of them shapes path-only);
23
+ * - `shapeEpicPlan` (pure, bounded by its budget);
24
+ * - an iteration counter per plan identity in `.swarm/epic/shaping.json`
25
+ * (a different plan restarts at 1); from iteration
26
+ * {@link SHAPING_ACCEPT_ITERATION} the next step says to accept the plan
27
+ * and proceed rather than reshape again.
28
+ *
29
+ * Result (`SavePlanResult.epic_shaping`): a one-line advisory when the plan
30
+ * is not epic-sized and no suggestion fixes that (r2 critic M5 — "run it in
31
+ * Balanced") or when it is over the shaping budget; otherwise the full
32
+ * advisory with ranked suggestions, a `next_step` and the iteration.
33
+ */
34
+ import type { Plan } from '../config/plan-schema.js';
35
+ import type { PluginConfig } from '../config/schema.js';
36
+ import { loadPlanJsonOnly } from '../plan/manager.js';
37
+ import { peekCoChangeData } from './cochange-source.js';
38
+ import { resolveEpicDeclaredScopes } from './declared-scopes.js';
39
+ import { loadEpicLearningView } from './learning-store.js';
40
+ import { isEpicOpenForProject } from './lifecycle.js';
41
+ import { type EpicShapingReport, shapeEpicPlan } from './shaping.js';
42
+ import type { EpicShapingSuggestion } from './shaping-suggestions.js';
43
+ /** `.swarm/epic/shaping.json` — the iteration counter. */
44
+ export declare const EPIC_SHAPING_RELATIVE_PATH: string;
45
+ /** From this iteration on, `next_step` says accept and proceed. */
46
+ export declare const SHAPING_ACCEPT_ITERATION = 3;
47
+ export type EpicShapingCochangeState = 'warm' | 'cold' | 'disabled';
48
+ /**
49
+ * One suggestion as `save_plan` reports it: the shaping suggestion with
50
+ * every key in snake_case (`task_ids`, `file_kind`, `edge_share`,
51
+ * `patch.new_task`, `patch.edits[].task_id`, …), its ΔS_eff as
52
+ * `delta_effective_speedup` and the what-if as `what_if`.
53
+ */
54
+ export type EpicShapingAdvisorySuggestion = {
55
+ type: EpicShapingSuggestion['type'];
56
+ summary: string;
57
+ delta_effective_speedup: number | null;
58
+ what_if: {
59
+ effective_speedup: number;
60
+ epic_sized: boolean;
61
+ } | null;
62
+ } & Record<string, unknown>;
63
+ export interface EpicShapingAdvisory {
64
+ status: 'acceptable' | 'improvable';
65
+ epic_sized: boolean;
66
+ effective_speedup: number;
67
+ pending_tasks: number;
68
+ serial_steps: number;
69
+ /** Sizing reasons (empty when epic-sized). */
70
+ reasons: string[];
71
+ cochange: EpicShapingCochangeState;
72
+ suggestions: EpicShapingAdvisorySuggestion[];
73
+ iteration: number;
74
+ next_step: string;
75
+ }
76
+ /** `SavePlanResult.epic_shaping`. */
77
+ export type EpicSavePlanShaping = {
78
+ status: 'not-epic-sized';
79
+ message: string;
80
+ cochange: EpicShapingCochangeState;
81
+ } | {
82
+ status: 'skipped-budget';
83
+ message: string;
84
+ cochange: EpicShapingCochangeState;
85
+ } | EpicShapingAdvisory;
86
+ /**
87
+ * Count this save as one shaping iteration of the plan (identity hash +
88
+ * ledger root ⇒ a new plan or a re-rooted ledger restarts at 1). A write
89
+ * failure is logged; the advisory still carries the computed iteration.
90
+ */
91
+ export declare function bumpShapingIteration(directory: string, plan: Plan): number;
92
+ /** The `epic_shaping` value of one shaping report. */
93
+ export declare function toSavePlanShaping(report: EpicShapingReport, iteration: number, cochange: EpicShapingCochangeState): EpicSavePlanShaping;
94
+ /**
95
+ * The `epic_shaping` of a saved plan, or null when an epic is open. The
96
+ * caller has checked the Epic config gate; may throw (the caller fails
97
+ * open). Shapes the PERSISTED plan (`plan.json` as save_plan just wrote it
98
+ * — statuses preserved, current_phase normalized), falling back to the
99
+ * plan the caller saved when it cannot be read.
100
+ */
101
+ export declare function computeSavePlanEpicShaping(directory: string, savedArgsPlan: Plan, config: PluginConfig): Promise<EpicSavePlanShaping | null>;
102
+ /** Test-only DI seam (AGENTS.md invariant 7). */
103
+ export declare const _internals: {
104
+ isEpicOpenForProject: typeof isEpicOpenForProject;
105
+ loadPlanJsonOnly: typeof loadPlanJsonOnly;
106
+ peekCoChangeData: typeof peekCoChangeData;
107
+ loadEpicLearningView: typeof loadEpicLearningView;
108
+ resolveEpicDeclaredScopes: typeof resolveEpicDeclaredScopes;
109
+ shapeEpicPlan: typeof shapeEpicPlan;
110
+ existsSync: (target: string) => boolean;
111
+ now: () => number;
112
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Epic v2 C5/C6 — the planner's inputs beyond the plan, read ONCE per
3
+ * planning call by both callers of the component planner (`epic_next_wave`
4
+ * and the `/swarm epic start` sizing dry-run) so the two plan with the same
5
+ * signals:
6
+ *
7
+ * - learned signals (`learning.ts`) when `epic.learning.enabled` is
8
+ * not false: the hot files (tasks declaring one run alone) and the
9
+ * co-write weights that expand each task's scope in the conflict graph.
10
+ * Read from the open epic's posterior (`epic_next_wave`), else from the
11
+ * project prior (the start's sizing, before the epic exists);
12
+ * - co-change: the git co-change pairs + threshold when
13
+ * `epic.cochange.enabled` (null otherwise ⇒ path-only conflicts);
14
+ * - density threshold: the static `epic.mode.activation_threshold`
15
+ * — the intra-component density above which a component runs serially.
16
+ *
17
+ * A failed read degrades to "no signal" (no learned signal / path-only),
18
+ * the fail-open the planner always had for these optional signals; the
19
+ * wave verdict still guards every issued wave. The sources are injected so
20
+ * each caller keeps its own `_internals` DI seam.
21
+ */
22
+ import type { PluginConfig } from '../config/schema.js';
23
+ import type { CoChangeData } from './cochange-source.js';
24
+ import { type EpicCochangeSignal } from './components.js';
25
+ import { type EpicCoWriteIndex, type EpicLearningSettings } from './learning.js';
26
+ import type { EpicIdentity, EpicLearningView } from './learning-store.js';
27
+ export interface EpicPlanningSignals {
28
+ hotFiles: string[];
29
+ coWrites: EpicCoWriteIndex | null;
30
+ cochange: EpicCochangeSignal | null;
31
+ densityThreshold: number;
32
+ }
33
+ export interface EpicPlanningSignalSources {
34
+ loadLearningView: (directory: string, epic: EpicIdentity | null, settings: EpicLearningSettings, nowMs: number) => EpicLearningView;
35
+ getCoChangeData: (directory: string) => Promise<CoChangeData>;
36
+ now: () => number;
37
+ }
38
+ export declare function loadEpicPlanningSignals(directory: string, config: Pick<PluginConfig, 'turbo' | 'epic'>, sources: EpicPlanningSignalSources, epic: EpicIdentity | null): Promise<EpicPlanningSignals>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `/swarm epic report [<key>|last] [--format json]` (Epic v2 C8): the
3
+ * scorecard (`scorecard.ts`) of the open epic, computed live from its
4
+ * record, or of a past epic, read from its close report in
5
+ * `.swarm/epic-prior/reports/` (kept across `/swarm close`, newest 50).
6
+ *
7
+ * Selection: no selector ⇒ the open epic when one is open, else the newest
8
+ * report; `last` ⇒ the newest report; `<key>` ⇒ the report `<key>.json`
9
+ * (a report key) or the newest report of epic `<key>` (an epic key).
10
+ * "Newest" is by file mtime (as the 50-report pruning), then name.
11
+ *
12
+ * Strict reader: a report is read only when it is an `epic-report-v2`
13
+ * report whose embedded scorecard validates group by group (every field the
14
+ * formatter reads, {@link EpicScorecardSchema}); anything else is reported
15
+ * as unreadable, never half-rendered.
16
+ *
17
+ * Read-only and bounded: one directory listing, `stat` per report, and one
18
+ * report read (≤ {@link MAX_EPIC_REPORT_BYTES}); the live view is sentinel
19
+ * first (no sentinel ⇒ no DB open). Like `/swarm epic status` it works
20
+ * regardless of the Epic config gate and for an orphaned epic (both noted).
21
+ */
22
+ import { z } from 'zod';
23
+ import { loadPlanJsonOnly } from '../plan/manager.js';
24
+ import { type EpicLearningSettings } from './learning.js';
25
+ import { loadEpicLearningView } from './learning-store.js';
26
+ import { type EpicRecordV1, epicSentinelExists, inspectEpic } from './lifecycle.js';
27
+ import { completedBeforeEpic } from './next-wave.js';
28
+ import { type EpicScorecardV1 } from './scorecard.js';
29
+ /** Largest close report read (reports are bounded by the record). */
30
+ export declare const MAX_EPIC_REPORT_BYTES: number;
31
+ /** DI seam (AGENTS.md invariant 7). Restore in `afterEach`. */
32
+ export declare const _internals: {
33
+ epicSentinelExists: typeof epicSentinelExists;
34
+ inspectEpic: typeof inspectEpic;
35
+ loadPlanJsonOnly: typeof loadPlanJsonOnly;
36
+ completedBeforeEpic: typeof completedBeforeEpic;
37
+ loadEpicLearningView: typeof loadEpicLearningView;
38
+ now: () => number;
39
+ };
40
+ export type EpicReportSelection = {
41
+ status: 'ok';
42
+ source: 'live' | 'report';
43
+ /** Null for the live scorecard. */
44
+ reportKey: string | null;
45
+ scorecard: EpicScorecardV1;
46
+ /** Caveats shown with a live scorecard (orphaned, config off). */
47
+ notes?: string[];
48
+ } | {
49
+ status: 'none';
50
+ message: string;
51
+ } | {
52
+ status: 'error';
53
+ message: string;
54
+ };
55
+ /** Report file names in `.swarm/epic-prior/reports/`, newest first. */
56
+ export declare function listEpicPriorReports(directory: string): string[];
57
+ /**
58
+ * Every scorecard group the formatter and the JSON consumers read. Unknown
59
+ * keys are allowed (a newer writer may add fields); a missing or mistyped
60
+ * known field makes the report unreadable.
61
+ */
62
+ export declare const EpicScorecardSchema: z.ZodObject<{
63
+ schema: z.ZodLiteral<"epic-scorecard-v1">;
64
+ epicKey: z.ZodString;
65
+ planId: z.ZodString;
66
+ outcome: z.ZodEnum<{
67
+ completed: "completed";
68
+ open: "open";
69
+ abandoned: "abandoned";
70
+ "abandoned-by-swarm-close": "abandoned-by-swarm-close";
71
+ }>;
72
+ startedAt: z.ZodString;
73
+ closedAt: z.ZodNullable<z.ZodString>;
74
+ forced: z.ZodBoolean;
75
+ sizingAtStart: z.ZodObject<{
76
+ epicSized: z.ZodBoolean;
77
+ reasons: z.ZodArray<z.ZodString>;
78
+ pendingTasks: z.ZodNumber;
79
+ scopedTasks: z.ZodNumber;
80
+ scopeCoverage: z.ZodNumber;
81
+ serialSteps: z.ZodNumber;
82
+ concurrency: z.ZodNumber;
83
+ effectiveSpeedup: z.ZodNumber;
84
+ }, z.core.$strip>;
85
+ tasks: z.ZodObject<{
86
+ total: z.ZodNullable<z.ZodNumber>;
87
+ completedInEpic: z.ZodNumber;
88
+ adoptedAtStart: z.ZodOptional<z.ZodNumber>;
89
+ exclusive: z.ZodNumber;
90
+ serialComponent: z.ZodNumber;
91
+ }, z.core.$strip>;
92
+ waves: z.ZodObject<{
93
+ count: z.ZodNumber;
94
+ parallel: z.ZodNumber;
95
+ meanWidth: z.ZodNumber;
96
+ maxWidth: z.ZodNumber;
97
+ }, z.core.$strip>;
98
+ time: z.ZodObject<{
99
+ method: z.ZodLiteral<"wave-span-v1">;
100
+ label: z.ZodString;
101
+ spanMs: z.ZodNumber;
102
+ workMs: z.ZodNumber;
103
+ concurrencyFactor: z.ZodNullable<z.ZodNumber>;
104
+ interWaveIdleMs: z.ZodNumber;
105
+ }, z.core.$strip>;
106
+ conflicts: z.ZodObject<{
107
+ mergeFailures: z.ZodNumber;
108
+ undeclaredWriteTasks: z.ZodNumber;
109
+ undeclaredFiles: z.ZodArray<z.ZodString>;
110
+ undeclaredFilesTotal: z.ZodNumber;
111
+ }, z.core.$strip>;
112
+ rework: z.ZodObject<{
113
+ tasksWithRework: z.ZodNumber;
114
+ extraGenerations: z.ZodNumber;
115
+ reopened: z.ZodNumber;
116
+ }, z.core.$strip>;
117
+ gates: z.ZodObject<{
118
+ stageAFirstPass: z.ZodObject<{
119
+ passed: z.ZodNumber;
120
+ of: z.ZodNumber;
121
+ rate: z.ZodNullable<z.ZodNumber>;
122
+ }, z.core.$strip>;
123
+ stageBFirstPass: z.ZodObject<{
124
+ passed: z.ZodNumber;
125
+ of: z.ZodNumber;
126
+ rate: z.ZodNullable<z.ZodNumber>;
127
+ }, z.core.$strip>;
128
+ phaseReviewFirstPass: z.ZodObject<{
129
+ passed: z.ZodNumber;
130
+ of: z.ZodNumber;
131
+ rate: z.ZodNullable<z.ZodNumber>;
132
+ }, z.core.$strip>;
133
+ boundedHistory: z.ZodLiteral<true>;
134
+ }, z.core.$strip>;
135
+ learning: z.ZodObject<{
136
+ priorDigest: z.ZodNullable<z.ZodString>;
137
+ topHotFiles: z.ZodArray<z.ZodString>;
138
+ }, z.core.$strip>;
139
+ }, z.core.$strip>;
140
+ /**
141
+ * The scorecard of a parsed close report, or an error naming the first
142
+ * problem (wrong schema, or the scorecard field that failed validation).
143
+ */
144
+ export declare function scorecardFromReport(raw: unknown): {
145
+ scorecard: EpicScorecardV1;
146
+ } | {
147
+ error: string;
148
+ };
149
+ /** The live scorecard of the epic record `epic` (open or closing). */
150
+ export declare function liveEpicScorecard(directory: string, epic: EpicRecordV1, learning: EpicLearningSettings): Promise<EpicScorecardV1>;
151
+ /** Resolve `selector` (null / `last` / a report or epic key). */
152
+ export declare function selectEpicReport(directory: string, selector: string | null, learning: EpicLearningSettings): Promise<EpicReportSelection>;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Epic v2 C3 (X1) — residue commits for non-coder writers.
3
+ *
4
+ * Coders of an open git epic are isolated in worktrees and their work lands
5
+ * as a commit (`task-landing.ts`). Other agents write in the MAIN tree: the
6
+ * test_engineer writes the task's tests, the docs agent writes docs, and so
7
+ * on. Left uncommitted, those files are invisible to a rework coder (its
8
+ * worktree is cut from HEAD) and collide with its landing. So:
9
+ *
10
+ * - when a non-coder delegation for a task of the open epic returns —
11
+ * a foreground Task (the delegation gate's after-hook) or a background
12
+ * one (the completion observer) — {@link commitEpicResidueAfterDelegation}
13
+ * commits the main-tree writes still uncommitted on the epic branch:
14
+ * the returning agent's own recorded writes as
15
+ * `swarm(task <id>): <agent> residue`, then the rest of the task's
16
+ * attribution and frozen scope as `swarm(task <id>): residue` (no
17
+ * agent is claimed for files it did not record), each with the plan's
18
+ * `Swarm-Plan:` trailer. Files another still-running delegation has
19
+ * recorded (the concurrent Stage B reviewer/test_engineer) are left
20
+ * alone: they belong to that writer and may be half-written;
21
+ * - `epic_next_wave` runs {@link commitTaskResidue} again for each task of
22
+ * the closing wave (a missed after-hook, a failed attempt); before a new
23
+ * wave the remaining dirty paths belong to no task
24
+ * ({@link classifyDirtyBaseline}).
25
+ *
26
+ * Attribution of a path to a task: the task's declared scope in the CURRENT
27
+ * (or closing) wave — literal files or directories, never globs — its write
28
+ * attribution in any session of this project, or, for the after-hook, any
29
+ * write recorded on the delegated agent's own child session. Scopes of
30
+ * earlier waves never attribute anything (later edits there are the
31
+ * user's). Never `.swarm/` (at any depth), never git pathspec magic; staging
32
+ * and the commit use `:(literal)` pathspecs, and the commit is `--only` so
33
+ * anything else in the index stays out of it. Every residue write is
34
+ * serialized with worktree merge-backs (one writer of the primary index at
35
+ * a time), and a failed commit restores the touched index entries exactly
36
+ * (the user's own staged state included).
37
+ *
38
+ * Subprocess discipline (AGENTS.md #3): status is read through `gitExec`;
39
+ * `git add` / `git commit` (and the index restore after a failed commit) run ONCE
40
+ * each through `gitExecOnce`; the commit is non-interactive
41
+ * (`--no-verify`, `-c commit.gpgsign=false`, closed stdin, timeout).
42
+ * Fail-open: every failure is a critical warning — the after-hook must never
43
+ * break, and `epic_next_wave` turns a persistent failure into `git-failed`.
44
+ */
45
+ import { getAgentSession as getAgentSession_import } from '../state.js';
46
+ import { checkEpicBranch as checkEpicBranch_import } from './epic-branch.js';
47
+ import { gitExecOnce } from './git-once.js';
48
+ import { type EpicRecordV1, epicSentinelExists as epicSentinelExists_import, getOpenEpic as getOpenEpic_import, readPlanTaskRef as readPlanTaskRef_import } from './lifecycle.js';
49
+ import { collectTaskAttribution as collectTaskAttribution_import } from './wave-close.js';
50
+ /** DI seam (AGENTS.md invariant 7). Restore in `afterEach`. */
51
+ export declare const _internals: {
52
+ epicSentinelExists: typeof epicSentinelExists_import;
53
+ getOpenEpic: typeof getOpenEpic_import;
54
+ readPlanTaskRef: typeof readPlanTaskRef_import;
55
+ checkEpicBranch: typeof checkEpicBranch_import;
56
+ collectTaskAttribution: typeof collectTaskAttribution_import;
57
+ getAgentSession: typeof getAgentSession_import;
58
+ /** Every in-process agent session (id → state), for in-flight writers. */
59
+ listAgentSessions: () => MapIterator<[string, import("../state.js").AgentSessionState]>;
60
+ gitExec: (args: string[], cwd: string) => string;
61
+ gitExecOnce: typeof gitExecOnce;
62
+ /** One writer at a time in the primary checkout (merge-back queue). */
63
+ serializeWithMergeBacks: <T>(task: () => T | Promise<T>) => Promise<T>;
64
+ now: () => number;
65
+ };
66
+ /** True for a path inside a `.swarm/` directory at any depth. */
67
+ export declare function isSwarmStatePath(file: string): boolean;
68
+ /** One uncommitted path outside `.swarm/` (porcelain v1). */
69
+ export interface DirtyEntry {
70
+ path: string;
71
+ untracked: boolean;
72
+ /**
73
+ * False when the change is fully staged already (worktree column blank):
74
+ * `git add` must skip it (a staged deletion no longer matches a pathspec).
75
+ */
76
+ needsAdd?: boolean;
77
+ /** Source path of a rename/copy record (its deletion is staged). */
78
+ origin?: string;
79
+ }
80
+ /**
81
+ * Uncommitted paths outside `.swarm/` (untracked files listed individually).
82
+ * Throws when git fails.
83
+ */
84
+ export declare function listDirtyEntries(directory: string): DirtyEntry[];
85
+ /** Result of one residue commit attempt. */
86
+ export type ResidueCommitResult = {
87
+ status: 'nothing';
88
+ } | {
89
+ status: 'committed';
90
+ sha: string | null;
91
+ files: string[];
92
+ } | {
93
+ status: 'failed';
94
+ error: string;
95
+ files: string[];
96
+ };
97
+ /**
98
+ * Commit, as task `taskId`'s residue, the dirty paths that are among
99
+ * `candidates` (exact repo-relative paths) or covered by `scopes` (declared
100
+ * files or directories). `dirty` is the current status (read once by the
101
+ * caller). Rename sources follow their destination.
102
+ */
103
+ export declare function commitTaskResidue(args: {
104
+ directory: string;
105
+ epic: EpicRecordV1;
106
+ taskId: string;
107
+ label: string;
108
+ candidates: Iterable<string>;
109
+ scopes?: readonly string[];
110
+ dirty: readonly DirtyEntry[];
111
+ }): ResidueCommitResult;
112
+ /**
113
+ * The frozen declared scope of `taskId` in the epic's CURRENT wave (the
114
+ * issued, active one), or `[]`. Scopes of earlier, closed waves never
115
+ * attribute a later write: the user's edits there are not a task's residue.
116
+ */
117
+ export declare function declaredScopeOf(epic: EpicRecordV1, taskId: string): string[];
118
+ /** What the delegation gate hands the after-hook seam. */
119
+ export interface EpicResidueRequest {
120
+ directory: string;
121
+ /** Raw `subagent_type` of the returned Task delegation. */
122
+ agent: string;
123
+ sessionID: string | undefined;
124
+ /** Plan task id(s) the delegation served (lazy: Epic-only work). */
125
+ resolveTaskIds: () => Promise<string[]>;
126
+ /** The delegated agent's child session id(s) (lazy). */
127
+ childSessionIds: () => Promise<Array<string | null | undefined>>;
128
+ }
129
+ /**
130
+ * After-hook seam (delegation gate, Task `tool.execute.after`): commit the
131
+ * residue of a non-coder writer that served a task of the open git epic.
132
+ * One `existsSync` and nothing else when no epic is open. Never throws.
133
+ */
134
+ export declare function commitEpicResidueAfterDelegation(request: EpicResidueRequest): Promise<void>;
135
+ /** Dirty paths before a new wave, by what Epic does with them. */
136
+ export interface DirtyBaselineClassification {
137
+ /** Tracked changes: block the next wave (`dirty-baseline`). */
138
+ unattributedTracked: string[];
139
+ /** Untracked files: advisory only. */
140
+ unattributedUntracked: string[];
141
+ }
142
+ /**
143
+ * Classify the dirty paths before a new wave (X1 `dirty-baseline`). Residue
144
+ * is only ever attributed to tasks of the CURRENT wave (the after-hook) or
145
+ * the closing wave (`epic_next_wave` commits it before the close), so when a
146
+ * new wave is about to be issued nothing dirty belongs to a task any more:
147
+ * a later edit under an earlier task's declared directory is the user's,
148
+ * never swept into that task's residue. Tracked changes block; untracked
149
+ * files are only reported.
150
+ */
151
+ export declare function classifyDirtyBaseline(dirty: readonly DirtyEntry[]): DirtyBaselineClassification;