Skip to content

Add multi-epic orchestration support#391

Merged
subsy merged 5 commits into
mainfrom
t3code/8d8163e1
May 13, 2026
Merged

Add multi-epic orchestration support#391
subsy merged 5 commits into
mainfrom
t3code/8d8163e1

Conversation

@subsy

@subsy subsy commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds repeatable --epic and comma-separated --epics support so one Ralph session can run across multiple selected epics while preserving single-epic compatibility.
  • Introduces execution scopes and MultiScopeTrackerPlugin to combine hierarchy tracker tasks, annotate tasks with their scope, dedupe duplicate task IDs, preserve cross-epic dependencies, and exclude tasks blocked by unresolved external dependencies.
  • Keeps parallel execution as one global scheduler, one session branch, one merge queue, and task-scoped worktrees with scoped branch names.
  • Adds multi-select epic picking, TUI scope filtering with g/G, per-scope labels/counts, and scope-aware parallel/merge/worker displays.
  • Persists and resumes selected epic sets, includes scopes in remote orchestration state, and documents the public CLI/TUI/remote/tracker behavior across the website docs and README.
  • Adds focused tests for config parsing, multi-scope tracker behavior, engine tracker injection, parallel scope state, session persistence, remote scope counts, and coverage-sensitive runtime paths.

Testing

  • bun run typecheck && bun run build
  • bun test src/commands/run.test.ts
  • bun test tests/tui
  • bun test src/config/index.test.ts src/engine/ src/parallel/ src/session/ src/remote/server.test.ts src/plugins/trackers/multi-scope.test.ts tests/remote/remote.test.ts --coverage --coverage-reporter=lcov
  • bun test src/plugins/trackers/builtin/beads-rust/index.test.ts
  • bun test src/plugins/trackers/builtin/beads-bv/index.test.ts
  • git diff --check

Note: bun test src/plugins/trackers as a single process still hits the existing Bun mock.module() isolation issue between beads-rust and beads-bv tests. The CI workflow already runs those files in isolated batches, and the isolated runs above pass.

Summary by CodeRabbit

  • New Features

    • Multi-epic parallel execution: repeatable --epic and comma-separated --epics, multi-select epic picker, persisted epicIds/executionScopes, scope-aware TUI (g/G), per-scope counts/summaries, scope labels in task/worker/merge views, deduplication and cross-epic dependency handling, session-aware branch names
  • Documentation

    • CLI, TUI, parallel, tracker and resume docs updated with multi-epic examples and behavior notes
  • Tests

    • Expanded coverage for parsing, tracker wrapper, remote helpers, session persistence, and parallel state reporting

Review Change Stack

- New --epic (repeatable) and --epics (comma-separated) CLI flags to run one session across multiple epics
- MultiScopeTrackerPlugin combines tasks from multiple epics, annotating each with its ExecutionScope
- ScopeFilterBar TUI component lets users cycle/reset scope filter with g/G keybindings
- Worktree branch names include session and scope slugs for multi-epic parallel runs
- Scope-level task summaries added to parallel run output
- epicIds and executionScopes persisted in session state and resumed correctly
@vercel

vercel Bot commented May 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ralph-tui Ignored Ignored Preview May 13, 2026 6:03pm

Request Review

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 845f30ea-a988-4274-b962-7be59a4a9df5

📥 Commits

Reviewing files that changed from the base of the PR and between 8086e23 and c8f4fd5.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • codecov.yml
  • src/commands/run.test.ts
  • src/commands/run.tsx

📝 Walkthrough

Walkthrough

This PR adds multi-epic parallel execution: execution-scope types, a MultiScopeTrackerPlugin to aggregate/deduplicate tasks across scopes, CLI support for repeated --epic and --epics, TUI multi-select and scope-filter UI with per-scope counts and scope-prefixed task displays, session persistence of epicIds/executionScopes, engine/parallel/worktree integration, remote orchestration support, tests, and documentation updates.

Changes

Multi-epic execution framework

Layer / File(s) Summary
Execution scope types and contracts
src/plugins/trackers/types.ts, src/config/types.ts, src/session/types.ts, src/session/persistence.ts, src/tui/types.ts
Adds ExecutionScope and ScopedTrackerTask types and updates config/session/TUI interfaces to carry epicIds and executionScopes.
MultiScopeTrackerPlugin aggregation
src/plugins/trackers/multi-scope.ts, src/plugins/trackers/index.ts, src/plugins/trackers/multi-scope.test.ts
Introduces MultiScopeTrackerPlugin which aggregates tasks across selected scopes, deduplicates IDs, annotates tasks with executionScope, applies external-dependency blocking semantics, and delegates lifecycle operations; tests validate aggregation, ordering, external blocking, and delegation.
Config builder and CLI parsing
src/config/index.ts, src/commands/run.tsx, src/commands/run.test.ts
buildConfig() derives epicIds and primary epic and wires them into tracker options; CLI accepts repeated --epic and comma-separated --epics accumulating/deduplicating IDs and preserving epicId selection semantics.
Run orchestration wiring
src/commands/run.tsx
Run command resolves execution scopes, conditionally wraps the tracker with MultiScopeTrackerPlugin for multi-epic runs, threads executionScopes into TUI and ParallelExecutor, persists epicIds/executionScopes in sessions and registry, and includes per-scope summaries in parallel run output.
Resume flow and session persistence
src/commands/resume.tsx, src/session/persistence.ts, src/session/index.ts, src/session/registry.ts
Resume hydrates epicIds/executionScopes from persisted session, resolves scopes for resume, chooses multi-scope tracker wrapping when needed, and session snapshots/registry entries persist the new fields; tests validate round-trip.
Engine & ParallelExecutor integration
src/engine/index.ts, src/parallel/types.ts, src/parallel/events.ts, src/parallel/index.ts
Engine.initialize accepts an optional tracker override via EngineInitializeOptions; ParallelExecutor accepts scopes in config, emits parallel:started with scopes, exposes scopes and workerResults in state, and passes scopeId to worktree acquisition.
WorktreeManager branch naming
src/parallel/worktree-manager.ts, src/parallel/worktree-manager.test.ts
WorktreeManager.acquire accepts optional sessionId/scopeId branchParts; branch segments are sanitized and branch names use hierarchical ralph-parallel/<session>/<scope>/<task> form; tests verify branch construction.
TUI scope filter and UI updates
src/tui/components/RunApp.tsx, src/tui/components/ScopeFilterBar.tsx, src/tui/components/LeftPanel.tsx, src/tui/components/ParallelProgressView.tsx, src/tui/components/MergeProgressView.tsx, src/tui/components/WorkerDetailView.tsx, src/tui/components/EpicSelectionApp.tsx, src/tui/components/EpicSelectionView.tsx, src/tui/theme.ts
RunApp accepts executionScopes, computes active scopes and per-scope counts, renders ScopeFilterBar when >1 scope, supports g/G to cycle/reset scope filter, EpicSelection UI supports multi-select, and task/progress/detail views display scope prefixes; theme help includes g/G.
Remote orchestration
src/remote/client.ts, src/remote/server.ts, src/remote/types.ts, tests/remote/remote.test.ts
Remote client startOrchestration accepts epicIds; server resolves requested epic IDs into ExecutionScope[], conditionally wraps tracker with MultiScopeTrackerPlugin, computes per-scope counts from executor state, and returns scopes and scopeCounts in orchestrate:get_state. Tests validate helpers and payloads.
Tests and documentation
src/commands/run.test.ts, src/plugins/trackers/multi-scope.test.ts, src/remote/server.test.ts, src/session/persistence.test.ts, src/engine/index.test.ts, src/parallel/parallel-executor.test.ts, website docs, README, tracker READMEs
Adds/updates tests for CLI parsing, config normalization, engine init with injected tracker, ParallelExecutor state/scopes, multi-scope tracker behavior (dedupe, ordering, external-block), remote helpers, session persistence, worktree naming; docs updated with repeated --epic / --epics examples and TUI scope-filter documentation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.20181% with 127 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.77%. Comparing base (b58a99c) to head (c8f4fd5).

Files with missing lines Patch % Lines
src/plugins/trackers/multi-scope.ts 66.93% 83 Missing ⚠️
src/remote/server.ts 77.61% 30 Missing ⚠️
src/parallel/index.ts 22.22% 7 Missing ⚠️
src/session/persistence.ts 50.00% 4 Missing ⚠️
src/config/index.ts 91.66% 1 Missing ⚠️
src/engine/index.ts 85.71% 1 Missing ⚠️
src/remote/client.ts 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #391      +/-   ##
==========================================
+ Coverage   48.37%   52.77%   +4.39%     
==========================================
  Files         117      116       -1     
  Lines       38879    35057    -3822     
==========================================
- Hits        18807    18500     -307     
+ Misses      20072    16557    -3515     
Files with missing lines Coverage Δ
src/config/types.ts 100.00% <ø> (ø)
src/parallel/worktree-manager.ts 57.39% <100.00%> (+1.17%) ⬆️
src/remote/types.ts 100.00% <ø> (ø)
src/session/index.ts 34.96% <100.00%> (+26.33%) ⬆️
src/session/registry.ts 72.44% <ø> (ø)
src/tui/theme.ts 89.41% <100.00%> (+0.07%) ⬆️
src/config/index.ts 61.22% <91.66%> (+0.83%) ⬆️
src/engine/index.ts 59.27% <85.71%> (+0.03%) ⬆️
src/remote/client.ts 31.78% <0.00%> (+3.77%) ⬆️
src/session/persistence.ts 36.85% <50.00%> (+26.82%) ⬆️
... and 3 more

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/run.tsx (1)

1617-1654: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the SIGINT handler on epic-picker teardown.

Line 1644 adds a process-level listener, but cleanup never unregisters it. Reopening this flow can stack stale handlers.

Suggested fix
 async function showEpicSelectionTui(
   tracker: TrackerPlugin
 ): Promise<TrackerTask[] | undefined> {
   return new Promise(async (resolve) => {
@@
-    const cleanup = () => {
+    const cleanup = () => {
+      process.removeListener('SIGINT', handleSigint);
       renderer.destroy();
     };
@@
-    process.on('SIGINT', handleSigint);
+    process.on('SIGINT', handleSigint);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/run.tsx` around lines 1617 - 1654, The SIGINT handler registered
by process.on('SIGINT', handleSigint) in the Promise should be removed during
teardown to avoid accumulating handlers; update the cleanup function in the same
block (the closure that currently calls renderer.destroy()) to also call
process.off('SIGINT', handleSigint) (or process.removeListener('SIGINT',
handleSigint)) so the handler is unregistered when EpicSelectionApp is torn
down, ensuring handleSigint is the same function reference used when
registering.
🧹 Nitpick comments (3)
src/commands/resume.tsx (2)

199-203: ⚡ Quick win

formatSessionEntry doesn't display multi-epic sessions.

This unchanged code path only inspects entry.epicId. For sessions registered with epicIds (and no epicId populated), trackerInfo will silently fall through to prdPath/trackerPlugin, hiding epic context from --list output. Worth handling here since the new epicIds field on SessionRegistryEntry was added in this PR.

✏️ Suggested fix
-  const trackerInfo = entry.epicId ? `epic:${entry.epicId}` :
-                      entry.prdPath ? `prd:${entry.prdPath}` : entry.trackerPlugin;
+  const epicLabel =
+    entry.epicIds && entry.epicIds.length > 0
+      ? `epics:${entry.epicIds.join(',')}`
+      : entry.epicId
+        ? `epic:${entry.epicId}`
+        : undefined;
+  const trackerInfo = epicLabel ?? (entry.prdPath ? `prd:${entry.prdPath}` : entry.trackerPlugin);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/resume.tsx` around lines 199 - 203, The function
formatSessionEntry currently sets trackerInfo by checking entry.epicId first and
ignores entry.epicIds, causing multi-epic sessions to be displayed incorrectly;
update the trackerInfo logic in formatSessionEntry to prefer entry.epicIds when
present (e.g., join the epic IDs into a readable string like "epic:id1,id2" or
similar) and fall back to entry.epicId, then to entry.prdPath and
entry.trackerPlugin, ensuring the epicIds representation is included in the
returned string (refer to trackerInfo, entry.epicIds, entry.epicId, and
formatSessionEntry to locate and change the code).

727-740: ⚡ Quick win

Mismatch warning omits multi-epic context.

When a multi-epic resume yields no tasks, this branch never surfaces the configured epic IDs (only epicId and prdPath are checked), so users debugging a stale multi-epic session get a generic message. Consider also reporting resumedState.trackerState.epicIds when present.

✏️ Suggested fix
     if (resumedState.trackerState.epicId) {
       console.warn(`  - The epic ID "${resumedState.trackerState.epicId}" no longer exists`);
     }
+    if (resumedState.trackerState.epicIds && resumedState.trackerState.epicIds.length > 0) {
+      console.warn(`  - One or more epic IDs no longer exist: ${resumedState.trackerState.epicIds.join(', ')}`);
+    }
     if (resumedState.trackerState.prdPath) {
       console.warn(`  - The PRD file "${resumedState.trackerState.prdPath}" is missing or empty`);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/resume.tsx` around lines 727 - 740, The warning branch that runs
when shouldWarnAboutTrackerMismatch(engineState.totalTasks, sessionTotalTasks)
is triggered only checks resumedState.trackerState.epicId and prdPath and omits
resumedState.trackerState.epicIds; update the message to include epicIds when
present so multi-epic resumes show the configured epic list. Locate the block
that logs the warnings (the if using shouldWarnAboutTrackerMismatch) and add a
conditional that checks resumedState.trackerState.epicIds (and formats them as a
comma-separated list or similar) and logs a user-friendly line like “Configured
epic IDs: …” alongside the existing epicId/prdPath lines. Ensure you reference
resumedState.trackerState.epicIds and keep the new log only when epicIds is
non-empty to avoid noise.
src/plugins/trackers/multi-scope.ts (1)

96-118: ⚡ Quick win

Fetch scoped task lists concurrently to reduce multi-scope latency.

This loop serializes one tracker request per scope. Parallelizing keeps scope order while avoiding N round-trips in sequence.

Proposed refactor
-    for (const scope of this.scopes) {
-      const scopedTasks = await this.delegate.getTasks(mergeFilterForScope(filter, scope));
+    const scopedResults = await Promise.all(
+      this.scopes.map(async (scope) => ({
+        scope,
+        scopedTasks: await this.delegate.getTasks(mergeFilterForScope(filter, scope)),
+      }))
+    );
+
+    for (const { scope, scopedTasks } of scopedResults) {
       for (const task of scopedTasks) {
         if (seenTaskIds.has(task.id)) {
           if (!this.warnedDuplicateIds.has(task.id)) {
             this.warnedDuplicateIds.add(task.id);
             console.warn(
               `Warning: task ${task.id} appears in multiple selected epics; using first occurrence.`
             );
           }
           continue;
         }
         seenTaskIds.add(task.id);
         combined.push({
           ...task,
           executionScope: scope,
           metadata: {
             ...task.metadata,
             executionScope: scope,
           },
         });
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/plugins/trackers/multi-scope.ts` around lines 96 - 118, The current loop
over this.scopes calls this.delegate.getTasks sequentially causing N
round-trips; change it to fetch all scoped task lists concurrently by mapping
this.scopes to an array of promises using
this.delegate.getTasks(mergeFilterForScope(filter, scope)), awaiting Promise.all
to get results in the same scope order, then iterate the resolved arrays in
original scope order to perform the existing dedupe/warn logic using seenTaskIds
and this.warnedDuplicateIds and push into combined (preserving executionScope
and metadata). Ensure error handling/propagation remains consistent when
awaiting the parallel requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/run.tsx`:
- Around line 523-533: Scope failure totals only account for merge-phase entries
from executorState.mergeQueue, so pre-merge worker-phase failures are missed;
update the logic that builds scopes/completed/failed (the loop referencing
getTaskExecutionScope and operation.workerResult.task) to also iterate the
worker-phase results (e.g., executorState.workerQueue or
executorState.workerResults) and increment failed for the corresponding scope.id
when a workerResult/status indicates failure, using the same scope lookup
(getTaskExecutionScope) and the same failed map update code; apply the same fix
to the analogous block around lines 535-542.

In `@src/plugins/trackers/multi-scope.ts`:
- Around line 84-86: The initialize method currently only checks readiness via
this.delegate.isReady() and never forwards the provided _config to the wrapped
tracker; update initialize to await and propagate the config to the delegate by
calling await this.delegate.initialize(_config) before (or in combination with)
checking/setting this.ready (using this.delegate.isReady() or the delegate's
return) so the underlying tracker is properly initialized when the wrapper is
used.

---

Outside diff comments:
In `@src/commands/run.tsx`:
- Around line 1617-1654: The SIGINT handler registered by process.on('SIGINT',
handleSigint) in the Promise should be removed during teardown to avoid
accumulating handlers; update the cleanup function in the same block (the
closure that currently calls renderer.destroy()) to also call
process.off('SIGINT', handleSigint) (or process.removeListener('SIGINT',
handleSigint)) so the handler is unregistered when EpicSelectionApp is torn
down, ensuring handleSigint is the same function reference used when
registering.

---

Nitpick comments:
In `@src/commands/resume.tsx`:
- Around line 199-203: The function formatSessionEntry currently sets
trackerInfo by checking entry.epicId first and ignores entry.epicIds, causing
multi-epic sessions to be displayed incorrectly; update the trackerInfo logic in
formatSessionEntry to prefer entry.epicIds when present (e.g., join the epic IDs
into a readable string like "epic:id1,id2" or similar) and fall back to
entry.epicId, then to entry.prdPath and entry.trackerPlugin, ensuring the
epicIds representation is included in the returned string (refer to trackerInfo,
entry.epicIds, entry.epicId, and formatSessionEntry to locate and change the
code).
- Around line 727-740: The warning branch that runs when
shouldWarnAboutTrackerMismatch(engineState.totalTasks, sessionTotalTasks) is
triggered only checks resumedState.trackerState.epicId and prdPath and omits
resumedState.trackerState.epicIds; update the message to include epicIds when
present so multi-epic resumes show the configured epic list. Locate the block
that logs the warnings (the if using shouldWarnAboutTrackerMismatch) and add a
conditional that checks resumedState.trackerState.epicIds (and formats them as a
comma-separated list or similar) and logs a user-friendly line like “Configured
epic IDs: …” alongside the existing epicId/prdPath lines. Ensure you reference
resumedState.trackerState.epicIds and keep the new log only when epicIds is
non-empty to avoid noise.

In `@src/plugins/trackers/multi-scope.ts`:
- Around line 96-118: The current loop over this.scopes calls
this.delegate.getTasks sequentially causing N round-trips; change it to fetch
all scoped task lists concurrently by mapping this.scopes to an array of
promises using this.delegate.getTasks(mergeFilterForScope(filter, scope)),
awaiting Promise.all to get results in the same scope order, then iterate the
resolved arrays in original scope order to perform the existing dedupe/warn
logic using seenTaskIds and this.warnedDuplicateIds and push into combined
(preserving executionScope and metadata). Ensure error handling/propagation
remains consistent when awaiting the parallel requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 18559a3e-05ef-47b1-ae8c-162f6d113047

📥 Commits

Reviewing files that changed from the base of the PR and between b58a99c and b488c12.

📒 Files selected for processing (35)
  • README.md
  • src/commands/resume.tsx
  • src/commands/run.test.ts
  • src/commands/run.tsx
  • src/config/index.ts
  • src/config/types.ts
  • src/engine/index.ts
  • src/parallel/events.ts
  • src/parallel/index.ts
  • src/parallel/types.ts
  • src/parallel/worktree-manager.test.ts
  • src/parallel/worktree-manager.ts
  • src/plugins/trackers/index.ts
  • src/plugins/trackers/multi-scope.test.ts
  • src/plugins/trackers/multi-scope.ts
  • src/plugins/trackers/types.ts
  • src/remote/client.ts
  • src/remote/server.ts
  • src/remote/types.ts
  • src/session/index.ts
  • src/session/persistence.ts
  • src/session/registry.ts
  • src/session/types.ts
  • src/tui/components/EpicSelectionApp.tsx
  • src/tui/components/EpicSelectionView.tsx
  • src/tui/components/LeftPanel.tsx
  • src/tui/components/MergeProgressView.tsx
  • src/tui/components/ParallelProgressView.tsx
  • src/tui/components/RunApp.tsx
  • src/tui/components/ScopeFilterBar.tsx
  • src/tui/components/WorkerDetailView.tsx
  • src/tui/components/index.ts
  • src/tui/theme.ts
  • src/tui/types.ts
  • tests/remote/remote.test.ts

Comment thread src/commands/run.tsx
Comment thread src/plugins/trackers/multi-scope.ts Outdated
@subsy subsy changed the title Add multi-epic support, in-session agent/model picker, and --remote-only mode Add multi-epic orchestration support May 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
website/content/docs/getting-started/quick-start.mdx (1)

146-157: ⚡ Quick win

Consider documenting the scope filter keys for multi-epic runs.

Now that multi-epic parallel sessions are prominently featured in the quick start, users would benefit from knowing about the scope filter bar. The PR introduces keys g / G to narrow the TUI view per scope, but these aren't currently documented in the keyboard shortcuts section.

📋 Suggested addition to View Controls section
 | `o` | Cycle right panel views (details → output → prompt) |
 | `O` | Jump directly to prompt preview |
 | `d` | Toggle progress dashboard |
 | `a` | Open agent/model picker |
+| `g` / `G` | Cycle scope filter (for multi-epic sessions) |
 | `h` | Toggle show/hide closed tasks |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/content/docs/getting-started/quick-start.mdx` around lines 146 - 157,
The View Controls keyboard shortcuts table is missing the new scope filter keys
for multi-epic runs; add entries for `g` and `G` to the table (e.g., `g` —
cycle/narrow to next scope filter, `G` — cycle/narrow to previous scope or show
all scopes) and include a one-line note in the View Controls section explaining
that these keys filter the TUI by epic/scope for multi-epic parallel sessions
(reference the existing "View Controls" table and the keys `g`/`G` when making
the change).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/session/persistence.test.ts`:
- Around line 66-71: The test uses loaded! when calling getSessionSummary, which
hides null failures; after calling loadPersistedSession(cwd) assert that the
result is non-null (e.g., expect(loaded).toBeDefined() or similar) before using
getSessionSummary so failures are explicit—add the assertion immediately after
const loaded = await loadPersistedSession(cwd) and then call
getSessionSummary(loaded) without the non-null assertion.

In `@website/content/docs/parallel/configuration.mdx`:
- Line 66: The table row for the `--epic <id>` flag has an incomplete sentence;
update the description text for `--epic <id>` to include an explicit subject
(for example: "Select an epic. This option can be repeated for multi-epic
hierarchy tracker runs.") so the second sentence is grammatically complete and
clear.

---

Nitpick comments:
In `@website/content/docs/getting-started/quick-start.mdx`:
- Around line 146-157: The View Controls keyboard shortcuts table is missing the
new scope filter keys for multi-epic runs; add entries for `g` and `G` to the
table (e.g., `g` — cycle/narrow to next scope filter, `G` — cycle/narrow to
previous scope or show all scopes) and include a one-line note in the View
Controls section explaining that these keys filter the TUI by epic/scope for
multi-epic parallel sessions (reference the existing "View Controls" table and
the keys `g`/`G` when making the change).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 997af4de-4aab-43b1-bc7d-8592cda0a96d

📥 Commits

Reviewing files that changed from the base of the PR and between b488c12 and ac9f6dd.

📒 Files selected for processing (30)
  • README.md
  • src/config/index.test.ts
  • src/engine/index.test.ts
  • src/parallel/parallel-executor.test.ts
  • src/plugins/trackers/builtin/beads-rust/README.md
  • src/plugins/trackers/multi-scope.test.ts
  • src/remote/server.test.ts
  • src/remote/server.ts
  • src/session/persistence.test.ts
  • src/session/registry.test.ts
  • website/content/docs/cli/overview.mdx
  • website/content/docs/cli/remote.mdx
  • website/content/docs/cli/resume.mdx
  • website/content/docs/cli/run.mdx
  • website/content/docs/configuration/config-file.mdx
  • website/content/docs/configuration/options.mdx
  • website/content/docs/configuration/overview.mdx
  • website/content/docs/getting-started/quick-start.mdx
  • website/content/docs/parallel/configuration.mdx
  • website/content/docs/parallel/git-worktrees.mdx
  • website/content/docs/parallel/how-it-works.mdx
  • website/content/docs/parallel/merge-and-conflicts.mdx
  • website/content/docs/parallel/overview.mdx
  • website/content/docs/parallel/task-dependencies.mdx
  • website/content/docs/parallel/troubleshooting.mdx
  • website/content/docs/parallel/tui-guide.mdx
  • website/content/docs/plugins/overview.mdx
  • website/content/docs/plugins/trackers/beads-bv.mdx
  • website/content/docs/plugins/trackers/beads-rust.mdx
  • website/content/docs/plugins/trackers/beads.mdx
✅ Files skipped from review due to trivial changes (6)
  • website/content/docs/configuration/overview.mdx
  • website/content/docs/configuration/config-file.mdx
  • website/content/docs/parallel/task-dependencies.mdx
  • website/content/docs/plugins/trackers/beads.mdx
  • website/content/docs/parallel/git-worktrees.mdx
  • website/content/docs/plugins/trackers/beads-bv.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
  • README.md
  • src/plugins/trackers/multi-scope.test.ts

Comment thread src/session/persistence.test.ts Outdated
Comment thread website/content/docs/parallel/configuration.mdx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/commands/run.tsx (2)

1640-1663: 💤 Low value

handleSigint reassignment pattern is fragile.

handleSigint is initialised to a no-op at Line 1640, reassigned at Line 1658, then registered with process.on at Line 1663 and removed inside cleanup via the captured binding. This currently works because process.on captures the (post-reassignment) reference and cleanup reads the same binding, but the indirection is easy to break in future edits (e.g., moving process.on(...) above the reassignment would silently register the no-op). Consider declaring the handler as a const directly:

♻️ Suggested simplification
-    let handleSigint = () => {};
-
-    const cleanup = () => {
-      process.off('SIGINT', handleSigint);
-      renderer.destroy();
-    };
-
-    const handleEpicSelected = (epics: TrackerTask[]) => {
-      cleanup();
-      resolve(epics);
-    };
-
-    const handleQuit = () => {
-      cleanup();
-      resolve(undefined);
-    };
-
-    // Handle Ctrl+C during epic selection
-    handleSigint = () => {
-      cleanup();
-      resolve(undefined);
-    };
-
-    process.on('SIGINT', handleSigint);
+    const handleSigint = () => {
+      cleanup();
+      resolve(undefined);
+    };
+
+    const cleanup = () => {
+      process.off('SIGINT', handleSigint);
+      renderer.destroy();
+    };
+
+    const handleEpicSelected = (epics: TrackerTask[]) => {
+      cleanup();
+      resolve(epics);
+    };
+
+    const handleQuit = () => {
+      cleanup();
+      resolve(undefined);
+    };
+
+    process.on('SIGINT', handleSigint);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/run.tsx` around lines 1640 - 1663, The SIGINT handler pattern is
fragile because handleSigint is assigned then reassigned before being
registered; instead define the handler as a single constant function (e.g.,
const handleSigint = () => { cleanup(); resolve(undefined); }) and use that same
reference when calling process.on('SIGINT', handleSigint) and
process.off('SIGINT', handleSigint) inside cleanup; ensure cleanup (which calls
renderer.destroy()) remains unchanged and that handleEpicSelected and handleQuit
still call cleanup() then resolve to keep current behavior.

3704-3708: 💤 Low value

Duplicate execution-scope resolution.

resolveExecutionScopes is invoked in two adjacent code paths with the same epicIds/executionScopes guard. The first call (around Line 3704) happens before session resume, and the second (around Line 3819) re-runs after the session block. Consider consolidating: resolve once after the resume branch has had a chance to set config.epicIds/executionScopes, and reuse trackerForRun for the engine. This removes the redundant getEpics round-trip and keeps the scope-resolution logic in one place.

Also applies to: 3819-3821

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/run.tsx` around lines 3704 - 3708, Duplicate calls to
resolveExecutionScopes should be consolidated: remove the early call (the block
that checks config.epicIds && config.epicIds.length > 0 &&
executionScopes.length === 0 around where trackerForRun is obtained via
getTrackerRegistry()) and instead perform a single resolveExecutionScopes call
after the session/resume branch (the area that may mutate
config.epicIds/executionScopes), reusing the existing trackerForRun instance for
both resolution and engine setup; ensure trackerForRun is initialized once
(trackerForRun = trackerForRun ?? await
trackerRegistry.getInstance(config.tracker)) before the single
resolveExecutionScopes call so you avoid the duplicate getEpics round-trip and
centralize scope-resolution logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/run.tsx`:
- Around line 1683-1688: The catch around the tracker.getEpics() call silently
swallows failures and causes fallback synthetic scopes; update the catch to
surface the error via the existing structured logger (or stderr) before falling
back: catch the error from tracker.getEpics(), log a clear message including the
error object and context (e.g. "failed to resolve epics from tracker" plus the
error) so failures are debuggable, then continue assigning epics fallback to the
epics variable as before; locate the tracker.getEpics() call and the epics
variable in run.tsx to add the logging.

---

Nitpick comments:
In `@src/commands/run.tsx`:
- Around line 1640-1663: The SIGINT handler pattern is fragile because
handleSigint is assigned then reassigned before being registered; instead define
the handler as a single constant function (e.g., const handleSigint = () => {
cleanup(); resolve(undefined); }) and use that same reference when calling
process.on('SIGINT', handleSigint) and process.off('SIGINT', handleSigint)
inside cleanup; ensure cleanup (which calls renderer.destroy()) remains
unchanged and that handleEpicSelected and handleQuit still call cleanup() then
resolve to keep current behavior.
- Around line 3704-3708: Duplicate calls to resolveExecutionScopes should be
consolidated: remove the early call (the block that checks config.epicIds &&
config.epicIds.length > 0 && executionScopes.length === 0 around where
trackerForRun is obtained via getTrackerRegistry()) and instead perform a single
resolveExecutionScopes call after the session/resume branch (the area that may
mutate config.epicIds/executionScopes), reusing the existing trackerForRun
instance for both resolution and engine setup; ensure trackerForRun is
initialized once (trackerForRun = trackerForRun ?? await
trackerRegistry.getInstance(config.tracker)) before the single
resolveExecutionScopes call so you avoid the duplicate getEpics round-trip and
centralize scope-resolution logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7be0ae3a-2c6a-40d0-80cb-be506af1f647

📥 Commits

Reviewing files that changed from the base of the PR and between ac9f6dd and 0fd4782.

📒 Files selected for processing (10)
  • src/commands/resume.tsx
  • src/commands/run.test.ts
  • src/commands/run.tsx
  • src/parallel/index.ts
  • src/parallel/parallel-executor.test.ts
  • src/parallel/types.ts
  • src/plugins/trackers/multi-scope.test.ts
  • src/plugins/trackers/multi-scope.ts
  • src/remote/server.test.ts
  • src/remote/server.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/parallel/types.ts
  • src/remote/server.test.ts
  • src/parallel/index.ts
  • src/remote/server.ts
  • src/plugins/trackers/multi-scope.test.ts
  • src/plugins/trackers/multi-scope.ts
  • src/commands/resume.tsx

Comment thread src/commands/run.tsx
@subsy subsy merged commit bae8094 into main May 13, 2026
10 checks passed
@subsy subsy deleted the t3code/8d8163e1 branch May 13, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant