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.
- package/README.md +42 -6
- package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
- package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
- package/dist/agents/architect.d.ts +1 -1
- package/dist/background/completion-observer.d.ts +6 -0
- package/dist/cli/{auto-checkpoint-m7c4x8bk.js → auto-checkpoint-26m3h5f4.js} +9 -9
- package/dist/cli/{checkpoint-m7q9886q.js → checkpoint-7a0achpv.js} +18 -20
- package/dist/cli/{coder-settlement-hh81z2f7.js → coder-settlement-dsb1t39t.js} +13 -13
- package/dist/cli/{config-doctor-0rexy3xt.js → config-doctor-9txawzng.js} +23 -20
- package/dist/cli/{core-ays6stz4.js → core-kv7jz9ty.js} +4 -4
- package/dist/cli/{curation-policy-65ckmmd5.js → curation-policy-6p6fcfjb.js} +6 -6
- package/dist/cli/{curator-dfzkfgs0.js → curator-26yzg8wp.js} +44 -44
- package/dist/cli/{curator-drift-nts5atks.js → curator-drift-kxn5sk3z.js} +9 -9
- package/dist/cli/curator-llm-factory-hs2y9kmw.js +82 -0
- package/dist/cli/{doctor-knowledge-receipt-phase-id-ec40k5hn.js → doctor-knowledge-receipt-phase-id-z8wea03e.js} +9 -9
- package/dist/cli/{evidence-summary-service-vgtm72g5.js → evidence-summary-service-bg5zq78s.js} +21 -22
- package/dist/cli/{gate-evidence-vmn65kxe.js → gate-evidence-0beg9c7r.js} +8 -8
- package/dist/cli/guardrail-explain-n8bm0hpm.js +83 -0
- package/dist/cli/{guardrail-log-hc8fjm4x.js → guardrail-log-r2zazmbn.js} +8 -8
- package/dist/cli/{guardrail-reset-ct00w300.js → guardrail-reset-0q54t44w.js} +44 -44
- package/dist/cli/{hive-promoter-2xax6van.js → hive-promoter-7dr5b9zv.js} +44 -44
- package/dist/cli/{index-9gqb5nkj.js → index-0gve0mkj.js} +4 -4
- package/dist/cli/{index-34vsyj15.js → index-0yrg10jx.js} +5 -5
- package/dist/cli/{index-mfxef20k.js → index-0z8vdkvg.js} +4 -4
- package/dist/cli/{index-2nexf5vz.js → index-1f084tqn.js} +22223 -15395
- package/dist/cli/{index-qx3g1bbe.js → index-26e2s6r1.js} +1 -1
- package/dist/cli/{index-kqegvngt.js → index-2c47efpt.js} +4 -4
- package/dist/cli/{index-9tvjwv01.js → index-2fpv2wmc.js} +1 -1
- package/dist/cli/{index-8q8783g0.js → index-3zwhxk98.js} +1 -1
- package/dist/cli/{index-9j3ybyfs.js → index-4a15347d.js} +7 -7
- package/dist/cli/{index-5cfhwd65.js → index-67a6mk20.js} +2 -2
- package/dist/cli/{index-d3rjvm9e.js → index-7vnr5cmp.js} +2 -2
- package/dist/cli/{index-twb3f4tk.js → index-8bx6vhzk.js} +1 -1
- package/dist/cli/{index-0gejy4cx.js → index-ay8ggtgr.js} +2 -2
- package/dist/cli/{index-8xabhfwr.js → index-b2j231jy.js} +2 -2
- package/dist/cli/{index-cbqkag7q.js → index-bcwmn4d7.js} +3 -3
- package/dist/cli/{index-fbgjj7h5.js → index-bfyh3gpe.js} +101 -13
- package/dist/cli/{index-skdpgjar.js → index-cewpxcwm.js} +862 -1221
- package/dist/cli/{index-bzbxn24k.js → index-dbdkh7pq.js} +5 -5
- package/dist/cli/{index-w4zscz6p.js → index-dnp4qtpf.js} +5 -5
- package/dist/cli/{index-waxvpwbv.js → index-efztf4yw.js} +127 -13
- package/dist/cli/{index-4p3jh14t.js → index-enq166kw.js} +4 -4
- package/dist/cli/{index-g03m5jhw.js → index-f46dbyew.js} +3 -3
- package/dist/cli/{index-ntda6tje.js → index-feaxsjrt.js} +10 -2
- package/dist/cli/{index-89f56ej4.js → index-fthhppnc.js} +46 -46
- package/dist/cli/{index-wqa31xnf.js → index-gqb8etv4.js} +7 -7
- package/dist/cli/{index-1cwmj1gs.js → index-ha0w0a7t.js} +77 -16
- package/dist/cli/{index-nhsafbrh.js → index-hx1fmywk.js} +5 -5
- package/dist/cli/{index-kaqdzhj3.js → index-kfevm0n5.js} +2 -2
- package/dist/cli/{index-z29hksyw.js → index-m28dzyy8.js} +26 -10
- package/dist/cli/{index-x1eh2636.js → index-mcv0v2dp.js} +1 -1
- package/dist/cli/{index-9czp2dcg.js → index-mjfpaxw3.js} +7 -7
- package/dist/cli/{index-f6exny9m.js → index-n8q43eff.js} +2 -2
- package/dist/cli/{index-7hr5m7yp.js → index-nkgzkt7c.js} +1 -1
- package/dist/cli/{index-p2tqzwh6.js → index-qs9myayh.js} +5 -10
- package/dist/cli/{index-hh7pa3yk.js → index-r9yvpr2m.js} +2 -2
- package/dist/cli/{index-cdjxzs2r.js → index-rjzn7hf0.js} +7 -7
- package/dist/cli/{index-zfbxfm2m.js → index-s8b67e6h.js} +33 -12
- package/dist/cli/{index-bskex45z.js → index-s9cgnd2z.js} +2 -2
- package/dist/cli/{index-cj4mp0fm.js → index-sc7t1tev.js} +6 -6
- package/dist/cli/{index-3dx0c7ve.js → index-sfjpbnqs.js} +44 -44
- package/dist/cli/{index-16m2m4ah.js → index-spgfs5y7.js} +1 -1
- package/dist/cli/{index-q9kefh33.js → index-sxj143a4.js} +1 -1
- package/dist/cli/{index-zsrtt42s.js → index-v3bmwmqw.js} +25 -1
- package/dist/cli/{index-5v09zw1q.js → index-v3j9fk16.js} +18 -8
- package/dist/cli/{index-2btybj8w.js → index-xdq6mak1.js} +4 -4
- package/dist/cli/{index-k8sarme1.js → index-y9v41185.js} +1 -1
- package/dist/cli/{index-4a7j5f60.js → index-yy5yyyst.js} +4 -4
- package/dist/cli/{index-1xa8wq76.js → index-z3ahy9c2.js} +2 -2
- package/dist/cli/{index-wrdktgpy.js → index-zk6ttjkt.js} +2 -2
- package/dist/cli/index.js +45 -45
- package/dist/cli/{knowledge-escalator-v3c8w86g.js → knowledge-escalator-hdrqt5pj.js} +15 -15
- package/dist/cli/{knowledge-events-kagaccj9.js → knowledge-events-ttebc8gn.js} +13 -13
- package/dist/cli/{knowledge-link-h4pdcnya.js → knowledge-link-6hw3z6wn.js} +5 -5
- package/dist/cli/{knowledge-store-ywxjk5pb.js → knowledge-store-8cxv6w2n.js} +6 -6
- package/dist/cli/{knowledge-validator-210gkpec.js → knowledge-validator-kr5xmqpa.js} +8 -8
- package/dist/cli/{mcp-58hgx6av.js → mcp-jtk79ygp.js} +1 -1
- package/dist/cli/{model-preflight-e69b48n0.js → model-preflight-d6tt7479.js} +44 -44
- package/dist/cli/{pending-delegations-g24d3qa9.js → pending-delegations-ttk09f50.js} +12 -12
- package/dist/cli/{pr-subscriptions-qprh2zsm.js → pr-subscriptions-2kbeezsr.js} +12 -12
- package/dist/cli/{pr-workflow-gate-pj7dhkv4.js → pr-workflow-gate-3ra6p7k4.js} +44 -44
- package/dist/cli/{runner-4hfmp1ag.js → runner-ssnsje35.js} +6 -6
- package/dist/cli/{scan-cursor-s5wsxxd1.js → scan-cursor-zzccy117.js} +7 -7
- package/dist/cli/{schema-smgngq9m.js → schema-9prswg1x.js} +14 -2
- package/dist/cli/{scope-persistence-tbv2ar4w.js → scope-persistence-eqm8vwnx.js} +16 -16
- package/dist/cli/{server-7p2ds5w2.js → server-swjre81h.js} +45 -45
- package/dist/cli/{skill-generator-r4hsne71.js → skill-generator-d8dk9vps.js} +17 -17
- package/dist/cli/{snapshot-coordination-init-ydjwh56d.js → snapshot-coordination-init-f49y0zr0.js} +44 -44
- package/dist/cli/{speckit-checkoff-aj75vmjq.js → speckit-checkoff-pnb6q9gd.js} +5 -5
- package/dist/cli/{telemetry-kvhe94gs.js → telemetry-9szfdfqv.js} +1 -1
- package/dist/cli/{workspace-snapshot-30kdf018.js → workspace-snapshot-9f12p1s1.js} +3 -3
- package/dist/cli/{worktree-collision-ownership-0bxj9f0t.js → worktree-collision-ownership-3c7xyc4p.js} +14 -14
- package/dist/cli/{worktree-isolation-h0c22pm2.js → worktree-isolation-jggdf28m.js} +46 -44
- package/dist/commands/coupling.d.ts +21 -4
- package/dist/commands/epic-learning.d.ts +28 -0
- package/dist/commands/epic.d.ts +81 -36
- package/dist/commands/registry.d.ts +8 -8
- package/dist/commands/turbo.d.ts +16 -1
- package/dist/config/constants.d.ts +11 -1
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/schema.d.ts +291 -77
- package/dist/db/coordination-store.d.ts +6 -0
- package/dist/epic/close.d.ts +209 -0
- package/dist/{turbo/epic → epic}/cochange-conflict.d.ts +5 -4
- package/dist/{turbo/epic → epic}/cochange-source.d.ts +10 -1
- package/dist/epic/components.d.ts +238 -0
- package/dist/epic/config-gate.d.ts +25 -0
- package/dist/epic/config.d.ts +101 -0
- package/dist/{turbo/epic → epic}/coupling-report.d.ts +3 -1
- package/dist/epic/declared-scopes.d.ts +37 -0
- package/dist/epic/epic-branch.d.ts +157 -0
- package/dist/epic/gate-policy.d.ts +114 -0
- package/dist/epic/git-once.d.ts +26 -0
- package/dist/epic/learning-store.d.ts +176 -0
- package/dist/epic/learning.d.ts +198 -0
- package/dist/epic/legacy-migration.d.ts +26 -0
- package/dist/epic/lifecycle.d.ts +437 -0
- package/dist/epic/markers.d.ts +123 -0
- package/dist/epic/merge-epoch.d.ts +90 -0
- package/dist/epic/next-wave-format.d.ts +101 -0
- package/dist/epic/next-wave.d.ts +102 -0
- package/dist/epic/phase-readiness.d.ts +221 -0
- package/dist/epic/plan-key.d.ts +104 -0
- package/dist/epic/plan-shaping-seam.d.ts +112 -0
- package/dist/epic/planning-signals.d.ts +38 -0
- package/dist/epic/report.d.ts +152 -0
- package/dist/epic/residue-commit.d.ts +151 -0
- package/dist/epic/scorecard.d.ts +130 -0
- package/dist/epic/shaping-sizing.d.ts +108 -0
- package/dist/epic/shaping-suggestions.d.ts +172 -0
- package/dist/epic/shaping.d.ts +112 -0
- package/dist/epic/sizing.d.ts +60 -0
- package/dist/epic/start.d.ts +187 -0
- package/dist/epic/task-landing.d.ts +83 -0
- package/dist/epic/wave-close.d.ts +112 -0
- package/dist/epic/wave-select.d.ts +95 -0
- package/dist/evaluation/contracts.d.ts +1 -1
- package/dist/evaluation/public-api.d.ts +1 -1
- package/dist/full-auto/policy.d.ts +12 -1
- package/dist/hooks/delegation-gate/worktree-isolation.d.ts +16 -0
- package/dist/hooks/delegation-gate.d.ts +11 -0
- package/dist/hooks/guardrails/index.d.ts +7 -0
- package/dist/hooks/promotion-evidence-store.d.ts +2 -2
- package/dist/index.js +814 -807
- package/dist/plan/manager.d.ts +6 -8
- package/dist/plan/parallel-verdict.d.ts +13 -2
- package/dist/retention/caps.d.ts +1 -1
- package/dist/review/contracts.d.ts +8 -1
- package/dist/services/config-doctor.d.ts +38 -0
- package/dist/session/snapshot-reader.d.ts +2 -0
- package/dist/session/snapshot-writer.d.ts +13 -2
- package/dist/state.d.ts +13 -18
- package/dist/tools/epic-next-wave.d.ts +20 -0
- package/dist/tools/epic-phase-review.d.ts +43 -0
- package/dist/tools/index.d.ts +2 -3
- package/dist/tools/manifest.d.ts +2 -3
- package/dist/tools/phase-complete.d.ts +5 -1
- package/dist/tools/save-plan.d.ts +7 -0
- package/dist/tools/tool-metadata.d.ts +4 -8
- package/dist/worktree/merge.d.ts +14 -1
- package/opencode-swarm.schema.json +273 -28
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-nrn6n2p1.js +0 -82
- package/dist/cli/guardrail-explain-9jmr8ezt.js +0 -83
- package/dist/tools/epic-plan-waves.d.ts +0 -79
- package/dist/tools/epic-record-divergence.d.ts +0 -73
- package/dist/tools/epic-run-phase.d.ts +0 -179
- package/dist/turbo/epic/activation.d.ts +0 -193
- package/dist/turbo/epic/calibration-engine.d.ts +0 -88
- package/dist/turbo/epic/calibration.d.ts +0 -80
- package/dist/turbo/epic/divergence-recorder.d.ts +0 -123
- package/dist/turbo/epic/index.d.ts +0 -24
- package/dist/turbo/epic/promotion-evidence.d.ts +0 -42
- package/dist/turbo/epic/state.d.ts +0 -69
- package/dist/turbo/epic/task-commit.d.ts +0 -110
- package/dist/turbo/epic/upstream-commits.d.ts +0 -82
- package/dist/turbo/epic/wave-planner.d.ts +0 -83
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Epic v2 C6 — what an epic learns from its own outcomes (pure).
|
|
3
|
+
*
|
|
4
|
+
* Two signals, both learned from the per-task outcomes `epic_next_wave`
|
|
5
|
+
* records when it closes a wave (`EpicTaskOutcome`, see `wave-close.ts`),
|
|
6
|
+
* and both used ONLY by the component planner (`components.ts`) — they are
|
|
7
|
+
* analysis, never write authorization:
|
|
8
|
+
*
|
|
9
|
+
* 1. Learned scope expansion. A task that declared D and wrote f without
|
|
10
|
+
* declaring it is evidence that work on D also touches f:
|
|
11
|
+
* w(d → f) += 1 for every d ∈ D. The planner's conflict graph uses
|
|
12
|
+
* scope*(t) = scope(t) ∪ { f : Σ_{d ∈ scope(t)} w(d → f) ≥ 1 }.
|
|
13
|
+
* scope* only ADDS path-conflict edges; the wave still freezes the
|
|
14
|
+
* DECLARED scopes, which the dispatch gate checks for drift and its
|
|
15
|
+
* parallel verdict reads. Because the planner is stricter than the
|
|
16
|
+
* verdict (a superset of its edges), every multi-task wave it picks
|
|
17
|
+
* stays `all_disjoint` under the verdict (property-tested). Co-change
|
|
18
|
+
* coupling keeps using the declared scopes: its "exclusively owns one
|
|
19
|
+
* side" rule is not monotone in the scope, so expanding it could REMOVE
|
|
20
|
+
* a verdict conflict.
|
|
21
|
+
*
|
|
22
|
+
* 2. Decaying hot set. Per file, incidents α_f and exposures β_f:
|
|
23
|
+
* incidents — undeclared write 1.0 (the written file); merge-back
|
|
24
|
+
* failure 0.5, Stage B failure 0.25 per failure, rework
|
|
25
|
+
* 0.25 × min(generation − 1, 4) and reopen 0.5 per reopen, each
|
|
26
|
+
* charged to every declared FILE entry of the task (the merge-status
|
|
27
|
+
* registry records no conflict files, so a merge failure is charged
|
|
28
|
+
* to the declared files);
|
|
29
|
+
* exposures — β += 1 per declared FILE entry of every resolved task.
|
|
30
|
+
* A declared entry that is a directory (on disk, or covering another
|
|
31
|
+
* path of the same outcome) is never charged or exposed: one troubled
|
|
32
|
+
* `src`-scoped task must not make everything under `src/` hot.
|
|
33
|
+
* A task run again after a reopen is charged only the DELTA of its
|
|
34
|
+
* counters over its previous outcome (`previous`), when that previous
|
|
35
|
+
* outcome was already learned.
|
|
36
|
+
* Beta prior with mean m0 = 0.1 and strength 2 (a0 = 0.2, b0 = 1.8):
|
|
37
|
+
* r(f) = (a0 + α_f) / (a0 + b0 + α_f + β_f).
|
|
38
|
+
* Strongest-co-writer discount: α'_f = α_f − m_f when the strongest
|
|
39
|
+
* learned co-writer m_f = max_d w(d → f) ≥ 1 (an active expansion edge
|
|
40
|
+
* already keeps that declarer apart from f's owners), else α'_f = α_f.
|
|
41
|
+
* f is HOT ⇔ α'_f ≥ 1 ∧ r(α'_f, β_f) − m0 > hot_excess (default 0.25). A task
|
|
42
|
+
* whose declared scope lists a hot file (exact normalized path) runs
|
|
43
|
+
* alone (exclusive). Hot
|
|
44
|
+
* needs EXCESS evidence: an empty or clean history has no hot file and
|
|
45
|
+
* no expansion, so the cold start is neutral (no R_C product).
|
|
46
|
+
*
|
|
47
|
+
* Decay keeps both bounded and current: × `decay_per_epic` (0.7) of the
|
|
48
|
+
* project prior at every epic close that learned something, and age decay
|
|
49
|
+
* in WHOLE half-lives — × 0.5^floor(Δdays / `half_life_days` (60)) since
|
|
50
|
+
* the state was last written — so an observation keeps its full weight for
|
|
51
|
+
* a whole half-life; entries below 0.05 are dropped and at most
|
|
52
|
+
* {@link MAX_EPIC_LEARNING_FILES} files (lowest α + β evicted) and
|
|
53
|
+
* {@link MAX_EPIC_LEARNING_EDGES} co-write edges (lowest weight evicted) are
|
|
54
|
+
* kept. Persistence (project prior + epic posterior) is `learning-store.ts`.
|
|
55
|
+
*/
|
|
56
|
+
import { type EpicConfigSource } from './config.js';
|
|
57
|
+
import type { EpicTaskOutcome } from './lifecycle.js';
|
|
58
|
+
/** Files with learned statistics kept (lowest α + β evicted first). */
|
|
59
|
+
export declare const MAX_EPIC_LEARNING_FILES = 2000;
|
|
60
|
+
/** Learned co-write edges kept (lowest weight evicted first). */
|
|
61
|
+
export declare const MAX_EPIC_LEARNING_EDGES = 2000;
|
|
62
|
+
/** Entries whose mass falls below this after decay are dropped. */
|
|
63
|
+
export declare const EPIC_LEARNING_DROP_BELOW = 0.05;
|
|
64
|
+
/** Prior mean incident rate m0 and its strength (a0 + b0). */
|
|
65
|
+
export declare const EPIC_LEARNING_PRIOR_MEAN = 0.1;
|
|
66
|
+
export declare const EPIC_LEARNING_PRIOR_STRENGTH = 2;
|
|
67
|
+
/** Minimum incident mass before a file can be hot. */
|
|
68
|
+
export declare const EPIC_HOT_MIN_ALPHA = 1;
|
|
69
|
+
/** Summed co-write weight at which a learned file joins scope*. */
|
|
70
|
+
export declare const EPIC_EXPANSION_MIN_WEIGHT = 1;
|
|
71
|
+
export declare const EPIC_LEARNING_INCIDENT_WEIGHTS: Readonly<{
|
|
72
|
+
undeclared: 1;
|
|
73
|
+
mergeFailurePerDeclared: 0.5;
|
|
74
|
+
stageBFailure: 0.25;
|
|
75
|
+
reworkPerGeneration: 0.25;
|
|
76
|
+
reworkMaxGenerations: 4;
|
|
77
|
+
reopen: 0.5;
|
|
78
|
+
}>;
|
|
79
|
+
/** Resolved `epic.learning.*` settings. */
|
|
80
|
+
export interface EpicLearningSettings {
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
decayPerEpic: number;
|
|
83
|
+
halfLifeDays: number;
|
|
84
|
+
hotExcess: number;
|
|
85
|
+
}
|
|
86
|
+
export declare const DEFAULT_EPIC_LEARNING_SETTINGS: EpicLearningSettings;
|
|
87
|
+
export declare function resolveEpicLearningSettings(config: EpicConfigSource): EpicLearningSettings;
|
|
88
|
+
export interface EpicFileStats {
|
|
89
|
+
/** Incident mass α_f. */
|
|
90
|
+
alpha: number;
|
|
91
|
+
/** Exposure mass β_f. */
|
|
92
|
+
beta: number;
|
|
93
|
+
}
|
|
94
|
+
/** In-memory learning statistics (paths normalized, never `.swarm/`). */
|
|
95
|
+
export interface EpicLearningStats {
|
|
96
|
+
files: Map<string, EpicFileStats>;
|
|
97
|
+
/** from (declared) → to (co-written undeclared) → weight. */
|
|
98
|
+
edges: Map<string, Map<string, number>>;
|
|
99
|
+
}
|
|
100
|
+
/** Learned co-write weights, read-only (the planner's view). */
|
|
101
|
+
export type EpicCoWriteIndex = ReadonlyMap<string, ReadonlyMap<string, number>>;
|
|
102
|
+
export declare function emptyEpicLearning(): EpicLearningStats;
|
|
103
|
+
export declare function isEpicLearningEmpty(stats: EpicLearningStats): boolean;
|
|
104
|
+
export declare function cloneEpicLearning(stats: EpicLearningStats): EpicLearningStats;
|
|
105
|
+
/** Normalized, unique paths learning may keep (never `.swarm/` or absolute). */
|
|
106
|
+
export declare function learnablePaths(files: readonly string[]): string[];
|
|
107
|
+
/**
|
|
108
|
+
* Files written that the declared scope does not cover (a declared
|
|
109
|
+
* directory covers every file beneath it, segment-aware). Sorted, unique,
|
|
110
|
+
* normalized.
|
|
111
|
+
*/
|
|
112
|
+
export declare function undeclaredFiles(declaredScope: readonly string[], actualFiles: readonly string[]): string[];
|
|
113
|
+
export interface EpicLearningOptions {
|
|
114
|
+
/**
|
|
115
|
+
* Whether a declared entry is a directory on disk (never charged or
|
|
116
|
+
* exposed). Absent ⇒ only entries covering another path of the same
|
|
117
|
+
* outcome count as directories.
|
|
118
|
+
*/
|
|
119
|
+
isDirectory?: (path: string) => boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Waves up to this seq were already learned: a re-run task whose
|
|
122
|
+
* `previous` outcome is among them is charged only the delta of its
|
|
123
|
+
* counters. Absent ⇒ every outcome is charged in full (the record
|
|
124
|
+
* fallback, where the earlier outcome was never learned separately).
|
|
125
|
+
*/
|
|
126
|
+
learnedThroughWaveSeq?: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The learning increments of resolved task outcomes (removed tasks never
|
|
130
|
+
* ran and teach nothing). Pure (the directory probe is injected); the
|
|
131
|
+
* caller merges the result.
|
|
132
|
+
*/
|
|
133
|
+
export declare function epicLearningFromOutcomes(outcomes: readonly EpicTaskOutcome[], options?: EpicLearningOptions): EpicLearningStats;
|
|
134
|
+
/** a ⊕ b (new object; inputs untouched). */
|
|
135
|
+
export declare function mergeEpicLearning(a: EpicLearningStats, b: EpicLearningStats): EpicLearningStats;
|
|
136
|
+
/** Every mass × factor (new object). */
|
|
137
|
+
export declare function scaleEpicLearning(stats: EpicLearningStats, factor: number): EpicLearningStats;
|
|
138
|
+
/**
|
|
139
|
+
* Age decay in WHOLE half-lives: 0.5^floor(Δdays / halfLifeDays) — an
|
|
140
|
+
* observation keeps its full weight for a whole half-life (a single
|
|
141
|
+
* co-write keeps expanding scopes until then). 1 for a future or unknown
|
|
142
|
+
* timestamp.
|
|
143
|
+
*/
|
|
144
|
+
export declare function epicTimeDecayFactor(updatedAtIso: string | null | undefined, nowMs: number, halfLifeDays: number): number;
|
|
145
|
+
/**
|
|
146
|
+
* Drop entries below {@link EPIC_LEARNING_DROP_BELOW} and keep at most the
|
|
147
|
+
* file / edge caps (lowest mass evicted; ties by path). New object.
|
|
148
|
+
*/
|
|
149
|
+
export declare function boundEpicLearning(stats: EpicLearningStats): EpicLearningStats;
|
|
150
|
+
/** r(f) = (a0 + α) / (a0 + b0 + α + β). */
|
|
151
|
+
export declare function epicIncidentRate(stats: EpicFileStats): number;
|
|
152
|
+
export declare function isEpicHotFile(stats: EpicFileStats, hotExcess: number): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* The incident mass the hot predicate judges (the strongest-co-writer
|
|
155
|
+
* discount): α' = max(0, α − m) when the file's strongest learned co-writer
|
|
156
|
+
* m = max_d w(d → f) is an ACTIVE scope-expansion edge (m ≥
|
|
157
|
+
* {@link EPIC_EXPANSION_MIN_WEIGHT}), else α. Undeclared writes that one
|
|
158
|
+
* declared file keeps explaining are already handled by scope expansion
|
|
159
|
+
* (the declarer now conflicts with the file's owners), so they must not
|
|
160
|
+
* ALSO make the file hot and serialize everyone declaring it; writes from
|
|
161
|
+
* several different declarers (a file that attracts undeclared writes
|
|
162
|
+
* from all over) and the other incidents still count.
|
|
163
|
+
*/
|
|
164
|
+
export declare function epicHotIncidentMass(stats: EpicFileStats, strongestCoWriter: number): number;
|
|
165
|
+
/** The hot predicate's evidence for one file (`/swarm epic learning`). */
|
|
166
|
+
export interface EpicHotEvidence {
|
|
167
|
+
/** Raw incident mass α. */
|
|
168
|
+
alpha: number;
|
|
169
|
+
/** α' after the strongest-co-writer discount. */
|
|
170
|
+
countedAlpha: number;
|
|
171
|
+
beta: number;
|
|
172
|
+
/** r(α', β). */
|
|
173
|
+
rate: number;
|
|
174
|
+
}
|
|
175
|
+
export declare function epicHotEvidence(stats: EpicLearningStats, file: string): EpicHotEvidence;
|
|
176
|
+
/**
|
|
177
|
+
* Hot files, hottest first (ties by path) — THE hot predicate (planner,
|
|
178
|
+
* sizing, shaping, report and commands all use it): {@link isEpicHotFile}
|
|
179
|
+
* over the discounted incident mass ({@link epicHotIncidentMass}).
|
|
180
|
+
*/
|
|
181
|
+
export declare function epicHotFiles(stats: EpicLearningStats, hotExcess: number): string[];
|
|
182
|
+
/**
|
|
183
|
+
* scope*(t) = scope(t) ∪ { f : Σ_{d ∈ scope(t)} w(d → f) ≥ 1 } (normalized;
|
|
184
|
+
* declared entries first, learned files appended sorted). Analysis only.
|
|
185
|
+
*/
|
|
186
|
+
export declare function expandEpicScope(scope: readonly string[], coWrites: EpicCoWriteIndex | null): string[];
|
|
187
|
+
export interface EpicLearningSummary {
|
|
188
|
+
files: number;
|
|
189
|
+
edges: number;
|
|
190
|
+
hotFiles: string[];
|
|
191
|
+
topCoWrites: Array<{
|
|
192
|
+
from: string;
|
|
193
|
+
to: string;
|
|
194
|
+
weight: number;
|
|
195
|
+
}>;
|
|
196
|
+
}
|
|
197
|
+
/** A bounded human summary (status / commands). */
|
|
198
|
+
export declare function summarizeEpicLearning(stats: EpicLearningStats, hotExcess: number, limit?: number): EpicLearningSummary;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time retirement of Epic v1 session state (Epic v2 C1a).
|
|
3
|
+
*
|
|
4
|
+
* Epic v1 kept a per-session "Epic on" flag in the coordination namespace
|
|
5
|
+
* `turbo.epic.session` with `.swarm/epic-state.json` as its projection. v2
|
|
6
|
+
* epics are plan-scoped and opened only by `/swarm epic start`, so v1 state
|
|
7
|
+
* is never imported as an open epic: it is retired here — rows deleted, the
|
|
8
|
+
* projection renamed to `.imported` — and any session that was still "on"
|
|
9
|
+
* yields an advisory to run `/swarm epic start`.
|
|
10
|
+
*
|
|
11
|
+
* Runs only inside `/swarm epic status` (never on a probe/hot path). Opens
|
|
12
|
+
* swarm.db only when it already exists. Idempotent: once retired there is
|
|
13
|
+
* nothing left to find.
|
|
14
|
+
*/
|
|
15
|
+
/** The retired v1 coordination namespace. */
|
|
16
|
+
export declare const LEGACY_EPIC_SESSION_NAMESPACE = "turbo.epic.session";
|
|
17
|
+
export interface LegacyEpicMigrationResult {
|
|
18
|
+
rowsRemoved: number;
|
|
19
|
+
/** Sessions that still had Epic v1 switched on. */
|
|
20
|
+
activeSessions: number;
|
|
21
|
+
fileArchivedTo: string | null;
|
|
22
|
+
errors: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function retireLegacyEpicSessionState(directory: string): LegacyEpicMigrationResult;
|
|
25
|
+
/** Status lines for a non-empty migration result (empty otherwise). */
|
|
26
|
+
export declare function describeLegacyEpicMigration(result: LegacyEpicMigrationResult): string[];
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Epic v2 lifecycle (C1a): one open epic per project, bound to one plan.
|
|
3
|
+
*
|
|
4
|
+
* Authority: the coordination row in namespace {@link EPIC_LIFECYCLE_NAMESPACE}
|
|
5
|
+
* (entity key = epicKey). The sentinel `.swarm/epic/epic.json` is a JSON
|
|
6
|
+
* projection of that row whose only job is to make "Epic is off" cost exactly
|
|
7
|
+
* ONE `existsSync` on every hot-path seam: a project that never opened an
|
|
8
|
+
* epic has no sentinel, so no probe ever opens swarm.db, reads config, or
|
|
9
|
+
* reads the plan.
|
|
10
|
+
*
|
|
11
|
+
* Probe order (`getOpenEpic`): sentinel → row → config → identity. No memo,
|
|
12
|
+
* no migration, no writes. A row whose plan identity no longer matches the
|
|
13
|
+
* current plan (plan renamed or replaced — a new ledger root) is ORPHANED:
|
|
14
|
+
* the probe answers "no open epic"; `/swarm epic status` explains and
|
|
15
|
+
* `/swarm epic close --abandon` repairs.
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle lock (M-d): every sentinel mutation runs INSIDE the coordination
|
|
18
|
+
* store's `BEGIN IMMEDIATE` transaction (`withCoordinationTransaction`), so
|
|
19
|
+
* the row write and the sentinel write/delete are serialized against every
|
|
20
|
+
* other lifecycle writer in every process:
|
|
21
|
+
* - start: only the CAS winner (row created with expectedRevision null)
|
|
22
|
+
* writes the sentinel; a sentinel-write failure rolls the row back.
|
|
23
|
+
* - close/repair: the row is deleted (revision CAS) and the sentinel is
|
|
24
|
+
* removed only when it still names the same epicKey + token
|
|
25
|
+
* (compare-and-delete), so a late close can never delete a newer epic's
|
|
26
|
+
* sentinel.
|
|
27
|
+
*/
|
|
28
|
+
import type { EpicWaveComponents } from './components.js';
|
|
29
|
+
import { isEpicModeConfigEnabledForDirectory } from './config-gate.js';
|
|
30
|
+
import type { EpicSizingVerdict } from './sizing.js';
|
|
31
|
+
/** Coordination namespace of the authoritative epic row. */
|
|
32
|
+
export declare const EPIC_LIFECYCLE_NAMESPACE = "turbo.epic.lifecycle";
|
|
33
|
+
/** Project-relative path of the sentinel projection. */
|
|
34
|
+
export declare const EPIC_SENTINEL_RELATIVE_PATH: string;
|
|
35
|
+
export type EpicLifecycleStatus = 'open' | 'closing';
|
|
36
|
+
/** One co-change pair recorded with a wave (threshold-passing, in-wave). */
|
|
37
|
+
export interface EpicCochangePair {
|
|
38
|
+
fileA: string;
|
|
39
|
+
fileB: string;
|
|
40
|
+
npmi: number;
|
|
41
|
+
coChangeCount: number;
|
|
42
|
+
}
|
|
43
|
+
/** Snapshot of a worktree merge-back failure observed for a wave task. */
|
|
44
|
+
export interface EpicMergeFailureSnapshot {
|
|
45
|
+
outcome: string;
|
|
46
|
+
stage: string;
|
|
47
|
+
message: string;
|
|
48
|
+
at: number | null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One wave issued by `epic_next_wave` (Epic v2 C2). `files` are the FROZEN
|
|
52
|
+
* declared scopes of the wave's tasks at issue time; later commits compare
|
|
53
|
+
* against them (divergence now, the dispatch gate in C4).
|
|
54
|
+
*/
|
|
55
|
+
export interface EpicWaveRecord {
|
|
56
|
+
seq: number;
|
|
57
|
+
phase: number;
|
|
58
|
+
/**
|
|
59
|
+
* `parallel` (tasks of conflict-free components), `exclusive` (a task
|
|
60
|
+
* that runs alone), or `serial-component` (one task of a densely coupled
|
|
61
|
+
* component; no other ready task could join it). See `components.ts`.
|
|
62
|
+
*/
|
|
63
|
+
kind: 'parallel' | 'exclusive' | 'serial-component';
|
|
64
|
+
taskIds: string[];
|
|
65
|
+
files: Record<string, string[]>;
|
|
66
|
+
/** ≤ 256 threshold-passing pairs within the wave's files; null = co-change off. */
|
|
67
|
+
cochange: {
|
|
68
|
+
pairs: EpicCochangePair[];
|
|
69
|
+
threshold: {
|
|
70
|
+
npmi: number;
|
|
71
|
+
minCoChanges: number;
|
|
72
|
+
};
|
|
73
|
+
} | null;
|
|
74
|
+
/**
|
|
75
|
+
* Epic v2 C5: the phase's components when the wave was issued (pending
|
|
76
|
+
* task → component, component modes/densities). Absent on waves issued
|
|
77
|
+
* before C5.
|
|
78
|
+
*/
|
|
79
|
+
components?: EpicWaveComponents;
|
|
80
|
+
/** HEAD when the wave was issued (null: non-git / unborn). */
|
|
81
|
+
baseHead: string | null;
|
|
82
|
+
issuedAt: string;
|
|
83
|
+
closedAt?: string;
|
|
84
|
+
closeHead?: string | null;
|
|
85
|
+
status: 'issued' | 'closed' | 'aborted';
|
|
86
|
+
abortReason?: string;
|
|
87
|
+
/** Merge-back failures observed while the wave was blocked (snapshots). */
|
|
88
|
+
mergeFailures?: Record<string, EpicMergeFailureSnapshot>;
|
|
89
|
+
/**
|
|
90
|
+
* Wave-level undeclared files from the git fallback (changed since
|
|
91
|
+
* `baseHead`, not declared by any wave task, not attributed to a task).
|
|
92
|
+
*/
|
|
93
|
+
undeclared?: string[];
|
|
94
|
+
}
|
|
95
|
+
/** How one wave task was resolved, recorded when its wave closes. */
|
|
96
|
+
export interface EpicTaskOutcome {
|
|
97
|
+
taskId: string;
|
|
98
|
+
phase: number;
|
|
99
|
+
waveSeq: number;
|
|
100
|
+
resolution: 'completed' | 'closed' | 'removed';
|
|
101
|
+
resolvedAt: string;
|
|
102
|
+
/** Evidence workflow generation (uncapped; 0 = no evidence). */
|
|
103
|
+
generation: number;
|
|
104
|
+
/**
|
|
105
|
+
* LOWER BOUNDS: `stage_a_failed` / `stage_b_failed` entries in the
|
|
106
|
+
* evidence workflow `retryHistory`, which keeps only the last 3 entries.
|
|
107
|
+
*/
|
|
108
|
+
stageAFailures: number;
|
|
109
|
+
stageBFailures: number;
|
|
110
|
+
/** Merge-back failure snapshot observed while the wave was blocked. */
|
|
111
|
+
mergeFailure: {
|
|
112
|
+
outcome: string;
|
|
113
|
+
stage: string;
|
|
114
|
+
} | null;
|
|
115
|
+
declared: string[];
|
|
116
|
+
undeclared: string[];
|
|
117
|
+
/** Where `undeclared` came from. */
|
|
118
|
+
attribution: 'session' | 'git-single-task' | 'unavailable' | 'no-git';
|
|
119
|
+
/** Ledger transitions out of `completed` for this task since the epic started. */
|
|
120
|
+
reopened: number;
|
|
121
|
+
/**
|
|
122
|
+
* Epic v2 C6: the counters of this task's earlier outcome when it ran
|
|
123
|
+
* again (reopened) — learning charges only the delta.
|
|
124
|
+
*/
|
|
125
|
+
previous?: {
|
|
126
|
+
waveSeq: number;
|
|
127
|
+
generation: number;
|
|
128
|
+
stageBFailures: number;
|
|
129
|
+
reopened: number;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* The commit proving the task's work is on the epic branch (Epic v2 C3),
|
|
133
|
+
* mirrored to `ref` (`refs/swarm/epics/<epicKey>/tasks/<id>`, see
|
|
134
|
+
* `markers.ts`): the task's newest `swarm(task <id>):` commit for this
|
|
135
|
+
* plan inside the wave (`landing-commit` — its coder's landing merge or a
|
|
136
|
+
* residue commit), else HEAD at wave close (`wave-close-head`), or a
|
|
137
|
+
* commit re-adopted by `/swarm epic status --repair-refs` (`repaired`).
|
|
138
|
+
* Completed tasks only; `no-git` epics record no commit.
|
|
139
|
+
*/
|
|
140
|
+
marker: {
|
|
141
|
+
ref: string | null;
|
|
142
|
+
sha: string | null;
|
|
143
|
+
provenance: EpicTaskMarkerProvenance;
|
|
144
|
+
} | null;
|
|
145
|
+
}
|
|
146
|
+
export type EpicTaskMarkerProvenance = 'landing-commit' | 'wave-close-head' | 'repaired' | 'no-git';
|
|
147
|
+
/** Per-phase lifecycle as seen by `epic_next_wave` / `epic_phase_review`. */
|
|
148
|
+
export interface EpicPhaseRecord {
|
|
149
|
+
status: 'active' | 'review' | 'complete';
|
|
150
|
+
/** True when the phase was already finished when the epic started. */
|
|
151
|
+
completeAtStart?: boolean;
|
|
152
|
+
reviewRuns: number;
|
|
153
|
+
/** Newest last; at most {@link EPIC_PHASE_VERDICTS_KEEP}. */
|
|
154
|
+
verdicts: string[];
|
|
155
|
+
}
|
|
156
|
+
/** Phase-review verdict strings kept per phase in the record. */
|
|
157
|
+
export declare const EPIC_PHASE_VERDICTS_KEEP = 20;
|
|
158
|
+
/**
|
|
159
|
+
* Where an epic's commits go (`epic.commit_policy`, default
|
|
160
|
+
* `epic-branch`): a dedicated `swarm/epic/<epicKey>` branch checked out at
|
|
161
|
+
* start and landed at close, or the branch that was current at start.
|
|
162
|
+
*/
|
|
163
|
+
export type EpicCommitPolicy = 'epic-branch' | 'current-branch';
|
|
164
|
+
/** How `/swarm epic close` lands the epic branch (default `squash`). */
|
|
165
|
+
export type EpicLandMode = 'squash' | 'merge' | 'none';
|
|
166
|
+
export interface EpicRecordConfig {
|
|
167
|
+
/** Non-git epics always record 'current-branch' (nothing to branch). */
|
|
168
|
+
commitPolicy: EpicCommitPolicy;
|
|
169
|
+
isolation: 'worktree' | 'main-tree-nogit';
|
|
170
|
+
/** Wave width cap honoured by `epic_next_wave` (1 for non-git, M-i). */
|
|
171
|
+
maxParallel: number;
|
|
172
|
+
}
|
|
173
|
+
export interface EpicRecordGit {
|
|
174
|
+
isRepo: boolean;
|
|
175
|
+
baseCommit: string | null;
|
|
176
|
+
originalBranch: string | null;
|
|
177
|
+
/**
|
|
178
|
+
* `swarm/epic/<epicKey>` — written ONLY after `git checkout -b` succeeded
|
|
179
|
+
* (M-e); null for current-branch / non-git epics.
|
|
180
|
+
*/
|
|
181
|
+
epicBranch: string | null;
|
|
182
|
+
}
|
|
183
|
+
/** A landing attempt that did not land (close stays resumable). */
|
|
184
|
+
export interface EpicLandingAttempt {
|
|
185
|
+
mode: EpicLandMode;
|
|
186
|
+
status: 'conflict' | 'failed';
|
|
187
|
+
at: string;
|
|
188
|
+
conflictFiles: string[];
|
|
189
|
+
detail: string;
|
|
190
|
+
}
|
|
191
|
+
export interface EpicClosingInfo {
|
|
192
|
+
requestedAt: string;
|
|
193
|
+
outcome: EpicCloseOutcome;
|
|
194
|
+
/** Landing mode of the latest close request (null: abandon / no branch). */
|
|
195
|
+
land: EpicLandMode | null;
|
|
196
|
+
/** Last landing attempt that failed; null until one fails. */
|
|
197
|
+
lastLandingAttempt: EpicLandingAttempt | null;
|
|
198
|
+
}
|
|
199
|
+
export type EpicCloseOutcome = 'completed' | 'abandoned' | 'abandoned-by-swarm-close';
|
|
200
|
+
export interface EpicRecordV1 {
|
|
201
|
+
schema: 'epic-record-v1';
|
|
202
|
+
epicKey: string;
|
|
203
|
+
/** Per-start nonce; the sentinel compare-and-delete matches it too. */
|
|
204
|
+
token: string;
|
|
205
|
+
planId: string;
|
|
206
|
+
planIdentityHash: string;
|
|
207
|
+
planEpoch: string | null;
|
|
208
|
+
planKey: string;
|
|
209
|
+
/** sha256 of the plan ledger's root line (sync epoch proxy); null = no ledger. */
|
|
210
|
+
ledgerRootDigest: string | null;
|
|
211
|
+
status: EpicLifecycleStatus;
|
|
212
|
+
startedAt: string;
|
|
213
|
+
startedBySession: string;
|
|
214
|
+
forced: boolean;
|
|
215
|
+
structureHashAtStart: string;
|
|
216
|
+
config: EpicRecordConfig;
|
|
217
|
+
git: EpicRecordGit;
|
|
218
|
+
sizing: EpicSizingVerdict;
|
|
219
|
+
/**
|
|
220
|
+
* Epic v2 C6: sha256 of the project prior (`.swarm/epic-prior/
|
|
221
|
+
* learning.json`) the epic's posterior was copied from at start; null
|
|
222
|
+
* when there was no readable prior or learning is disabled.
|
|
223
|
+
*/
|
|
224
|
+
priorDigest: string | null;
|
|
225
|
+
closing: EpicClosingInfo | null;
|
|
226
|
+
/** Waves issued by `epic_next_wave`, in issue order (Epic v2 C2). */
|
|
227
|
+
waves: EpicWaveRecord[];
|
|
228
|
+
/** Seq of the wave currently issued and not yet closed. */
|
|
229
|
+
activeWaveSeq: number | null;
|
|
230
|
+
/** Outcome of every task resolved through a closed wave, by task id. */
|
|
231
|
+
tasks: Record<string, EpicTaskOutcome>;
|
|
232
|
+
/** Phase lifecycle keyed by phase id (string). */
|
|
233
|
+
phases: Record<string, EpicPhaseRecord>;
|
|
234
|
+
}
|
|
235
|
+
/** Sentinel projection: enough to identify the row it mirrors. */
|
|
236
|
+
export interface EpicSentinel {
|
|
237
|
+
schema: 'epic-sentinel-v1';
|
|
238
|
+
epicKey: string;
|
|
239
|
+
token: string;
|
|
240
|
+
planId: string;
|
|
241
|
+
startedAt: string;
|
|
242
|
+
}
|
|
243
|
+
/** Raised when the lifecycle row exists but cannot be trusted. */
|
|
244
|
+
export declare class EpicStateUnreadableError extends Error {
|
|
245
|
+
constructor(message: string);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* DI seam (AGENTS.md invariant 7). Restore in `afterEach`.
|
|
249
|
+
*/
|
|
250
|
+
export declare const _internals: {
|
|
251
|
+
isEpicModeConfigEnabledForDirectory: typeof isEpicModeConfigEnabledForDirectory;
|
|
252
|
+
now: () => number;
|
|
253
|
+
/** Bounded warn-once set so a corrupt row does not spam every probe. */
|
|
254
|
+
warnedDirectories: Set<string>;
|
|
255
|
+
};
|
|
256
|
+
export declare function epicSentinelPath(directory: string): string;
|
|
257
|
+
/**
|
|
258
|
+
* The one hot-path check: does the sentinel exist? Exactly one `existsSync`.
|
|
259
|
+
* Never follows raw `..` segments into an ancestor's `.swarm/`.
|
|
260
|
+
*/
|
|
261
|
+
export declare function epicSentinelExists(directory: string): boolean;
|
|
262
|
+
/** Ref-safe, filename-safe epic key: `<planId≤40>-<planKey≤12>`. */
|
|
263
|
+
export declare function computeEpicKey(planId: string, planKey: string): string;
|
|
264
|
+
/** Identity fields of the current plan used for the orphan check. */
|
|
265
|
+
export interface EpicPlanIdentity {
|
|
266
|
+
planId: string;
|
|
267
|
+
planIdentityHash: string;
|
|
268
|
+
}
|
|
269
|
+
export declare function planIdentityOf(plan: {
|
|
270
|
+
swarm: string;
|
|
271
|
+
title: string;
|
|
272
|
+
}): EpicPlanIdentity;
|
|
273
|
+
/**
|
|
274
|
+
* sha256 of the plan ledger's first line. The ledger is append-only and a
|
|
275
|
+
* new plan (save_plan of a different plan, reset, recovery re-root) starts a
|
|
276
|
+
* new root line, so the digest is a synchronous proxy for the plan epoch.
|
|
277
|
+
* Returns null when there is no ledger.
|
|
278
|
+
*/
|
|
279
|
+
export declare function readLedgerRootDigest(directory: string): string | null;
|
|
280
|
+
/** Synchronous, bounded read of the current plan identity from plan.json. */
|
|
281
|
+
export declare function readCurrentPlanIdentity(directory: string): EpicPlanIdentity | null;
|
|
282
|
+
export declare function parseEpicRecord(payload: string): EpicRecordV1;
|
|
283
|
+
/** Read and validate the sentinel; null when absent, invalid, or oversized. */
|
|
284
|
+
export declare function readEpicSentinel(directory: string): EpicSentinel | null;
|
|
285
|
+
/** Why a row-backed epic is not usable for the current plan. */
|
|
286
|
+
export type EpicOrphanReason = 'plan-missing' | 'plan-renamed-or-replaced' | 'plan-ledger-replaced';
|
|
287
|
+
/** Pure identity comparison (exported for tests and status). */
|
|
288
|
+
export declare function evaluateEpicIdentity(record: EpicRecordV1, current: EpicPlanIdentity | null, ledgerRootDigest: string | null): EpicOrphanReason | null;
|
|
289
|
+
/** Full diagnostic view used by `/swarm epic status` / close / repair. */
|
|
290
|
+
export interface EpicInspection {
|
|
291
|
+
sentinelPresent: boolean;
|
|
292
|
+
sentinel: EpicSentinel | null;
|
|
293
|
+
/** Validated record (null when no row, or when `unreadable` is set). */
|
|
294
|
+
record: EpicRecordV1 | null;
|
|
295
|
+
/** Row entity keys present (even when unparseable). */
|
|
296
|
+
rowKeys: string[];
|
|
297
|
+
unreadable: string | null;
|
|
298
|
+
orphanReason: EpicOrphanReason | null;
|
|
299
|
+
configEnabled: boolean;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Inspect every piece of lifecycle state without writing. Unlike the probe
|
|
303
|
+
* it reads rows even when the sentinel is absent (repair needs that), so it
|
|
304
|
+
* is only for `/swarm epic *` and `/swarm close` finalization. Opens the DB
|
|
305
|
+
* only when swarm.db already exists.
|
|
306
|
+
*/
|
|
307
|
+
export declare function inspectEpic(directory: string, plan?: {
|
|
308
|
+
swarm: string;
|
|
309
|
+
title: string;
|
|
310
|
+
} | null): EpicInspection;
|
|
311
|
+
/**
|
|
312
|
+
* The open epic for the current plan, or null. Probe order: sentinel → row →
|
|
313
|
+
* config → identity; no memo, no writes. Throws {@link EpicStateUnreadableError}
|
|
314
|
+
* when the sentinel exists and the row is present but untrustworthy.
|
|
315
|
+
*
|
|
316
|
+
* @param plan - when supplied, its identity is used instead of reading
|
|
317
|
+
* `.swarm/plan.json`.
|
|
318
|
+
*/
|
|
319
|
+
export declare function getOpenEpic(directory: string, plan?: {
|
|
320
|
+
swarm: string;
|
|
321
|
+
title: string;
|
|
322
|
+
} | null): EpicRecordV1 | null;
|
|
323
|
+
/**
|
|
324
|
+
* Boolean probe for hot-path seams. Sentinel-first (one `existsSync` when
|
|
325
|
+
* Epic is off); corrupt state ⇒ false + one critical warning per directory.
|
|
326
|
+
*/
|
|
327
|
+
export declare function isEpicOpenForProject(directory: string): boolean;
|
|
328
|
+
export type EpicCreateOutcome = {
|
|
329
|
+
outcome: 'created';
|
|
330
|
+
record: EpicRecordV1;
|
|
331
|
+
} | {
|
|
332
|
+
outcome: 'exists';
|
|
333
|
+
existingKeys: string[];
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* CAS-create the epic row and (as the winner only) write the sentinel, both
|
|
337
|
+
* under the lifecycle lock. A sentinel-write failure rolls the row back.
|
|
338
|
+
*/
|
|
339
|
+
export declare function createEpicRecord(directory: string, record: EpicRecordV1): EpicCreateOutcome;
|
|
340
|
+
/**
|
|
341
|
+
* The epic row would outgrow the store payload cap. Distinct from a generic
|
|
342
|
+
* store failure so callers can surface the remedy instead of the raw
|
|
343
|
+
* `payload must contain 1..1048576 characters`.
|
|
344
|
+
*/
|
|
345
|
+
export declare class EpicRecordTooLargeError extends Error {
|
|
346
|
+
constructor(epicKey: string);
|
|
347
|
+
}
|
|
348
|
+
/** True when `record` would serialize past {@link MAX_EPIC_RECORD_PAYLOAD_CHARS}. */
|
|
349
|
+
export declare function epicRecordPayloadTooLarge(record: EpicRecordV1): boolean;
|
|
350
|
+
/**
|
|
351
|
+
* Revision-checked read-modify-write of the epic row. Returns the updated
|
|
352
|
+
* record, or null when the row vanished or (with `expectedToken`) belongs to
|
|
353
|
+
* a different start of the same epicKey. Throws on contention/corruption, and
|
|
354
|
+
* throws {@link EpicRecordTooLargeError} when the row would outgrow the store
|
|
355
|
+
* payload cap.
|
|
356
|
+
*/
|
|
357
|
+
export declare function updateEpicRecord(directory: string, epicKey: string, mutate: (record: EpicRecordV1) => EpicRecordV1, expectedToken?: string | null): EpicRecordV1 | null;
|
|
358
|
+
/** A phase as the Epic phase order sees it (id + plan status). */
|
|
359
|
+
export interface EpicPhaseRef {
|
|
360
|
+
id: number;
|
|
361
|
+
status?: string;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Whether `phase` is done for Epic: recorded complete on the epic (by
|
|
365
|
+
* `phase_complete`, or finished before the epic started), or closed in the
|
|
366
|
+
* plan. The plan's own `complete` status is NOT used: it turns `complete`
|
|
367
|
+
* as soon as every task completes, before the phase review ran.
|
|
368
|
+
*/
|
|
369
|
+
export declare function isEpicPhaseDone(record: Pick<EpicRecordV1, 'phases'>, phase: EpicPhaseRef): boolean;
|
|
370
|
+
/** The epic's current phase: the first phase (plan order) not done. */
|
|
371
|
+
export declare function epicCurrentPhase(record: Pick<EpicRecordV1, 'phases'>, phases: readonly EpicPhaseRef[]): number | null;
|
|
372
|
+
/** Bounded read of the plan's phase order from `.swarm/plan.json`. */
|
|
373
|
+
export declare function readPlanPhaseRefs(directory: string): EpicPhaseRef[] | null;
|
|
374
|
+
/** A plan task as Epic's hot-path seams see it. */
|
|
375
|
+
export interface EpicPlanTaskRef {
|
|
376
|
+
id: string;
|
|
377
|
+
phase: number;
|
|
378
|
+
description: string | undefined;
|
|
379
|
+
files: string[];
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Bounded synchronous lookup of one task in `.swarm/plan.json` (the derived
|
|
383
|
+
* projection — enough for membership and the commit subject). Null when the
|
|
384
|
+
* plan is unreadable or the task is not in it.
|
|
385
|
+
*/
|
|
386
|
+
export declare function readPlanTaskRef(directory: string, taskId: string): EpicPlanTaskRef | null;
|
|
387
|
+
export type EpicPhaseCompleteOutcome = {
|
|
388
|
+
outcome: 'recorded' | 'already-complete';
|
|
389
|
+
record: EpicRecordV1;
|
|
390
|
+
} | {
|
|
391
|
+
outcome: 'no-open-epic';
|
|
392
|
+
} | {
|
|
393
|
+
outcome: 'not-current-phase';
|
|
394
|
+
currentPhase: number | null;
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* Record `phase` complete on the open epic (Epic v2 C2; called by
|
|
398
|
+
* `phase_complete` on success while an epic is open). Phases are
|
|
399
|
+
* iterations: only the epic's CURRENT phase can be completed — completing
|
|
400
|
+
* another phase out of order is refused (`not-current-phase`) so it cannot
|
|
401
|
+
* skip the current phase's waves. Idempotent for an already-complete phase.
|
|
402
|
+
* Throws on unreadable state / contention.
|
|
403
|
+
*/
|
|
404
|
+
export declare function markEpicPhaseComplete(directory: string, phase: number, planPhases?: readonly EpicPhaseRef[]): EpicPhaseCompleteOutcome;
|
|
405
|
+
/**
|
|
406
|
+
* CAS the row to `closing` (first close step; idempotent). A resumed close
|
|
407
|
+
* keeps the original request time and outcome — except that an explicit
|
|
408
|
+
* abandon upgrades a `completed` close whose landing never finished — and
|
|
409
|
+
* records the landing mode of the latest request.
|
|
410
|
+
*/
|
|
411
|
+
export declare function markEpicClosing(directory: string, epicKey: string, outcome: EpicCloseOutcome, expectedToken?: string | null, land?: EpicLandMode | null): EpicRecordV1 | null;
|
|
412
|
+
/**
|
|
413
|
+
* Record the epic branch once `git checkout -b` succeeded (M-e). Returns the
|
|
414
|
+
* updated record, or null when the row vanished / belongs to another start.
|
|
415
|
+
*/
|
|
416
|
+
export declare function recordEpicBranch(directory: string, epicKey: string, expectedToken: string, epicBranch: string): EpicRecordV1 | null;
|
|
417
|
+
/** Record a failed landing attempt on a `closing` row (close stays resumable). */
|
|
418
|
+
export declare function recordEpicLandingAttempt(directory: string, epicKey: string, expectedToken: string, attempt: EpicLandingAttempt): EpicRecordV1 | null;
|
|
419
|
+
export interface EpicDeleteResult {
|
|
420
|
+
rowsDeleted: string[];
|
|
421
|
+
sentinelDeleted: boolean;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Delete the epic row(s) then the sentinel (compare-and-delete), under the
|
|
425
|
+
* lifecycle lock. `epicKey` null deletes every lifecycle row WITHOUT parsing
|
|
426
|
+
* payloads (corrupt-state repair for `close --abandon`).
|
|
427
|
+
*/
|
|
428
|
+
export declare function deleteEpicState(directory: string, epicKey: string | null, token: string | null): EpicDeleteResult;
|
|
429
|
+
export type EpicSentinelRepair = 'none' | 'removed-stale-sentinel' | 'restored-sentinel' | 'rewrote-mismatched-sentinel';
|
|
430
|
+
/**
|
|
431
|
+
* Reconcile the sentinel with the authoritative row under the lifecycle lock:
|
|
432
|
+
* - sentinel without any row → remove the sentinel (row confirmed absent);
|
|
433
|
+
* - open row without sentinel → restore the sentinel from the row;
|
|
434
|
+
* - sentinel naming another epic than the row → rewrite it from the row.
|
|
435
|
+
* Corrupt rows are left alone (repair is `close --abandon`).
|
|
436
|
+
*/
|
|
437
|
+
export declare function repairEpicSentinel(directory: string): EpicSentinelRepair;
|