opencode-swarm 7.188.12 → 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.
- 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-4g963859.js → auto-checkpoint-qvk04fya.js} +9 -9
- package/dist/cli/{checkpoint-y9nbjper.js → checkpoint-9q2r5c3j.js} +18 -20
- package/dist/cli/{coder-settlement-q8ea2jkw.js → coder-settlement-xcparbf3.js} +13 -13
- package/dist/cli/{config-doctor-4v5m6v30.js → config-doctor-by0ak49m.js} +23 -20
- package/dist/cli/{core-879105xb.js → core-b1b3kxzy.js} +4 -4
- package/dist/cli/{curation-policy-65ckmmd5.js → curation-policy-6p6fcfjb.js} +6 -6
- package/dist/cli/{curator-t7g3hamd.js → curator-d7b88mzr.js} +44 -44
- package/dist/cli/{curator-drift-satfdnp2.js → curator-drift-6v604220.js} +9 -9
- package/dist/cli/curator-llm-factory-yvbktznk.js +82 -0
- package/dist/cli/{doctor-knowledge-receipt-phase-id-y3ft2v24.js → doctor-knowledge-receipt-phase-id-kwrvnps9.js} +9 -9
- package/dist/cli/{evidence-summary-service-56rqzmrf.js → evidence-summary-service-mzr2q4bn.js} +21 -22
- package/dist/cli/{gate-evidence-zzmgph9z.js → gate-evidence-78jes9kq.js} +8 -8
- package/dist/cli/guardrail-explain-8kt2mxmw.js +83 -0
- package/dist/cli/{guardrail-log-sxv7q0xw.js → guardrail-log-2sthykag.js} +8 -8
- package/dist/cli/{guardrail-reset-37cr442b.js → guardrail-reset-2n746rzv.js} +44 -44
- package/dist/cli/{hive-promoter-hyeta8d6.js → hive-promoter-s7a5gwhz.js} +44 -44
- package/dist/cli/{index-e638qt2d.js → index-0qc7a88d.js} +3 -3
- package/dist/cli/{index-kap0d52p.js → index-1s95jrgc.js} +3 -3
- package/dist/cli/{index-9tvjwv01.js → index-2fpv2wmc.js} +1 -1
- package/dist/cli/{index-jgr1rsr1.js → index-2kpqqcch.js} +4 -4
- package/dist/cli/{index-msvf6p5y.js → index-3t9t9q2q.js} +44 -44
- package/dist/cli/{index-8q8783g0.js → index-3zwhxk98.js} +1 -1
- package/dist/cli/{index-0b172h6r.js → index-49e65y51.js} +5 -5
- package/dist/cli/{index-5pafc9x6.js → index-69tgdm9t.js} +18 -8
- 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-84pwb6cz.js → index-9gcqmgbc.js} +5 -5
- package/dist/cli/{index-9sps637v.js → index-9xxqdzp4.js} +4 -4
- package/dist/cli/{index-j0qep2zr.js → index-a5ca62vt.js} +1 -1
- package/dist/cli/{index-xy2403g9.js → index-ajzgeb2s.js} +2 -2
- package/dist/cli/{index-8xabhfwr.js → index-b2j231jy.js} +2 -2
- package/dist/cli/{index-19yt5p2t.js → index-dmq7p8s9.js} +6 -6
- package/dist/cli/{index-95pzyv9a.js → index-ej1hdw16.js} +5 -5
- package/dist/cli/{index-1krdj1sc.js → index-ejk34dnw.js} +127 -13
- package/dist/cli/{index-tbyvvq50.js → index-fb5s32c2.js} +862 -1221
- package/dist/cli/{index-ntda6tje.js → index-feaxsjrt.js} +10 -2
- package/dist/cli/{index-94fgbb34.js → index-ffgcdf49.js} +22147 -15396
- package/dist/cli/{index-fhtcx5ag.js → index-h0xhec3d.js} +4 -4
- package/dist/cli/{index-kaqdzhj3.js → index-kfevm0n5.js} +2 -2
- package/dist/cli/{index-z7gakzb5.js → index-kp8k95v9.js} +4 -4
- 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-tmn4pf7q.js → index-mp1s7b0n.js} +7 -7
- package/dist/cli/{index-f0jn2nvs.js → index-mseerehd.js} +26 -10
- 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-8jy605yf.js → index-npe10823.js} +7 -7
- package/dist/cli/{index-yc0y5fjt.js → index-p69k8v0e.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-qha2p47y.js → index-rgkzcw2a.js} +5 -5
- package/dist/cli/{index-zfbxfm2m.js → index-s8b67e6h.js} +33 -12
- package/dist/cli/{index-m3hhaf61.js → index-s92a5ta3.js} +4 -4
- package/dist/cli/{index-dd6p8xb5.js → index-sm01zpqe.js} +46 -46
- package/dist/cli/{index-q9kefh33.js → index-sxj143a4.js} +1 -1
- package/dist/cli/{index-2h13hjbf.js → index-t0gj8s46.js} +77 -16
- package/dist/cli/{index-3dqazqk4.js → index-t73adn9b.js} +2 -2
- package/dist/cli/{index-5cksakjf.js → index-tkhenmms.js} +101 -13
- package/dist/cli/{index-zsrtt42s.js → index-v3bmwmqw.js} +25 -1
- package/dist/cli/{index-w9tfb0am.js → index-vhn4e6rm.js} +7 -7
- package/dist/cli/{index-yjjyapbf.js → index-xyvrfram.js} +2 -2
- package/dist/cli/{index-k8sarme1.js → index-y9v41185.js} +1 -1
- 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-7z2pxxye.js → index-zyg1m9d0.js} +4 -4
- package/dist/cli/index.js +45 -45
- package/dist/cli/{knowledge-escalator-dzd35eb3.js → knowledge-escalator-n1ezv1zp.js} +15 -15
- package/dist/cli/{knowledge-events-wkr5ne5f.js → knowledge-events-nyqk3mr7.js} +13 -13
- package/dist/cli/{knowledge-link-h4pdcnya.js → knowledge-link-6hw3z6wn.js} +5 -5
- package/dist/cli/{knowledge-store-sme6sqrb.js → knowledge-store-mnxqcyet.js} +6 -6
- package/dist/cli/{knowledge-validator-jgzeawa8.js → knowledge-validator-z30113mp.js} +8 -8
- package/dist/cli/{mcp-egrbh8na.js → mcp-mnbqw2s5.js} +1 -1
- package/dist/cli/{model-preflight-h7azh4m4.js → model-preflight-ex482fsy.js} +44 -44
- package/dist/cli/{pending-delegations-pg284vsk.js → pending-delegations-j18afz5z.js} +12 -12
- package/dist/cli/{pr-subscriptions-hzc4fg25.js → pr-subscriptions-yngda8mz.js} +12 -12
- package/dist/cli/{pr-workflow-gate-j3r3g3q7.js → pr-workflow-gate-0vt614f0.js} +44 -44
- package/dist/cli/{runner-4hfmp1ag.js → runner-ssnsje35.js} +6 -6
- package/dist/cli/{scan-cursor-zg0a56c1.js → scan-cursor-8pwfg5g9.js} +7 -7
- package/dist/cli/{schema-p246rqkt.js → schema-e58ytmqt.js} +14 -2
- package/dist/cli/{scope-persistence-7qtsmx5c.js → scope-persistence-9qfx6ext.js} +16 -16
- package/dist/cli/{server-twhpwhgq.js → server-wxcfwsdx.js} +45 -45
- package/dist/cli/{skill-generator-7vfbmyrw.js → skill-generator-tbd4tq9r.js} +17 -17
- package/dist/cli/{snapshot-coordination-init-050hf69t.js → snapshot-coordination-init-sckyzb94.js} +44 -44
- package/dist/cli/{speckit-checkoff-d3v522cb.js → speckit-checkoff-k5jm0abh.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-yy1tp8t9.js → worktree-collision-ownership-3wwfc72t.js} +14 -14
- package/dist/cli/{worktree-isolation-e56skbfw.js → worktree-isolation-zfk7zk6a.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-fx1qnpsh.js +0 -82
- package/dist/cli/guardrail-explain-g03manbh.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,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 {};
|