opencode-swarm 7.85.0 → 7.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) 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-w4txg349.js → guardrail-explain-wb1cj312.js} +13 -13
  6. package/dist/cli/{guardrail-log-80116wmz.js → guardrail-log-eegabqcp.js} +5 -5
  7. package/dist/cli/{index-qqabjns2.js → index-0m44n5qv.js} +14 -14
  8. package/dist/cli/{index-0sxvwjt0.js → index-1cb4wxnm.js} +2 -2
  9. package/dist/cli/{index-zfsbaaqh.js → index-5e4e2hvv.js} +1 -1
  10. package/dist/cli/{index-vq2321gg.js → index-5hvbw5xh.js} +2 -2
  11. package/dist/cli/{index-5cb86007.js → index-5vpe6vq9.js} +1 -1
  12. package/dist/cli/{index-red8fm8p.js → index-89xjr3h4.js} +1162 -214
  13. package/dist/cli/{index-f8r50m3h.js → index-adz3nk9b.js} +2 -2
  14. package/dist/cli/{index-x7qck34v.js → index-f13d3b69.js} +2 -2
  15. package/dist/cli/{index-ckntc5gf.js → index-gn8n22th.js} +2 -2
  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-hz59hg4h.js → index-v4fcn4tr.js} +1 -1
  19. package/dist/cli/{index-eb85wtx9.js → index-vqyfscxd.js} +2 -2
  20. package/dist/cli/{index-yhqt45de.js → index-wv2yj8ka.js} +2598 -1406
  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 +8670 -5803
  35. package/dist/memory/schema.d.ts +3 -3
  36. package/dist/plan/manager.d.ts +10 -0
  37. package/dist/state.d.ts +16 -0
  38. package/dist/tools/epic-plan-waves.d.ts +79 -0
  39. package/dist/tools/epic-record-divergence.d.ts +73 -0
  40. package/dist/tools/epic-run-phase.d.ts +179 -0
  41. package/dist/tools/index.d.ts +3 -0
  42. package/dist/tools/manifest.d.ts +3 -0
  43. package/dist/tools/repo-graph/builder.d.ts +46 -0
  44. package/dist/tools/repo-graph/query.d.ts +26 -1
  45. package/dist/tools/repo-graph/types.d.ts +73 -1
  46. package/dist/tools/repo-graph.d.ts +4 -3
  47. package/dist/tools/tool-metadata.d.ts +12 -0
  48. package/dist/turbo/epic/activation.d.ts +193 -0
  49. package/dist/turbo/epic/calibration-engine.d.ts +88 -0
  50. package/dist/turbo/epic/calibration.d.ts +65 -0
  51. package/dist/turbo/epic/cochange-conflict.d.ts +79 -0
  52. package/dist/turbo/epic/cochange-source.d.ts +80 -0
  53. package/dist/turbo/epic/coupling-report.d.ts +85 -0
  54. package/dist/turbo/epic/divergence-recorder.d.ts +112 -0
  55. package/dist/turbo/epic/index.d.ts +24 -0
  56. package/dist/turbo/epic/promotion-evidence.d.ts +42 -0
  57. package/dist/turbo/epic/state.d.ts +85 -0
  58. package/dist/turbo/epic/task-commit.d.ts +110 -0
  59. package/dist/turbo/epic/upstream-commits.d.ts +82 -0
  60. package/dist/turbo/epic/wave-planner.d.ts +83 -0
  61. package/dist/turbo/lean/partition-common.d.ts +85 -0
  62. package/dist/turbo/lean/planner.d.ts +12 -20
  63. package/dist/utils/index.d.ts +1 -1
  64. package/dist/utils/logger.d.ts +19 -0
  65. package/package.json +1 -1
@@ -46,8 +46,8 @@ export declare const MemorySourceSchema: z.ZodObject<{
46
46
  manual: "manual";
47
47
  test: "test";
48
48
  tool: "tool";
49
- user: "user";
50
49
  commit: "commit";
50
+ user: "user";
51
51
  repo: "repo";
52
52
  web: "web";
53
53
  }>;
@@ -105,8 +105,8 @@ export declare const MemoryRecordSchema: z.ZodObject<{
105
105
  manual: "manual";
106
106
  test: "test";
107
107
  tool: "tool";
108
- user: "user";
109
108
  commit: "commit";
109
+ user: "user";
110
110
  repo: "repo";
111
111
  web: "web";
112
112
  }>;
@@ -183,8 +183,8 @@ export declare const MemoryProposalSchema: z.ZodObject<{
183
183
  manual: "manual";
184
184
  test: "test";
185
185
  tool: "tool";
186
- user: "user";
187
186
  commit: "commit";
187
+ user: "user";
188
188
  repo: "repo";
189
189
  web: "web";
190
190
  }>;
@@ -39,6 +39,11 @@ export interface AcknowledgedRemovals {
39
39
  source: string;
40
40
  }
41
41
  import { type Plan, type RuntimePlan, type TaskStatus } from '../config/plan-schema';
42
+ import { isGitRepo } from '../git/branch';
43
+ import { getWorktreeMergeFailure } from '../hooks/delegation-gate/worktree-merge-status';
44
+ import { isEpicModeActiveForProject } from '../turbo/epic/state.js';
45
+ import { commitTaskCompletion } from '../turbo/epic/task-commit.js';
46
+ import { readTaskScopes } from '../turbo/lean/conflicts.js';
42
47
  import { type LedgerEvent, type LedgerEventInput, takeSnapshotWithRetry } from './ledger';
43
48
  /** Reset the startup ledger check flag. For testing only. */
44
49
  export declare function resetStartupLedgerCheck(): void;
@@ -54,6 +59,11 @@ export declare const _internals: {
54
59
  loadPlan: typeof loadPlan;
55
60
  loadPlanJsonOnly: typeof loadPlanJsonOnly;
56
61
  regeneratePlanMarkdown: typeof regeneratePlanMarkdown;
62
+ isGitRepo: typeof isGitRepo;
63
+ isEpicModeActiveForProject: typeof isEpicModeActiveForProject;
64
+ readTaskScopes: typeof readTaskScopes;
65
+ commitTaskCompletion: typeof commitTaskCompletion;
66
+ getWorktreeMergeFailure: typeof getWorktreeMergeFailure;
57
67
  };
58
68
  /** @internal Test seam for snapshot retry helper */
59
69
  export declare const _snapshot_test_exports: {
package/dist/state.d.ts CHANGED
@@ -188,6 +188,11 @@ export interface AgentSessionState {
188
188
  * When set, overrides the plan's execution_profile.max_concurrent_tasks
189
189
  * for delegation-gate guidance. Cleared on session reset. */
190
190
  maxConcurrencyOverride?: number;
191
+ /** Whether Epic Mode (additive overlay above Lean Turbo) is active for
192
+ * this session. Durable mirror lives in `.swarm/epic-state.json`; this
193
+ * in-memory flag matches what `src/turbo/epic/state.ts` persists and is
194
+ * what `hasActiveEpicMode(sessionID)` reads on the hot path. */
195
+ epicModeActive?: boolean;
191
196
  /** Session-scoped override for execution_profile.auto_proceed.
192
197
  * When set, overrides the plan's auto_proceed for this session.
193
198
  * true = auto-advance, false = do not auto-advance. Cleared on session reset. */
@@ -616,6 +621,16 @@ export declare function hasActiveFullAuto(sessionID?: string): boolean;
616
621
  * or if any session has that combination when no sessionID provided.
617
622
  */
618
623
  export declare function hasActiveLeanTurbo(sessionID?: string): boolean;
624
+ /**
625
+ * Check if Epic Mode is active for a specific session or ANY session.
626
+ * Mirrors `hasActiveLeanTurbo` but reads `session.epicModeActive`. The flag
627
+ * is set by `enableEpicMode` (and by `/swarm turbo epic on`) and cleared by
628
+ * `disableEpicMode` (and `/swarm turbo epic off`). The durable mirror is
629
+ * `.swarm/epic-state.json` — see `src/turbo/epic/state.ts`. Epic Mode does
630
+ * NOT require `turboStrategy === 'lean'`; it composes Lean Turbo internally
631
+ * inside `epic_run_phase`.
632
+ */
633
+ export declare function hasActiveEpicMode(sessionID?: string): boolean;
619
634
  /**
620
635
  * Resolves the effective auto_proceed value for a session.
621
636
  * Session override (autoProceedOverride) takes precedence over the plan default.
@@ -675,6 +690,7 @@ export declare const _internals: {
675
690
  hasActiveFullAuto: typeof hasActiveFullAuto;
676
691
  hasActiveTurboMode: typeof hasActiveTurboMode;
677
692
  hasActiveLeanTurbo: typeof hasActiveLeanTurbo;
693
+ hasActiveEpicMode: typeof hasActiveEpicMode;
678
694
  buildRehydrationCache: typeof buildRehydrationCache;
679
695
  applyRehydrationCache: typeof applyRehydrationCache;
680
696
  rehydrateSessionFromDisk: typeof rehydrateSessionFromDisk;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Epic Mode `epic_plan_waves` tool.
3
+ *
4
+ * Wraps `planEpicWaves` from `src/turbo/epic/wave-planner`. Partitions a
5
+ * phase's pending tasks into ordered concurrent waves and returns them in a
6
+ * shape the architect can iterate over for wave-by-wave Task dispatch.
7
+ *
8
+ * This is Epic Mode's replacement for `lean_turbo_plan_lanes`. The lane
9
+ * planner stays in place for non-Epic Lean Turbo callers; Epic flows route
10
+ * through this tool because the wave abstraction expresses branching DAGs
11
+ * (sibling fanout from a shared prefix) correctly, where lanes collapse them.
12
+ */
13
+ import type { ToolDefinition } from '@opencode-ai/plugin/tool';
14
+ import { loadPluginConfigWithMeta as loadPluginConfigWithMeta_import } from '../config';
15
+ import { buildIsUpstreamCommittedWithStatus as buildIsUpstreamCommittedWithStatus_import } from '../turbo/epic/upstream-commits';
16
+ import { type EpicWavePlan } from '../turbo/epic/wave-planner';
17
+ import { readTaskScopes as readTaskScopes_import } from '../turbo/lean/conflicts';
18
+ import type { PlanPhase } from '../turbo/lean/partition-common';
19
+ /** Arguments for the `epic_plan_waves` tool. */
20
+ export interface EpicPlanWavesArgs {
21
+ directory: string;
22
+ phase: number;
23
+ scopes?: Record<string, string[]>;
24
+ }
25
+ /** Result envelope. */
26
+ export interface EpicPlanWavesResult {
27
+ success: boolean;
28
+ /** Set on success — the full wave plan from `planEpicWaves`. */
29
+ plan?: EpicWavePlan;
30
+ /** Set on success — shortcut alias for `plan.waves`. */
31
+ waves?: EpicWavePlan['waves'];
32
+ /** Set on success — shortcut alias for `plan.serializedTasks`. */
33
+ serializedTasks?: EpicWavePlan['serializedTasks'];
34
+ /** Set on success — shortcut alias for `plan.degradedTasks`. */
35
+ degradedTasks?: EpicWavePlan['degradedTasks'];
36
+ /**
37
+ * Set when `reason === 'scopes-missing'` — the task ids that have no
38
+ * declared scope and no `files_touched` fallback. The architect must
39
+ * call `declare_scope` for each of these and re-invoke this tool.
40
+ */
41
+ missingScopes?: string[];
42
+ /** Set on failure — categorical short code (machine-readable). */
43
+ reason?: 'no-plan' | 'no-phase' | 'phase-empty' | 'phase-already-complete' | 'scopes-missing' | 'git-failed' | 'planner-error';
44
+ /** Set on failure — long-form actionable error text. */
45
+ errors?: string[];
46
+ }
47
+ declare function readPlanJson(directory: string): {
48
+ phases: PlanPhase[];
49
+ } | null;
50
+ /**
51
+ * Execute the `epic_plan_waves` tool.
52
+ *
53
+ * Six possible outcomes:
54
+ * 1. `no-plan` — `.swarm/plan.json` missing / unparseable
55
+ * 2. `no-phase` — phase number not in `plan.json`
56
+ * 3. `phase-empty` — phase exists but has zero tasks
57
+ * 4. `phase-already-complete` — every task already completed
58
+ * 5. `scopes-missing` — one or more pending tasks have no declared scope
59
+ * (preflight; identical to `epic_decide_phase` so the architect can't
60
+ * bypass scope discipline by calling planner direct)
61
+ * 6. `git-failed` — git log scan failed (Rule 3 evidence unavailable;
62
+ * we fail closed rather than implicitly satisfying cross-batch deps)
63
+ * 7. success — `plan` and aliased fields populated
64
+ */
65
+ export declare function executeEpicPlanWaves(args: EpicPlanWavesArgs): Promise<EpicPlanWavesResult>;
66
+ /**
67
+ * DI seam — same pattern as `lean-turbo-plan-lanes.ts` (AGENTS.md invariant 7).
68
+ * Tests substitute deterministic doubles via `_internals.*` rather than `mock.module`.
69
+ */
70
+ export declare const _internals: {
71
+ readPlanJson: typeof readPlanJson;
72
+ readTaskScopes: typeof readTaskScopes_import;
73
+ isGitRepo: (cwd: string) => boolean;
74
+ buildIsUpstreamCommittedWithStatus: typeof buildIsUpstreamCommittedWithStatus_import;
75
+ loadPluginConfigWithMeta: typeof loadPluginConfigWithMeta_import;
76
+ };
77
+ /** Tool definition for `epic_plan_waves`. */
78
+ export declare const epic_plan_waves: ToolDefinition;
79
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Epic Mode divergence-record tool (Capability D — capture leg).
3
+ *
4
+ * After the architect marks a task `completed` via `update_task_status`, it
5
+ * calls this tool with `{ directory, taskId, sessionID }`. The tool:
6
+ *
7
+ * 1. Reads the task's DECLARED scope from `.swarm/scopes/scope-{taskId}.json`
8
+ * (the same on-disk record `readScopeFromDisk` consults).
9
+ * 2. Reads the ACTUAL files the coder modified from the session's
10
+ * `modifiedFilesThisCoderTask` — populated by the guardrails write hook
11
+ * and reset by Lean Turbo at task-boundaries, so it captures THIS
12
+ * task's writes only.
13
+ * 3. Appends one record to `.swarm/epic/divergence.jsonl` via
14
+ * `recordTaskDivergence`. The calibration engine reads that file on the
15
+ * next `epic_decide_phase` invocation (the architect-facing decide
16
+ * tool — `epic_run_phase` is the legacy unified path, retained as
17
+ * `executeEpicRunPhase` for composition users only).
18
+ *
19
+ * Best-effort by design — failure to record divergence is logged but never
20
+ * surfaces as a task-blocking error. Worst case: a single observation is
21
+ * missed and the calibration loop sees one fewer data point.
22
+ *
23
+ * Composition contract: this tool does NOT modify `update_task_status` or
24
+ * any maintainer file. The architect is instructed to call it via the
25
+ * `EPIC_MODE_BANNER` system-enhancer injection. If the architect forgets,
26
+ * the only effect is missing calibration signal — Epic Mode keeps working.
27
+ */
28
+ import type { ToolDefinition } from '@opencode-ai/plugin/tool';
29
+ import { loadPlanJsonOnly as loadPlanJsonOnly_import } from '../plan/manager.js';
30
+ import { readScopeFromDisk as readScopeFromDisk_import } from '../scope/scope-persistence.js';
31
+ import { getAgentSession as getAgentSession_import, hasActiveEpicMode as hasActiveEpicMode_import } from '../state.js';
32
+ import { recordTaskDivergence as recordTaskDivergence_import } from '../turbo/epic/divergence-recorder.js';
33
+ export interface EpicRecordDivergenceArgs {
34
+ directory: string;
35
+ taskId: string;
36
+ sessionID: string;
37
+ }
38
+ export interface EpicRecordDivergenceResult {
39
+ success: boolean;
40
+ /**
41
+ * Either:
42
+ * - `'recorded'` — a record was appended to divergence.jsonl.
43
+ * - `'epic-mode-not-active'` — session has not toggled Epic Mode; no-op.
44
+ * - `'no-scope'` — no declared scope on disk for this task (could be a
45
+ * pure verification task that bypassed `declare_scope`). Skipped.
46
+ * - `'no-session'` — no agent session for `sessionID`; skipped.
47
+ * - `'persist-failed'` — write to JSONL failed (logged); skipped.
48
+ */
49
+ reason: string;
50
+ /** When `reason === 'recorded'`, summarises the record without the full file lists. */
51
+ summary?: {
52
+ declaredCount: number;
53
+ actualCount: number;
54
+ undeclaredCount: number;
55
+ unusedCount: number;
56
+ divergenceRatio: number;
57
+ isClean: boolean;
58
+ };
59
+ }
60
+ /**
61
+ * Test-only DI seam (AGENTS.md invariant 7). Mutating this object is
62
+ * file-scoped and trivially restorable via afterEach, avoiding Bun's
63
+ * cross-file `mock.module` leak.
64
+ */
65
+ export declare const _internals: {
66
+ hasActiveEpicMode: typeof hasActiveEpicMode_import;
67
+ getAgentSession: typeof getAgentSession_import;
68
+ readScopeFromDisk: typeof readScopeFromDisk_import;
69
+ loadPlanJsonOnly: typeof loadPlanJsonOnly_import;
70
+ recordTaskDivergence: typeof recordTaskDivergence_import;
71
+ };
72
+ export declare function executeEpicRecordDivergence(args: EpicRecordDivergenceArgs): Promise<EpicRecordDivergenceResult>;
73
+ export declare const epic_record_divergence: ToolDefinition;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Epic Mode run-phase tool (Capability C).
3
+ *
4
+ * The architect invokes this tool — instead of `lean_turbo_run_phase` —
5
+ * when Epic Mode is active. It:
6
+ *
7
+ * 1. Verifies Epic Mode is on for the session (else fails closed).
8
+ * 2. Loads the plan, resolves task scopes the same way the coupling
9
+ * report does, and queries the co-change signal.
10
+ * 3. Runs `decideEpicActivation` over the WHOLE PLAN (per-plan
11
+ * activation per Q1) to get a `promote | demote` verdict.
12
+ * 4. Appends one record to `.swarm/evidence/epic-promotions.jsonl`
13
+ * and updates `.swarm/epic-state.json` with the verdict.
14
+ * 5. If promoted: invokes `LeanTurboRunner` for the given phase by
15
+ * composition (zero edits to `src/turbo/lean/`).
16
+ * 6. If demoted: returns a structured "epic recommends serial"
17
+ * verdict so the caller can fall back to the standard serial
18
+ * flow.
19
+ *
20
+ * Composition contract: this tool is the only architect-facing entry
21
+ * point Capability C adds. It does not modify `lean_turbo_run_phase`,
22
+ * `LeanTurboRunner`, or any Lean Turbo file. Decision happens above
23
+ * Lean Turbo; execution dispatches into Lean Turbo via import only.
24
+ */
25
+ import type { ToolDefinition } from '@opencode-ai/plugin/tool';
26
+ import { loadPluginConfigWithMeta as loadPluginConfigWithMeta_import } from '../config/index.js';
27
+ import { isGitRepo as isGitRepo_import } from '../git/branch.js';
28
+ import { loadPlanJsonOnly as loadPlanJsonOnly_import } from '../plan/manager.js';
29
+ import type { EpicActivationVerdict } from '../turbo/epic/activation.js';
30
+ import { decideEpicActivation as decideEpicActivation_import } from '../turbo/epic/activation.js';
31
+ import { loadCalibrationState as loadCalibrationState_import, saveCalibrationState as saveCalibrationState_import } from '../turbo/epic/calibration.js';
32
+ import { applyCalibration as applyCalibration_import, effectiveActivationThreshold as effectiveActivationThreshold_import, effectiveHotModules as effectiveHotModules_import } from '../turbo/epic/calibration-engine.js';
33
+ import { getCoChangeData as getCoChangeData_import } from '../turbo/epic/cochange-source.js';
34
+ import { readDivergenceHistory as readDivergenceHistory_import } from '../turbo/epic/divergence-recorder.js';
35
+ import { appendPromotionEvidence as appendPromotionEvidence_import } from '../turbo/epic/promotion-evidence.js';
36
+ import { isEpicModeActive as isEpicModeActive_import, recordEpicDecision as recordEpicDecision_import } from '../turbo/epic/state.js';
37
+ import { buildIsUpstreamCommitted as buildIsUpstreamCommitted_import, buildIsUpstreamCommittedWithStatus as buildIsUpstreamCommittedWithStatus_import } from '../turbo/epic/upstream-commits.js';
38
+ import { readTaskScopes as readTaskScopes_import } from '../turbo/lean/conflicts.js';
39
+ import type { LaneResult } from '../turbo/lean/runner.js';
40
+ import { LeanTurboRunner as LeanTurboRunner_import } from '../turbo/lean/runner.js';
41
+ export interface EpicRunPhaseArgs {
42
+ directory: string;
43
+ phase: number;
44
+ sessionID: string;
45
+ }
46
+ export interface EpicRunPhaseResult {
47
+ success: boolean;
48
+ /** The verdict for this run, persisted to evidence. */
49
+ verdict?: EpicActivationVerdict;
50
+ /** Set when the verdict was `promote` and Lean Turbo ran. */
51
+ lanes?: LaneResult[];
52
+ degradedTasks?: string[];
53
+ serializedTasks?: string[];
54
+ /**
55
+ * Either:
56
+ * - `'demoted'` — epic chose serial; the caller should fall back.
57
+ * - `'promoted'` — epic chose parallel and Lean Turbo ran.
58
+ * - `'epic-mode-not-active'` — the session has not toggled Epic Mode.
59
+ * - `'no-plan'` — `.swarm/plan.json` is missing.
60
+ * - `'no-phase'` — the requested phase number isn't present in the
61
+ * plan. Phase 12 (B11): without this, an unknown phase silently
62
+ * produced `currentPhaseTasks = []` and vacuously-passed the
63
+ * activation gate — promoting a phase that doesn't exist.
64
+ * - `'phase-already-complete'` — every task in the requested phase
65
+ * is already `status: 'completed'`. Phase 15 (B35): without this,
66
+ * re-running an already-completed phase silently produced a
67
+ * vacuous-pass `promote` verdict; the architect then called the
68
+ * wave planner and got an empty plan with no diagnostic.
69
+ * - `'phase-empty'` — the requested phase exists but its `tasks`
70
+ * array is empty (architect created a phase header but never
71
+ * populated it, or a council edit removed every task). Phase 17
72
+ * (E.1): the Phase 15 B35 guard only fired when at least one
73
+ * completed task existed; an empty `tasks: []` slipped through to
74
+ * the same vacuous-pass `promote` B35 was supposed to prevent.
75
+ * - `'lean-runner-error'` — Lean Turbo threw during promoted execution.
76
+ * - `'scopes-missing'` — one or more pending tasks in the phase have
77
+ * neither a declared scope file on disk nor `files_touched` in
78
+ * plan.json. Lean Turbo's lane planner needs scope data to compute
79
+ * parallel lanes; without it the dispatch returns empty lanes and
80
+ * the parallelization promise is silently broken. The architect
81
+ * must call `declare_scope` for each missing task and then
82
+ * re-invoke `epic_decide_phase`.
83
+ */
84
+ reason: string;
85
+ /** Set when `reason === 'lean-runner-error'`. */
86
+ errors?: string[];
87
+ /** Set when `reason === 'scopes-missing'` — the task ids with no scope. */
88
+ missingScopes?: string[];
89
+ /** Set when `reason === 'scopes-missing'` — actionable message for the architect. */
90
+ message?: string;
91
+ }
92
+ /**
93
+ * Test-only DI seam. Mutating this object is file-scoped and trivially
94
+ * restorable via afterEach, avoiding Bun's cross-file `mock.module`
95
+ * leak (AGENTS.md invariant 7).
96
+ */
97
+ export declare const _internals: {
98
+ loadPluginConfigWithMeta: typeof loadPluginConfigWithMeta_import;
99
+ loadPlanJsonOnly: typeof loadPlanJsonOnly_import;
100
+ getCoChangeData: typeof getCoChangeData_import;
101
+ decideEpicActivation: typeof decideEpicActivation_import;
102
+ isGitRepo: typeof isGitRepo_import;
103
+ appendPromotionEvidence: typeof appendPromotionEvidence_import;
104
+ recordEpicDecision: typeof recordEpicDecision_import;
105
+ isEpicModeActive: typeof isEpicModeActive_import;
106
+ readTaskScopes: typeof readTaskScopes_import;
107
+ loadCalibrationState: typeof loadCalibrationState_import;
108
+ saveCalibrationState: typeof saveCalibrationState_import;
109
+ applyCalibration: typeof applyCalibration_import;
110
+ effectiveActivationThreshold: typeof effectiveActivationThreshold_import;
111
+ effectiveHotModules: typeof effectiveHotModules_import;
112
+ readDivergenceHistory: typeof readDivergenceHistory_import;
113
+ LeanTurboRunner: typeof LeanTurboRunner_import;
114
+ buildIsUpstreamCommitted: typeof buildIsUpstreamCommitted_import;
115
+ buildIsUpstreamCommittedWithStatus: typeof buildIsUpstreamCommittedWithStatus_import;
116
+ };
117
+ /**
118
+ * Decide-only path: runs stages 1-9 of the phase flow (preflight + calibration
119
+ * + co-change + decision + evidence write + session state mirror) and returns
120
+ * the verdict WITHOUT dispatching Lean Turbo.
121
+ *
122
+ * This is the shared helper between:
123
+ * - `epic_run_phase`: legacy unified tool (decide + dispatch in one call) —
124
+ * calls this then continues with dispatch when verdict is promote.
125
+ * - `epic_decide_phase`: transparent flow (decide only — architect then
126
+ * calls `epic_plan_waves` and dispatches each wave via Task for visibility).
127
+ *
128
+ * Returns the same EpicRunPhaseResult shape with:
129
+ * - reason: 'decided' → verdict is promote, caller may dispatch.
130
+ * - reason: 'demoted' → verdict is demote, caller falls back to serial.
131
+ *
132
+ * Error / non-decision reasons (all set success: false):
133
+ * - 'epic-mode-not-active' — the session has not toggled Epic Mode.
134
+ * - 'no-plan' — `.swarm/plan.json` is missing.
135
+ * - 'no-phase' (Phase 12 B11) — the requested phase number isn't in the plan.
136
+ * - 'phase-empty' (Phase 17 E.1) — phase exists but has zero tasks.
137
+ * - 'phase-already-complete' (Phase 15 B35) — every task already completed.
138
+ * - 'scopes-missing' — one or more pending tasks lack declared scope.
139
+ * - 'epic-state-unreadable' — `.swarm/epic-state.json` is corrupt.
140
+ */
141
+ export declare function executeEpicDecidePhase(args: EpicRunPhaseArgs): Promise<EpicRunPhaseResult>;
142
+ /**
143
+ * Full unified path: decide + dispatch in one call (legacy behavior).
144
+ *
145
+ * For transparent CLI-visible dispatch, prefer `epic_decide_phase` + lane
146
+ * dispatch via the architect's Task tool — see EPIC_MODE_BANNER. This unified
147
+ * path remains for back-compat and for callers that don't need visibility
148
+ * into the parallel coder agents.
149
+ */
150
+ export declare function executeEpicRunPhase(args: EpicRunPhaseArgs): Promise<EpicRunPhaseResult>;
151
+ /**
152
+ * NOTE: `epic_run_phase` is intentionally NOT exposed as a tool to the
153
+ * architect. The transparent decide-then-dispatch wave flow (`epic_decide_phase`
154
+ * → `epic_plan_waves` → Task dispatch per wave) is the ONLY supported flow,
155
+ * because it gives the user real-time visibility into each concurrent coder
156
+ * agent. The legacy unified-path function `executeEpicRunPhase` remains
157
+ * exported for tests and any composition users, but no ToolDefinition
158
+ * wraps it — so the architect cannot call it and accidentally fall back
159
+ * to the opaque path. This is a deliberate product decision: one flow,
160
+ * unambiguous, always-visible.
161
+ */
162
+ /**
163
+ * Transparent decide-only tool. Returns the verdict (promote/demote/error)
164
+ * without dispatching coders. The architect should:
165
+ * 1. Call this after declaring scopes for all pending tasks.
166
+ * 2. Surface the verdict to the user.
167
+ * 3. If verdict is `promote`, call `epic_plan_waves` to get the wave plan,
168
+ * then for each wave dispatch one `Task` per `taskId` in that wave —
169
+ * ALL in one assistant message so the wave runs concurrently. Wait for
170
+ * the wave to complete, then advance. Each Task is a visible subagent
171
+ * the user can click into for live progress.
172
+ * 4. After each task completes (via `update_task_status`), call
173
+ * `epic_record_divergence` to feed the calibration loop.
174
+ *
175
+ * This is the CLI-visibility flow. The legacy `epic_run_phase` bundles
176
+ * decide + dispatch into one opaque tool call where the user can't see
177
+ * the concurrent coder agents.
178
+ */
179
+ export declare const epic_decide_phase: ToolDefinition;
@@ -79,6 +79,9 @@ export type { ClassifiedFailure, FailureClassification, FailureCluster, } from '
79
79
  export { classifyAndCluster, classifyFailure, clusterFailures, } from '../test-impact/failure-classifier.js';
80
80
  export type { FlakyTestEntry } from '../test-impact/flaky-detector.js';
81
81
  export { computeFlakyScore, detectFlakyTests, isTestQuarantined, } from '../test-impact/flaky-detector.js';
82
+ export { epic_plan_waves } from './epic-plan-waves';
83
+ export { epic_record_divergence } from './epic-record-divergence';
84
+ export { epic_decide_phase } from './epic-run-phase';
82
85
  export { generate_mutants } from './generate-mutants';
83
86
  export { lean_turbo_acquire_locks } from './lean-turbo-acquire-locks';
84
87
  export { lean_turbo_plan_lanes } from './lean-turbo-plan-lanes';
@@ -119,4 +119,7 @@ export declare const TOOL_MANIFEST: {
119
119
  external_skill_reject: () => ToolDefinition;
120
120
  external_skill_delete: () => ToolDefinition;
121
121
  external_skill_revoke: () => ToolDefinition;
122
+ epic_decide_phase: () => ToolDefinition;
123
+ epic_plan_waves: () => ToolDefinition;
124
+ epic_record_divergence: () => ToolDefinition;
122
125
  };
@@ -26,6 +26,8 @@ export declare const _internals: {
26
26
  extractPythonSymbols: typeof extractPythonSymbols;
27
27
  parseFileImports: typeof parseFileImports;
28
28
  extractFileOntology: typeof extractFileOntology;
29
+ stripComments: typeof stripComments;
30
+ computeUsedSymbols: typeof computeUsedSymbols;
29
31
  };
30
32
  /**
31
33
  * Add or update a node in the graph.
@@ -66,6 +68,16 @@ export declare function resolveModuleSpecifier(workspaceRoot: string, sourceFile
66
68
  /**
67
69
  * A parsed import with its specifier and type.
68
70
  */
71
+ /**
72
+ * A single imported binding: the symbol's *exported* name in the target file
73
+ * and the *local* name it is bound to in the importing file (differs when an
74
+ * `as` alias or default import is used). Used to attribute call-site usage back
75
+ * to the correct exported symbol.
76
+ */
77
+ interface ImportBinding {
78
+ imported: string;
79
+ local: string;
80
+ }
69
81
  interface ParsedImport {
70
82
  /** The module specifier (e.g., './foo', 'lodash') */
71
83
  specifier: string;
@@ -73,8 +85,42 @@ interface ParsedImport {
73
85
  importType: 'default' | 'named' | 'namespace' | 'require' | 'sideeffect';
74
86
  /** Named imported symbols when statically detectable */
75
87
  importedSymbols: string[];
88
+ /** Alias-aware imported→local bindings for usage attribution */
89
+ bindings: ImportBinding[];
90
+ /** True for `export { x } from '...'` re-exports (symbols are re-exposed). */
91
+ reExport: boolean;
76
92
  }
93
+ /**
94
+ * Strip line (`//…`) and block (`/* … *\/`) comments from JS/TS source while
95
+ * preserving string, template-literal, and regex-literal contents (DD-C010).
96
+ * Import specifiers live inside string literals, so strings must be kept
97
+ * intact; only comment spans are removed. This is a bounded single-pass scanner
98
+ * — not a full parser (AST parsing in the repo-graph init path would violate
99
+ * AGENTS.md invariant 1) — and it eliminates the most common source of false
100
+ * import edges: import-like text inside comments (`// import x from "y"`).
101
+ *
102
+ * It is string-aware (a `//` inside `"http://…"` is not a comment) and
103
+ * regex-aware (a regex literal such as `/[/*]/` must not be mistaken for the
104
+ * start of a block comment, which would otherwise run to EOF and delete real
105
+ * imports). Regex-vs-division is disambiguated by the previous significant
106
+ * character (REGEX_ALLOWED_AFTER).
107
+ */
108
+ declare function stripComments(content: string): string;
77
109
  declare function parseFileImports(rawContent: string): ParsedImport[];
110
+ /**
111
+ * Conservatively determine which imported bindings are actually referenced in
112
+ * the importing file's body.
113
+ *
114
+ * Heuristic: in a well-formed import statement, each local binding name appears
115
+ * exactly once. Counting occurrences of the local name across the
116
+ * comment-stripped file content, a count > 1 means at least one body reference.
117
+ * Strings are intentionally *not* stripped, so the bias is toward "used" — a
118
+ * conservative direction that avoids false dead-export positives. Bindings whose
119
+ * local name cannot be safely word-boundary matched are assumed used.
120
+ *
121
+ * @returns the *exported* names (binding.imported) judged to be used.
122
+ */
123
+ declare function computeUsedSymbols(strippedContent: string, bindings: readonly ImportBinding[]): string[];
78
124
  /**
79
125
  * Result of scanning a single file for graph updates.
80
126
  */
@@ -1,10 +1,35 @@
1
- import type { BlastRadiusResult, FileOntology, FileReference, GraphNode, LocalizationBlock, PackageBoundarySummary, RepoGraph, SymbolReference } from './types';
1
+ import type { BlastRadiusResult, CallerReference, DeadExportsResult, FileOntology, FileReference, GraphNode, LocalizationBlock, PackageBoundarySummary, RepoGraph, SymbolReference } from './types';
2
2
  export declare function getGraphNode(graph: RepoGraph, input: string): GraphNode | undefined;
3
3
  export declare function resetQueryCache(): void;
4
4
  export declare function isGraphFresh(graph: RepoGraph | null, maxAgeMs?: number): boolean;
5
5
  export declare function getImporters(graph: RepoGraph, filePath: string): FileReference[];
6
6
  export declare function getDependencies(graph: RepoGraph, filePath: string): FileReference[];
7
7
  export declare function getSymbolConsumers(graph: RepoGraph, filePath: string, symbolName: string): SymbolReference[];
8
+ /**
9
+ * Files that actually *reference* an exported symbol of `filePath` — call-site
10
+ * granularity, not just "imports the file". On schema >= 1.1.0 graphs this uses
11
+ * per-edge `usedSymbols`; on older graphs (or namespace imports) it falls back
12
+ * to import-level matching, flagged via `resolution: 'imported'`.
13
+ */
14
+ export declare function getCallers(graph: RepoGraph, filePath: string, symbolName: string): CallerReference[];
15
+ export interface DeadExportsOptions {
16
+ /** Max candidates returned (default 100). */
17
+ maxCandidates?: number;
18
+ }
19
+ /**
20
+ * Conservatively detect exported symbols with no detected in-repo reference.
21
+ *
22
+ * Scoping for precision (advisory "candidate" output, never a delete directive):
23
+ * - Requires schema >= 1.1.0 (per-edge usedSymbols); otherwise returns
24
+ * schemaSupported=false so the caller can prompt a rebuild.
25
+ * - Only considers files imported by >= 1 other file — a file with no
26
+ * importers is a likely public-API entry / CLI / test, not dead code.
27
+ * - Skips files imported anywhere via namespace/side-effect/require/dynamic
28
+ * imports, where per-symbol usage is unresolvable.
29
+ * - Excludes framework-invoked roles (routes, CLIs, tests, agents, hooks,
30
+ * middleware) and the synthetic 'default' export.
31
+ */
32
+ export declare function getDeadExports(graph: RepoGraph, options?: DeadExportsOptions): DeadExportsResult;
8
33
  export declare function getBlastRadius(graph: RepoGraph, filePaths: string[], maxDepth?: number): BlastRadiusResult;
9
34
  export declare function getKeyFiles(graph: RepoGraph, topN?: number): GraphNode[];
10
35
  export declare function getFileOntology(graph: RepoGraph, filePath: string): FileOntology | null;
@@ -7,7 +7,23 @@
7
7
  * Every other submodule imports from here.
8
8
  */
9
9
  export declare const REPO_GRAPH_FILENAME = "repo-graph.json";
10
- export declare const GRAPH_SCHEMA_VERSION = "1.0.0";
10
+ /**
11
+ * Graph schema version.
12
+ *
13
+ * 1.1.0 added per-edge `usedSymbols` (imported symbols actually referenced in
14
+ * the importing file) and per-node `exportLines`, enabling the `callers` and
15
+ * `dead_exports` queries. Both fields are optional, so graphs written by older
16
+ * versions (1.0.0) still load — but `dead_exports` requires >= 1.1.0 data and
17
+ * self-gates via {@link isSchemaVersionAtLeast} rather than relying on the
18
+ * loader (which only checks that a version string is present, not its value).
19
+ */
20
+ export declare const GRAPH_SCHEMA_VERSION = "1.1.0";
21
+ /**
22
+ * Compare dotted numeric version strings (e.g. '1.1.0' >= '1.1.0').
23
+ * Missing/non-numeric segments are treated as 0. Returns true when `version`
24
+ * is greater than or equal to `minimum`.
25
+ */
26
+ export declare function isSchemaVersionAtLeast(version: string | undefined, minimum: string): boolean;
11
27
  export declare const FILE_ROLE_VALUES: readonly ["api_route", "middleware", "service_module", "data_module", "swarm_tool", "agent", "hook", "config", "schema", "test_file", "cli_command", "documentation", "source_module"];
12
28
  export type FileRole = (typeof FILE_ROLE_VALUES)[number];
13
29
  export declare const ROUTE_METHOD_VALUES: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "ALL"];
@@ -73,6 +89,13 @@ export interface GraphNode {
73
89
  moduleName: string;
74
90
  /** Exported symbols from this file */
75
91
  exports: string[];
92
+ /**
93
+ * Definition line for each exported symbol, keyed by symbol name (1-based).
94
+ * Optional and best-effort: present on graphs built at schema >= 1.1.0,
95
+ * absent for symbols whose line could not be determined. Used to point
96
+ * `dead_exports` candidates at a location.
97
+ */
98
+ exportLines?: Record<string, number>;
76
99
  /** Imported module specifiers */
77
100
  imports: string[];
78
101
  /** Language/extension of the file */
@@ -98,6 +121,14 @@ export interface GraphEdge {
98
121
  importType: ImportType;
99
122
  /** Named symbols imported from the target, when statically detectable */
100
123
  importedSymbols?: string[];
124
+ /**
125
+ * The subset of the target's exported symbols (by their *exported* name)
126
+ * that are actually referenced in the source file's body — not merely
127
+ * imported. Computed at build time via a conservative, alias-aware textual
128
+ * scan (schema >= 1.1.0). Absent on namespace/side-effect/require/dynamic
129
+ * imports, where individual symbol usage is not statically resolvable.
130
+ */
131
+ usedSymbols?: string[];
101
132
  }
102
133
  export interface FileReference {
103
134
  file: string;
@@ -109,6 +140,47 @@ export interface SymbolReference {
109
140
  line?: number;
110
141
  importedAs: string;
111
142
  }
143
+ /**
144
+ * A file that references a specific exported symbol of a target file.
145
+ * `resolution` records how confidently the usage was attributed:
146
+ * - 'used' → the symbol was found referenced in the source body
147
+ * - 'imported' → fallback for graphs predating usedSymbols (schema < 1.1.0);
148
+ * the symbol is imported but body usage was not analyzed
149
+ */
150
+ export interface CallerReference {
151
+ file: string;
152
+ resolution: 'used' | 'imported';
153
+ }
154
+ /**
155
+ * An exported symbol with no detected in-repo reference. Advisory only —
156
+ * regex-based analysis cannot see dynamic dispatch, string-keyed access, or
157
+ * usage through namespace/barrel re-exports, so this is a *candidate* for
158
+ * review, never a directive to delete.
159
+ */
160
+ export interface DeadExportCandidate {
161
+ /** Module name (workspace-relative) of the file that owns the export */
162
+ file: string;
163
+ /** The exported symbol name */
164
+ symbol: string;
165
+ /** Definition line, when known (from exportLines) */
166
+ line?: number;
167
+ /** How many other in-repo files import this file at all */
168
+ importerCount: number;
169
+ }
170
+ export interface DeadExportsResult {
171
+ /** False when the graph predates schema 1.1.0 (rebuild required). */
172
+ schemaSupported: boolean;
173
+ /** Files whose exports were analyzed (imported by >= 1 other file). */
174
+ analyzedFiles: number;
175
+ /**
176
+ * Files skipped because at least one importer used a namespace/side-effect/
177
+ * require/dynamic import, making per-symbol usage unresolvable.
178
+ */
179
+ skippedUnresolvable: number;
180
+ candidates: DeadExportCandidate[];
181
+ /** Human-readable note describing scope and limitations of the result. */
182
+ note: string;
183
+ }
112
184
  export interface BlastRadiusResult {
113
185
  target: string[];
114
186
  directDependents: string[];