opencode-swarm 7.86.0 → 7.87.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/cli/{capability-probe-jevmgwmf.js → capability-probe-wsjzcp48.js} +2 -2
  2. package/dist/cli/{config-doctor-zejarrr6.js → config-doctor-6h64pn8n.js} +4 -4
  3. package/dist/cli/{dispatch-k86d928w.js → dispatch-kb69qw40.js} +3 -3
  4. package/dist/cli/{evidence-summary-service-g2znnd33.js → evidence-summary-service-gg5m9z57.js} +4 -4
  5. package/dist/cli/{guardrail-explain-rtd1x26f.js → guardrail-explain-scym5r5y.js} +13 -13
  6. package/dist/cli/{guardrail-log-80116wmz.js → guardrail-log-eegabqcp.js} +5 -5
  7. package/dist/cli/{index-0sxvwjt0.js → index-1cb4wxnm.js} +2 -2
  8. package/dist/cli/{index-zfsbaaqh.js → index-5e4e2hvv.js} +1 -1
  9. package/dist/cli/{index-vq2321gg.js → index-5hvbw5xh.js} +2 -2
  10. package/dist/cli/{index-5cb86007.js → index-5vpe6vq9.js} +1 -1
  11. package/dist/cli/{index-red8fm8p.js → index-89xjr3h4.js} +1162 -214
  12. package/dist/cli/{index-f8r50m3h.js → index-adz3nk9b.js} +2 -2
  13. package/dist/cli/{index-jwz50183.js → index-dsjyfd3g.js} +14 -14
  14. package/dist/cli/{index-ckntc5gf.js → index-gn8n22th.js} +2 -2
  15. package/dist/cli/{index-5q66xc88.js → index-gwzpy671.js} +2699 -1403
  16. package/dist/cli/{index-hw9b2xng.js → index-q9h0wb04.js} +36 -3
  17. package/dist/cli/{index-d9fbxaqd.js → index-s8bj492g.js} +1 -1
  18. package/dist/cli/{index-7r2b453y.js → index-ts2j1wjr.js} +2 -2
  19. package/dist/cli/{index-hz59hg4h.js → index-v4fcn4tr.js} +1 -1
  20. package/dist/cli/{index-eb85wtx9.js → index-vqyfscxd.js} +2 -2
  21. package/dist/cli/{index-yx44zd0p.js → index-zgwm4ryv.js} +9 -1
  22. package/dist/cli/index.js +12 -12
  23. package/dist/cli/{pending-delegations-rd40tv9s.js → pending-delegations-35fvcj7z.js} +3 -3
  24. package/dist/cli/{pr-subscriptions-y1nn36e5.js → pr-subscriptions-b18n1yd8.js} +4 -4
  25. package/dist/cli/{schema-8d32b2v6.js → schema-84146tvk.js} +3 -1
  26. package/dist/cli/{skill-generator-a5ehggyg.js → skill-generator-3pvpk4y2.js} +2 -2
  27. package/dist/commands/coupling.d.ts +36 -0
  28. package/dist/commands/epic.d.ts +52 -0
  29. package/dist/commands/registry.d.ts +18 -2
  30. package/dist/config/constants.d.ts +1 -0
  31. package/dist/config/schema.d.ts +145 -0
  32. package/dist/git/branch.d.ts +22 -1
  33. package/dist/hooks/delegation-gate/worktree-merge-status.d.ts +86 -0
  34. package/dist/index.js +8577 -5858
  35. package/dist/memory/schema.d.ts +3 -3
  36. package/dist/memory/scoring.d.ts +18 -0
  37. package/dist/memory/sqlite-provider.d.ts +10 -0
  38. package/dist/plan/manager.d.ts +10 -0
  39. package/dist/state.d.ts +16 -0
  40. package/dist/tools/epic-plan-waves.d.ts +79 -0
  41. package/dist/tools/epic-record-divergence.d.ts +73 -0
  42. package/dist/tools/epic-run-phase.d.ts +179 -0
  43. package/dist/tools/index.d.ts +3 -0
  44. package/dist/tools/manifest.d.ts +3 -0
  45. package/dist/tools/tool-metadata.d.ts +12 -0
  46. package/dist/turbo/epic/activation.d.ts +193 -0
  47. package/dist/turbo/epic/calibration-engine.d.ts +88 -0
  48. package/dist/turbo/epic/calibration.d.ts +65 -0
  49. package/dist/turbo/epic/cochange-conflict.d.ts +79 -0
  50. package/dist/turbo/epic/cochange-source.d.ts +80 -0
  51. package/dist/turbo/epic/coupling-report.d.ts +85 -0
  52. package/dist/turbo/epic/divergence-recorder.d.ts +112 -0
  53. package/dist/turbo/epic/index.d.ts +24 -0
  54. package/dist/turbo/epic/promotion-evidence.d.ts +42 -0
  55. package/dist/turbo/epic/state.d.ts +85 -0
  56. package/dist/turbo/epic/task-commit.d.ts +110 -0
  57. package/dist/turbo/epic/upstream-commits.d.ts +82 -0
  58. package/dist/turbo/epic/wave-planner.d.ts +83 -0
  59. package/dist/turbo/lean/partition-common.d.ts +85 -0
  60. package/dist/turbo/lean/planner.d.ts +12 -20
  61. package/dist/utils/index.d.ts +1 -1
  62. package/dist/utils/logger.d.ts +19 -0
  63. package/package.json +1 -1
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Divergence recorder for Epic Mode Capability D (self-calibration).
3
+ *
4
+ * After every task transitions to `completed`, this module:
5
+ * 1. Compares the task's DECLARED scope (from
6
+ * `.swarm/scopes/scope-{taskId}.json` — what the coder said it would
7
+ * touch) against the ACTUAL files modified during the task
8
+ * (`session.modifiedFilesThisCoderTask` — what the guardrails hook
9
+ * observed the coder writing to).
10
+ * 2. Computes divergence — undeclared writes (actual − declared), unused
11
+ * declarations (declared − actual), and a per-task divergence ratio
12
+ * (undeclared / max(1, actual)).
13
+ * 3. Appends one record to `.swarm/epic/divergence.jsonl`.
14
+ *
15
+ * The calibration engine (`./calibration-engine.ts`) reads this history on
16
+ * the next `epic_decide_phase` invocation and uses it to adjust the
17
+ * activation threshold and hot-module list. This module just records.
18
+ *
19
+ * Pure I/O: never throws to the caller. Failures are logged and swallowed
20
+ * so the task-completion path is never blocked by an audit write.
21
+ */
22
+ /** One record per task completion. */
23
+ export interface DivergenceRecord {
24
+ /** ISO 8601. */
25
+ timestamp: string;
26
+ sessionID: string;
27
+ taskId: string;
28
+ /** Phase the task belonged to, when known. */
29
+ phaseNumber?: number;
30
+ /** Normalised paths declared via `declare_scope` or files_touched fallback. */
31
+ declaredScope: string[];
32
+ /** Normalised paths the guardrails hook observed the coder write to. */
33
+ actualFiles: string[];
34
+ /** Files in `actualFiles` not present in `declaredScope`. */
35
+ undeclared: string[];
36
+ /** Files in `declaredScope` not present in `actualFiles`. */
37
+ unused: string[];
38
+ /** undeclared.length / max(1, actualFiles.length). 0 ⇒ fully declared. */
39
+ divergenceRatio: number;
40
+ /** True when divergenceRatio === 0 (no undeclared writes). */
41
+ isClean: boolean;
42
+ }
43
+ /**
44
+ * Compute the divergence between a declared scope and the files actually
45
+ * modified. Pure — no I/O, no side effects. Returns the diff sets plus the
46
+ * ratio used by the calibration engine.
47
+ *
48
+ * Path comparison uses `normalizePath` (POSIX-style, no trailing slash,
49
+ * Windows-lowercased) from Lean Turbo's conflicts module so the comparison
50
+ * is consistent with everything else in the lane planner.
51
+ */
52
+ export declare function computeDivergence(declaredScope: readonly string[], actualFiles: readonly string[]): {
53
+ declared: string[];
54
+ actual: string[];
55
+ undeclared: string[];
56
+ unused: string[];
57
+ divergenceRatio: number;
58
+ };
59
+ interface RecordTaskDivergenceArgs {
60
+ directory: string;
61
+ sessionID: string;
62
+ taskId: string;
63
+ phaseNumber?: number;
64
+ declaredScope: readonly string[];
65
+ actualFiles: readonly string[];
66
+ }
67
+ /**
68
+ * Append one divergence record to the JSONL audit file.
69
+ *
70
+ * Append-only, line-delimited so partial writes are tolerable (the calibration
71
+ * reader skips malformed lines). Best-effort — never throws to caller:
72
+ * - Directory-creation failure → log and return null.
73
+ * - Append write failure → log and return null.
74
+ * Either keeps the task-completion path moving even if the audit subsystem
75
+ * is broken (audit miss is not a correctness issue; blocking task completion
76
+ * would be).
77
+ */
78
+ export declare function recordTaskDivergence(args: RecordTaskDivergenceArgs): {
79
+ path: string;
80
+ record: DivergenceRecord;
81
+ } | null;
82
+ export interface ReadDivergenceHistoryOptions {
83
+ /** Read at most this many of the most recent records. */
84
+ limit?: number;
85
+ /** Filter to this session (default: all sessions). */
86
+ sessionID?: string;
87
+ /**
88
+ * Maximum bytes to read from the tail of the file. Defaults to
89
+ * `MAX_TAIL_BYTES` (16 MiB) — large enough to hold thousands of
90
+ * records, small enough to avoid OOMing on a runaway audit log.
91
+ * Pass `Infinity` to disable the bound (callers that truly need the
92
+ * whole history — adversarial review H3).
93
+ */
94
+ maxBytes?: number;
95
+ }
96
+ /**
97
+ * Read divergence records from disk, oldest-to-newest within the read
98
+ * window. Malformed lines (rare — could occur on partial write) are
99
+ * silently skipped — they do not corrupt the well-formed records before or
100
+ * after them. Returns `[]` when the file does not exist.
101
+ *
102
+ * Tail-bounded: by default reads at most the last `MAX_TAIL_BYTES`. When
103
+ * the file is larger, the read starts mid-file and the FIRST encountered
104
+ * line (which is almost certainly a partial record split by the byte
105
+ * boundary) is discarded. This means very old records are not returned by
106
+ * a default-bounded read — the calibration engine consumes the tail
107
+ * incrementally via `processedRecords`, so it never needs the full history
108
+ * in memory at once. For full-history audit reads (tests, ad-hoc tooling),
109
+ * pass `maxBytes: Infinity`.
110
+ */
111
+ export declare function readDivergenceHistory(directory: string, options?: ReadDivergenceHistoryOptions): DivergenceRecord[];
112
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Epic mode — barrel export.
3
+ *
4
+ * Epic mode is a new, additive execution mode that composes Lean Turbo without
5
+ * modifying it. Capabilities:
6
+ * - A: co-change-aware pair conflict (`epicPairConflict`).
7
+ * - B: coupling KPI + decoupling roadmap (`computeCouplingReport`).
8
+ * - C: per-plan activation decision (`decideEpicActivation`).
9
+ *
10
+ * Dependency direction is one-way: `epic` depends on `lean`; `lean` never
11
+ * depends on `epic`. All Lean Turbo files stay byte-for-byte untouched.
12
+ */
13
+ export type { EpicActivationOptions, EpicActivationRationale, EpicActivationVerdict, } from './activation.js';
14
+ export { decideEpicActivation } from './activation.js';
15
+ export type { CoChangeThreshold, EpicPairVerdict, } from './cochange-conflict.js';
16
+ export { epicPairConflict } from './cochange-conflict.js';
17
+ export type { CoChangeData, GetCoChangePairsOptions, } from './cochange-source.js';
18
+ export { getCoChangeData, getCoChangePairs } from './cochange-source.js';
19
+ export type { ComputeCouplingReportOptions, ConflictingPair, CouplingReport, CouplingTask, ModuleContention, } from './coupling-report.js';
20
+ export { computeCouplingReport, formatCouplingReportMarkdown, } from './coupling-report.js';
21
+ export type { PromotionEvidenceRecord } from './promotion-evidence.js';
22
+ export { appendPromotionEvidence, readPromotionEvidence, } from './promotion-evidence.js';
23
+ export type { EpicLastDecision, EpicPersistedState, EpicSessionState, } from './state.js';
24
+ export { disableEpicMode, emptyPersisted as emptyEpicPersisted, emptySessionState as emptyEpicSessionState, enableEpicMode, isEpicModeActive, isStateUnreadable as isEpicStateUnreadable, loadEpicSessionState, recordEpicDecision, repairStateUnreadable as repairEpicStateUnreadable, resetEpicSession, saveEpicSessionState, } from './state.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Promotion-evidence writer for Epic Mode (Capability C).
3
+ *
4
+ * Each activation decision (one per phase per session) appends a single
5
+ * JSON line to `.swarm/evidence/epic-promotions.jsonl`. The file is
6
+ * line-delimited so partial writes are tolerable — readers can skip a
7
+ * truncated trailing line and continue.
8
+ *
9
+ * The write is intentionally append-mode (not atomic-rename) because
10
+ * each line is a self-contained record and the file is monotonically
11
+ * growing. Per AGENTS.md invariant 4, writes go under `ctx.directory`,
12
+ * never `process.cwd()`.
13
+ */
14
+ import type { EpicActivationVerdict } from './activation.js';
15
+ /** What `appendPromotionEvidence` writes per decision. */
16
+ export interface PromotionEvidenceRecord {
17
+ /** ISO 8601 timestamp of the decision. */
18
+ timestamp: string;
19
+ /** Session that made the decision (so multi-session usage stays auditable). */
20
+ sessionID: string;
21
+ /** Phase the decision applied to (Capability C operates per-plan but each
22
+ * phase invokes the runner; we record per phase for granular telemetry). */
23
+ phase?: number;
24
+ /** The decision and the rationale, copied from the activation verdict. */
25
+ verdict: EpicActivationVerdict;
26
+ }
27
+ /**
28
+ * Append one decision record to `.swarm/evidence/epic-promotions.jsonl`.
29
+ *
30
+ * Returns the absolute path of the file written. On any I/O failure the
31
+ * error is rethrown — the caller decides whether to surface a warning to
32
+ * the user or fail closed. Returns null when the directory itself cannot
33
+ * be created (best-effort fail-soft so a missing `.swarm/` does not break
34
+ * the activation flow's primary verdict).
35
+ */
36
+ export declare function appendPromotionEvidence(directory: string, record: PromotionEvidenceRecord): string | null;
37
+ /**
38
+ * Read all evidence records from the JSONL file. Convenience for
39
+ * `/swarm epic status` and tests. Skips malformed lines (best-effort
40
+ * tolerance for the rare partial-write case).
41
+ */
42
+ export declare function readPromotionEvidence(directory: string): PromotionEvidenceRecord[];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Durable Epic Mode session state (Capability C).
3
+ *
4
+ * Persists per-session Epic Mode activation state under
5
+ * `<projectRoot>/.swarm/epic-state.json` so toggling survives process
6
+ * restarts. Mirrors the pattern in `src/turbo/lean/state.ts` (atomic
7
+ * `tmp + rename`, per-directory `stateUnreadableMap` for fail-closed
8
+ * semantics, sessions-keyed shape) — without modifying that file.
9
+ *
10
+ * Dependency direction is one-way: this module imports nothing from
11
+ * `src/turbo/lean/`. The shape is parallel but independent.
12
+ */
13
+ /** Top-level state for a single session. */
14
+ export interface EpicSessionState {
15
+ sessionID: string;
16
+ /** When epic mode was last enabled for this session (ISO 8601). */
17
+ enabledAt?: string;
18
+ /** When epic mode was last disabled for this session (ISO 8601). */
19
+ disabledAt?: string;
20
+ /** Most recent activation decision recorded for this session, if any. */
21
+ lastDecision?: EpicLastDecision;
22
+ /** Whether epic mode is currently active for this session. */
23
+ active: boolean;
24
+ }
25
+ /** Minimal snapshot of the last activation decision. */
26
+ export interface EpicLastDecision {
27
+ decidedAt: string;
28
+ phase?: number;
29
+ decision: 'promote' | 'demote';
30
+ p: number;
31
+ blockingReasons: string[];
32
+ }
33
+ /** Persisted shape of `.swarm/epic-state.json`. */
34
+ export interface EpicPersistedState {
35
+ version: 1;
36
+ updatedAt: string;
37
+ sessions: Record<string, EpicSessionState>;
38
+ }
39
+ export declare function emptyPersisted(): EpicPersistedState;
40
+ export declare function emptySessionState(sessionID: string): EpicSessionState;
41
+ export declare function isStateUnreadable(directory: string): boolean;
42
+ export declare function repairStateUnreadable(directory: string): void;
43
+ /** Read this session's state, or null if not yet recorded. */
44
+ export declare function loadEpicSessionState(directory: string, sessionID: string): EpicSessionState | null;
45
+ /** Write the given session state, replacing any prior entry for that sessionID. */
46
+ export declare function saveEpicSessionState(directory: string, state: EpicSessionState): void;
47
+ /** True iff epic mode is currently active for the given session. */
48
+ export declare function isEpicModeActive(directory: string, sessionID: string): boolean;
49
+ /**
50
+ * True iff epic mode is currently active for ANY session in the project.
51
+ *
52
+ * Use this when a code path needs to know "is the project running under
53
+ * Epic Mode right now" without caring which session toggled it. The
54
+ * session-scoped `isEpicModeActive` answers "did THIS session toggle it" —
55
+ * a different question with a different answer.
56
+ *
57
+ * The architect's session enables Epic via `/swarm epic on`; sub-agents
58
+ * (coders, reviewers) dispatched through the `Task` tool run in their own
59
+ * sessions and have no record of that toggle. Asking the project-scoped
60
+ * check is the only correct way to honor Epic Mode from those flows.
61
+ * Rule 2's auto-commit (centralized in Phase 5) is the canonical caller.
62
+ *
63
+ * Fail-closed: returns `false` on unreadable state, matching the rest of
64
+ * this module's defaults.
65
+ */
66
+ export declare function isEpicModeActiveForProject(directory: string): boolean;
67
+ /** Enable epic mode for the session; records `enabledAt`. */
68
+ export declare function enableEpicMode(directory: string, sessionID: string): void;
69
+ /** Disable epic mode for the session; records `disabledAt`. */
70
+ export declare function disableEpicMode(directory: string, sessionID: string): void;
71
+ /** Reset the session's state entry entirely. */
72
+ export declare function resetEpicSession(directory: string, sessionID: string): void;
73
+ /**
74
+ * Update the session's `lastDecision` field. Used by the runner after each
75
+ * activation evaluation so `/swarm epic status` can show the most recent
76
+ * decision rationale without re-reading the evidence JSONL.
77
+ *
78
+ * Precondition: the session must already have an entry (i.e. the caller has
79
+ * called `enableEpicMode` previously). This is intentional — recording a
80
+ * decision for a never-toggled session would produce phantom state that
81
+ * `/swarm epic status` could not distinguish from a legitimately-active
82
+ * session. Callers that reach this function should have already verified
83
+ * `isEpicModeActive(...)` returned `true`. Throws if no session entry exists.
84
+ */
85
+ export declare function recordEpicDecision(directory: string, sessionID: string, decision: EpicLastDecision): void;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Auto-commit on task completion — Rule 2 of the greenfield-smart redesign.
3
+ *
4
+ * When Epic Mode is active for the session and the project is a git repo,
5
+ * `update_task_status` calls `commitTaskCompletion` after a task transitions
6
+ * to `completed` and the durable plan write has succeeded. The resulting
7
+ * commit serves two purposes:
8
+ *
9
+ * 1. **Greenfield gate progress.** Each completed-and-committed task
10
+ * advances `commitsObserved` so the activation gate
11
+ * (`src/turbo/epic/activation.ts`) eventually opens. Without this,
12
+ * an Epic-only workflow never produces commits and the gate
13
+ * permanently blocks parallel promotion — the exact failure mode
14
+ * Rule 4 of the redesign identified.
15
+ *
16
+ * 2. **Parallel-eligibility evidence (Rule 3).** Downstream tasks can
17
+ * require their `depends:` upstream to be *committed* (not just
18
+ * marked complete) before they fan out. The commit message format
19
+ * `swarm(task <id>): ...` is the searchable marker the lane
20
+ * planner consumes in `upstream-commits.ts`.
21
+ *
22
+ * Failure handling: every step degrades non-fatally. A failed commit must
23
+ * never block the durable task-status update — the plan ledger is the
24
+ * authoritative source (AGENTS.md #5), git is a downstream artifact.
25
+ *
26
+ * Subprocess discipline: delegates to `src/git/branch.ts`, which already
27
+ * enforces AGENTS.md #3 (explicit cwd, bounded timeout, array-form spawn,
28
+ * non-interactive). This module adds no new subprocess primitives.
29
+ */
30
+ /** Result of a single task-commit attempt. */
31
+ export interface CommitTaskCompletionResult {
32
+ /**
33
+ * `true` when a `swarm(task <id>):` marker for this taskId is present
34
+ * in git history at function exit — whether this call produced it
35
+ * (`reason: 'success'`) or whether an earlier call did
36
+ * (`reason: 'idempotent-skip'`).
37
+ *
38
+ * Phase 17 (B.M9): pre-Phase-17 the `'already-committed'` reason
39
+ * returned `committed: false`, self-contradicting ("not committed
40
+ * because already committed"). Architect LLMs interpreted the
41
+ * `false` as a failure and retried, producing log noise. The fixed
42
+ * semantic: `committed` answers "is the marker in git for this
43
+ * taskId now?" — yes for both the fresh-write and the idempotent
44
+ * skip paths.
45
+ */
46
+ committed: boolean;
47
+ reason: 'no-git' | 'commit-failed' | 'success' | 'idempotent-skip';
48
+ sha?: string;
49
+ error?: string;
50
+ }
51
+ export declare function formatTaskCommitMessage(taskId: string, description?: string): string;
52
+ export declare function commitTaskCompletion(directory: string, taskId: string, description?: string, scopePaths?: string[]): Promise<CommitTaskCompletionResult>;
53
+ /**
54
+ * DI seam — production code calls through `_internals.<name>` so tests
55
+ * substitute deterministic doubles without `mock.module`'s cross-file
56
+ * leak (AGENTS.md invariant 7). Restore in `afterEach`.
57
+ */
58
+ export declare const _internals: {
59
+ isGitRepo: (cwd: string) => boolean;
60
+ /**
61
+ * Stage exactly the declared scope paths for this task. The trailing
62
+ * `:(exclude).swarm` + `:(exclude).swarm/**` pathspecs are belt-and-
63
+ * suspenders against AGENTS.md #4 even when the architect's scope
64
+ * declaration accidentally points into `.swarm/`. We do NOT rely on
65
+ * the user's `.gitignore`: a single misconfigured project would
66
+ * otherwise commit prompts, ledgers, telemetry, and evidence into
67
+ * git history every time Rule 2 fires.
68
+ *
69
+ * Missing pathspecs (declared scope points to a non-existent file) are
70
+ * left to surface as a `commit-failed` reason — better than silent
71
+ * staging skip, since it tells the user their scope declaration is
72
+ * stale. Rule 2's non-fatal contract means the plan-write still wins.
73
+ */
74
+ stageScopedPaths: (cwd: string, paths: string[]) => void;
75
+ /**
76
+ * `--allow-empty` variant of commit. We don't expose this in
77
+ * `src/git/branch.ts` because it's specific to the task-completion
78
+ * marker semantics — a normal commit should fail on empty trees to
79
+ * surface bugs. Here we explicitly want the marker.
80
+ *
81
+ * Phase 8: `--no-verify` skips `pre-commit`, `commit-msg`, and
82
+ * `pre-commit-msg` hooks. Rule 2's commits are protocol markers, not
83
+ * user-authored content — running Biome/typecheck/lint on every task
84
+ * completion would add minutes of wall-clock per task and, worse,
85
+ * could block the marker entirely on a repo with a strict pre-commit
86
+ * gate. Plan ledger remains authoritative; the commit is the audit
87
+ * trail, not the gate.
88
+ */
89
+ commitAllowEmpty: (cwd: string, message: string) => void;
90
+ gitHeadSha: (cwd: string) => string;
91
+ /**
92
+ * Returns true when a `swarm(task <id>):` marker subject for this
93
+ * taskId already exists anywhere in git history. Used by the
94
+ * idempotency guard above so repeat completion calls don't mint
95
+ * duplicate markers.
96
+ *
97
+ * Implementation: `git log --grep=<pattern> -F` is NOT used (it
98
+ * fixed-string-matches the whole subject); instead we anchor the
99
+ * regex with `--extended-regexp` and bound the scan with `-n 1` so a
100
+ * single match suffices. Returns false on any git failure — the
101
+ * caller treats that as "unknown" and proceeds.
102
+ */
103
+ /**
104
+ * Phase 11 (B5): async sleep used by `commitTaskCompletion`'s
105
+ * retry loop. Routed through `_internals` so tests can substitute
106
+ * a no-op stub and not actually wait during fast-path unit tests.
107
+ */
108
+ sleep: (ms: number) => Promise<void>;
109
+ hasExistingTaskCommit: (cwd: string, taskId: string) => boolean;
110
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Upstream-commit predicate — Rule 3 of the greenfield-smart redesign.
3
+ *
4
+ * Given a project directory, returns a fast `(taskId) => boolean` predicate
5
+ * that answers "has this task been committed?" The lane planner consults
6
+ * this when evaluating cross-batch dependencies: a downstream task is
7
+ * parallel-eligible only when every `depends:` upstream that lives in a
8
+ * prior phase batch is already in git HEAD's history.
9
+ *
10
+ * Why this matters: without it, the planner treats any dep not in the
11
+ * current task batch as implicitly satisfied (the existing behavior in
12
+ * `src/turbo/lean/planner.ts:381-390`). That's fine when the prior batch
13
+ * actually finished cleanly, but it doesn't distinguish "marked complete"
14
+ * from "marked complete *and* the work is in version control". Rule 3
15
+ * insists on the stronger condition so parallel coders can't inherit an
16
+ * uncommitted upstream worktree.
17
+ *
18
+ * Source format: `commitTaskCompletion` writes commit subjects shaped like
19
+ * `swarm(task <id>): <description>` (see `./task-commit.ts:formatTaskCommitMessage`).
20
+ * This module parses the `<id>` from those subjects.
21
+ *
22
+ * Boundedness (AGENTS.md #3): subprocess timeout matches the rest of git
23
+ * helpers (30s); `--max-count` caps the log scan; failures degrade to a
24
+ * permissive predicate so the planner does not regress when git is broken.
25
+ */
26
+ export interface BuildUpstreamCommitsOptions {
27
+ /** Override the log scan window. Default: 10,000. */
28
+ maxCommits?: number;
29
+ }
30
+ /**
31
+ * Eagerly read git log once and return a fast `(taskId) => boolean`
32
+ * predicate the lane planner uses for cross-batch upstream-commit checks.
33
+ *
34
+ * Single evidence source: a `swarm(task <id>):` commit subject in git
35
+ * log, produced by Rule 2's `commitTaskCompletion`.
36
+ *
37
+ * History note: an earlier revision OR'd in a plan-ledger fallback (any
38
+ * task with `status: completed` in `.swarm/plan.json` was treated as
39
+ * committed) to guard against a hypothetical deadlock where `commit-
40
+ * TaskCompletion` failed silently. Phase 5 of the 2026-06-03 corrective
41
+ * plan made Rule 2 reliable on every completion path by centralizing
42
+ * the invocation in `plan/manager.updateTaskStatus` — so the guard's
43
+ * premise no longer holds, and keeping the fallback would defeat Rule 3's
44
+ * own purpose (distinguishing "marked complete" from "in git history").
45
+ * Removed in Phase 6.
46
+ *
47
+ * Failure mode:
48
+ * - Git log read fails (no git, spawn error, timeout) → predicate
49
+ * returns `true` for everything (permissive). The lane planner falls
50
+ * back to its legacy "cross-batch dep implicitly satisfied" behavior,
51
+ * which is the pre-Rule-3 semantics. Better legacy than wedged in a
52
+ * broken environment.
53
+ */
54
+ export declare function buildIsUpstreamCommitted(directory: string, options?: BuildUpstreamCommitsOptions): (taskId: string) => boolean;
55
+ /**
56
+ * Phase 12 (B10) — same predicate construction as `buildIsUpstreamCommitted`
57
+ * but exposes whether the git-log read failed. Callers that need to FAIL
58
+ * CLOSED on a broken git environment (e.g. the Phase 10 activation gate,
59
+ * where the predicate is the only safety signal) use `gitFailed` to pick
60
+ * a stricter policy; callers that can tolerate "I don't know" fall-open
61
+ * (e.g. Rule 3 at the lane planner, where wave ordering is the backstop)
62
+ * continue using `buildIsUpstreamCommitted` directly.
63
+ *
64
+ * The two-tier API exists because the original permissive degradation
65
+ * was correct for Rule 3 (no regression vs pre-Rule-3 semantics) but
66
+ * inverted the safety polarity for Phase 10 (which has no Path-B
67
+ * fallback after the commit-count floor was retired).
68
+ */
69
+ export interface UpstreamCommittedEvidence {
70
+ predicate: (taskId: string) => boolean;
71
+ /** True when the git-log read threw — predicate is the permissive fallback. */
72
+ gitFailed: boolean;
73
+ }
74
+ export declare function buildIsUpstreamCommittedWithStatus(directory: string, options?: BuildUpstreamCommitsOptions): UpstreamCommittedEvidence;
75
+ /**
76
+ * DI seam — production code routes the git-log read through `_internals`
77
+ * so tests can substitute deterministic doubles without `mock.module`
78
+ * (AGENTS.md invariant 7).
79
+ */
80
+ export declare const _internals: {
81
+ readGitLogSubjects: (cwd: string, max: number) => string;
82
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Epic Mode Wave Planner.
3
+ *
4
+ * The wave planner partitions a phase's pending tasks into ordered *waves*.
5
+ * A wave is a set of tasks that:
6
+ * - have all their dependencies satisfied by tasks in completed waves, AND
7
+ * - have mutually disjoint declared scopes (no two tasks in the same wave
8
+ * touch a shared file or parent/child path).
9
+ *
10
+ * Architectural contrast with the lane planner:
11
+ * - Lane planner: greedy fill into a fixed number of independent serial
12
+ * chains. Branching DAGs (e.g. `A → B → {C, D, E}`) collapse into a
13
+ * single chain because the cross-lane-dep rule forbids C/D/E from
14
+ * living in a different lane than A/B.
15
+ * - Wave planner: emits a sequence of concurrent groups. The same DAG
16
+ * becomes `wave 1: [A], wave 2: [B], wave 3: [C, D, E]`. Within a wave
17
+ * the architect dispatches one Task per taskId — all in one message —
18
+ * and the next wave starts only when the prior one is done.
19
+ *
20
+ * The two planners share `runPartitionPreflight` from `partition-common.ts`
21
+ * so classification (global/protected/no-scope/normal), scope resolution,
22
+ * and topological sort are identical. Divergence is intentional and lives
23
+ * only in the assignment loop below.
24
+ */
25
+ import type { LeanTurboConfig } from '../../config/schema';
26
+ import { type PlanPhase } from '../lean/partition-common';
27
+ import type { LeanTurboDegradedTask } from '../lean/state';
28
+ /**
29
+ * A single wave in the Epic plan. Tasks in a wave run concurrently; the next
30
+ * wave starts only after all tasks in this wave complete.
31
+ */
32
+ export interface EpicWave {
33
+ /** 1-indexed wave number (display + ordering). */
34
+ waveId: number;
35
+ /** Tasks in this wave. Architect dispatches one Task per id, all in one message. */
36
+ taskIds: string[];
37
+ /**
38
+ * Union of declared scope files across the wave. Informational — Epic
39
+ * Mode does NOT acquire file locks (architect-driven dispatch goes
40
+ * directly through `Task`, bypassing the lean runner's lock path).
41
+ * Used for evidence/divergence reporting.
42
+ */
43
+ files: string[];
44
+ }
45
+ /**
46
+ * The complete wave plan produced by `planEpicWaves`.
47
+ */
48
+ export interface EpicWavePlan {
49
+ /** The phase number this plan covers. */
50
+ phase: number;
51
+ /** Unique identifier for this wave plan. */
52
+ planId: string;
53
+ /** Ordered list of waves. Empty when every task degraded or serialized. */
54
+ waves: EpicWave[];
55
+ /** Tasks that were serialized (cycles, no-scope, invalid-scope, protected with serialize policy). */
56
+ serializedTasks: string[];
57
+ /** Tasks that were degraded (global files, protected paths, Rule-3 leftovers). */
58
+ degradedTasks: LeanTurboDegradedTask[];
59
+ /** Human-readable summary when all tasks ended up degraded. */
60
+ degradationSummary?: string;
61
+ /** Total number of pending tasks the planner saw (before assignment). */
62
+ totalPendingTasks: number;
63
+ /** Sum of `waves[*].taskIds.length` — tasks that will actually run concurrently in a wave. */
64
+ totalConcurrentTasks: number;
65
+ }
66
+ /**
67
+ * Partition phase tasks into ordered concurrent waves.
68
+ *
69
+ * @param directory - Project root directory
70
+ * @param phaseNumber - Phase number to plan
71
+ * @param plan - The full plan object (from `.swarm/plan.json`)
72
+ * @param config - Lean Turbo configuration (reused for risk/conflict policy)
73
+ * @param scopes - Optional pre-loaded scopes map (taskId -> file paths)
74
+ * @param isUpstreamCommitted - Optional Rule-3 predicate (greenfield-smart).
75
+ * When supplied, a cross-batch dependency (a `depends:` upstream NOT
76
+ * in this planning call's task set) is treated as satisfied only if
77
+ * the predicate returns `true`. Without it, legacy semantics apply
78
+ * (cross-batch deps implicitly satisfied).
79
+ * @returns Complete wave plan with ordered concurrent groups.
80
+ */
81
+ export declare function planEpicWaves(directory: string, phaseNumber: number, plan: {
82
+ phases: PlanPhase[];
83
+ }, config: LeanTurboConfig, scopes?: Record<string, string[]>, isUpstreamCommitted?: (taskId: string) => boolean): EpicWavePlan;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared partition primitives for Lean Turbo's lane planner AND Epic Mode's
3
+ * wave planner. Both planners run the same three preflight steps:
4
+ *
5
+ * 1. Resolve declared scopes (or files_touched fallback) per pending task.
6
+ * 2. Classify each task by file risk (global / protected / no-scope / normal).
7
+ * 3. Topologically sort with cycle detection so a downstream task can never
8
+ * be released before its upstream.
9
+ *
10
+ * They diverge only at the assignment step:
11
+ * - Lane planner: greedy fill into a fixed number of serial chains.
12
+ * - Wave planner: emit ordered concurrent groups whose membership is gated
13
+ * by inter-task scope disjointness.
14
+ *
15
+ * Owning the preflight in one place guarantees both planners produce
16
+ * identical classifications and sort orders for the same inputs.
17
+ */
18
+ import type { LeanTurboConfig } from '../../config/schema';
19
+ import { type TaskRiskAssessment } from './risk';
20
+ /**
21
+ * A single task within a plan phase. Matches `.swarm/plan.json`.
22
+ */
23
+ export interface PlanTask {
24
+ id: string;
25
+ description: string;
26
+ status: 'pending' | 'in_progress' | 'completed' | 'blocked';
27
+ depends?: string[];
28
+ files_touched?: string[];
29
+ }
30
+ /**
31
+ * A phase within a plan, containing multiple tasks.
32
+ */
33
+ export interface PlanPhase {
34
+ id: number;
35
+ name: string;
36
+ tasks: PlanTask[];
37
+ }
38
+ export type ClassifiedTask = {
39
+ task: PlanTask;
40
+ files: string[];
41
+ hasDeclaredScope: boolean;
42
+ category: TaskRiskAssessment['category'];
43
+ conflictReason?: string;
44
+ };
45
+ export interface PartitionPreflight {
46
+ /** Topologically sorted, lexicographically tie-broken. */
47
+ sortedTasks: ClassifiedTask[];
48
+ /** taskId -> ClassifiedTask for O(1) lookup. */
49
+ taskMap: Map<string, ClassifiedTask>;
50
+ /** Tasks whose deps form a cycle. Callers must fail-closed: serialize them. */
51
+ tasksInCycle: Set<string>;
52
+ }
53
+ /**
54
+ * Validate and normalize a task's declared scope.
55
+ *
56
+ * Symlink containment is NOT enforced here — the lock layer resolves symlinks
57
+ * at acquisition time. This lets architects declare scopes with symlinks for
58
+ * convenience without compromising actual file-write safety.
59
+ *
60
+ * @returns Tuple of [validFiles, invalidCount]
61
+ */
62
+ export declare function getValidatedFiles(files: string[], directory: string): [string[], number];
63
+ /**
64
+ * Run the shared preflight: resolve scopes, classify by risk, topo-sort with
65
+ * cycle detection.
66
+ *
67
+ * The same `(directory, pendingTasks, config, scopes)` produces the same
68
+ * `PartitionPreflight` from both planners.
69
+ */
70
+ export declare function runPartitionPreflight(directory: string, pendingTasks: PlanTask[], config: LeanTurboConfig, scopes?: Record<string, string[]>): PartitionPreflight;
71
+ /**
72
+ * Build the predicate that a task's dependencies are all satisfied.
73
+ *
74
+ * Rule 3 (greenfield-smart): when `isUpstreamCommitted` is supplied, a
75
+ * cross-batch dependency (a `depends:` upstream NOT in the current planning
76
+ * call's task set — typically completed in a prior phase) is treated as
77
+ * satisfied **only** if the predicate returns `true`. Without the predicate
78
+ * the legacy semantics apply: cross-batch deps are implicitly satisfied.
79
+ */
80
+ export declare function makeDependencySatisfactionChecker(taskMap: Map<string, ClassifiedTask>, assignedTasks: Set<string>, isUpstreamCommitted?: (taskId: string) => boolean): (task: ClassifiedTask) => boolean;
81
+ /**
82
+ * Return the next group of tasks ready for assignment: not yet assigned and
83
+ * all dependencies satisfied. Lexicographically sorted for determinism.
84
+ */
85
+ export declare function getReadyTasks(sortedTasks: ClassifiedTask[], assignedTasks: Set<string>, isSatisfied: (task: ClassifiedTask) => boolean): ClassifiedTask[];