Skip to content

feat(tracker): Support for Beads-Rust-BV plugin#342

Merged
subsy merged 9 commits intosubsy:mainfrom
cachemoney:main
Mar 13, 2026
Merged

feat(tracker): Support for Beads-Rust-BV plugin#342
subsy merged 9 commits intosubsy:mainfrom
cachemoney:main

Conversation

@cachemoney
Copy link
Contributor

@cachemoney cachemoney commented Feb 25, 2026

This tracker supports beads-rust with using bv as the source to ralph-tui pick up next beads to work on. The beads-rust tracker only supports beads, bd, cli.

Summary by CodeRabbit

  • New Features

    • Added a beads-rust-bv tracker that integrates optional BV analysis for graph‑aware task selection, triage metadata enrichment, async triage refreshes and graceful fallbacks when BV is unavailable.
  • Templates

    • New built‑in beads‑rust‑bv template and runtime support to include BV selection reasons in prompts.
  • Documentation

    • New docs page covering installation, usage, config and troubleshooting.
  • Tests

    • Comprehensive test suite validating BV integration, metadata enrichment and fallback behaviour.
  • UI

    • Navigation updated to surface the new tracker in Plugins > Trackers.

@vercel
Copy link

vercel bot commented Feb 25, 2026

@cachemoney is attempting to deploy a commit to the plgeek Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

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

Walkthrough

Adds a composite BeadsRustBvTrackerPlugin that composes the existing beads-rust tracker and integrates the bv CLI for graph-aware task selection and triage; registers the tracker, adds a built-in template/type, docs, and tests; exposes BV availability and triage controls.

Changes

Cohort / File(s) Summary
Core Plugin Implementation
src/plugins/trackers/builtin/beads-rust-bv/index.ts
New BeadsRustBvTrackerPlugin, BV-related types, execBv utility, triage caching/throttling and background refresh scheduling; delegates many ops to beads-rust, runs bv --robot-next/--robot-triage, decorates tasks with BV metadata, exposes isBvAvailable/refreshTriage/getTemplate.
Built-in Tracker Registry
src/plugins/trackers/builtin/index.ts
Imports, registers and re-exports createBeadsRustBvTracker in builtinTrackers.
Templates
src/templates/builtin.ts, src/templates/engine.ts, src/templates/index.ts, src/templates/types.ts
Adds BEADS_RUST_BV_TEMPLATE, wires 'beads-rust-bv' into template resolution/install and re-exports it; note: builtin.ts includes a duplicate BEADS_RUST_BV_TEMPLATE declaration.
Tests
tests/plugins/beads-rust-bv-tracker.test.ts
Extensive new test suite mocking CLIs and filesystem: detection, BV availability, getNextTask BV selection and fallback paths, triage refresh scheduling/deduplication, lifecycle methods, template content and error handling.
Docs & Navigation
website/content/docs/plugins/trackers/beads-rust-bv.mdx, website/lib/navigation.ts
New documentation page and a navigation entry under Plugins → Trackers.
Engine context
src/engine/index.ts
Injects selectionReason (from task.metadata.bvReasons) into the extended template rendering context.

Sequence Diagram

sequenceDiagram
    participant User as User/Ralph
    participant Plugin as BeadsRustBvTracker
    participant BR as br CLI
    participant BV as bv CLI
    participant Cache as Triage Cache

    User->>Plugin: getNextTask()
    Plugin->>BR: request candidate tasks (delegate)
    BR-->>Plugin: candidate tasks

    alt bv available
        Plugin->>Cache: check triage cache
        alt cache missing/stale
            Plugin->>BV: bv --robot-triage
            BV-->>Plugin: triage recommendations
            Plugin->>Cache: store triage
        end
        Plugin->>BV: bv --robot-next (with labels/epic filters)
        alt BV returns task
            BV-->>Plugin: selected task + metadata
            Plugin-->>User: decorated task (bvScore/bvReasons/bvUnblocks)
        else BV fails/empty
            Plugin->>BR: fallback to delegate selection
            BR-->>Plugin: fallback task
            Plugin-->>User: task
        end
    else bv unavailable
        Plugin->>BR: delegate selection
        BR-->>Plugin: task
        Plugin-->>User: task
    end

    User->>Plugin: completeTask()/updateTaskStatus()
    Plugin->>Plugin: schedule triage refresh (throttled)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(tracker): Support for Beads-Rust-BV plugin' accurately summarises the main change: introduction of a new tracker plugin that integrates Beads-Rust with BV graph-aware task selection.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/templates/engine.ts (1)

648-654: ⚠️ Potential issue | 🟡 Minor

installBuiltinTemplates includes beads-rust-bv but is missing beads-rust.

The BEADS_RUST_TEMPLATE is imported at line 22 and handled in getBuiltinTemplate, but it's absent from the templates map here. This means ralph will install the beads-rust-bv template globally but not the beads-rust template. This appears to be a pre-existing omission, but since this PR is adding to this map, it's a good time to fix it.

💡 Proposed fix
   const templates: Record<string, string> = {
     'default': DEFAULT_TEMPLATE,
     'beads': BEADS_TEMPLATE,
     'beads-bv': BEADS_BV_TEMPLATE,
+    'beads-rust': BEADS_RUST_TEMPLATE,
     'beads-rust-bv': BEADS_RUST_BV_TEMPLATE,
     'json': JSON_TEMPLATE,
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/templates/engine.ts` around lines 648 - 654, The templates map is missing
the 'beads-rust' entry so installBuiltinTemplates won't install
BEADS_RUST_TEMPLATE; update the templates Record named templates (in
src/templates/engine.ts) to include the 'beads-rust': BEADS_RUST_TEMPLATE
key/value alongside the existing entries (e.g., 'beads-rust-bv':
BEADS_RUST_BV_TEMPLATE) so that getBuiltinTemplate/ installBuiltinTemplates can
locate and install the beads-rust template.
🧹 Nitpick comments (5)
src/plugins/trackers/builtin/beads-rust-bv/index.ts (2)

84-115: Missing timeout on execBv — risk of indefinite hang.

If the bv process hangs (e.g. waiting on a lock, network, or broken pipe), this promise never resolves, stalling the entire plugin. Consider adding a timeout that kills the child process after a reasonable deadline.

💡 Proposed fix — add a timeout to execBv
 async function execBv(
     args: string[],
-    cwd?: string
+    cwd?: string,
+    timeoutMs = 30_000
 ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
     return new Promise((resolve) => {
         const proc = spawn('bv', args, {
             cwd,
             env: { ...process.env },
             stdio: ['ignore', 'pipe', 'pipe'],
         });
 
         let stdout = '';
         let stderr = '';
+        let settled = false;
+
+        const timer = setTimeout(() => {
+            if (!settled) {
+                settled = true;
+                proc.kill('SIGTERM');
+                resolve({ stdout, stderr: stderr + '\nexecBv timed out', exitCode: 1 });
+            }
+        }, timeoutMs);
 
         proc.stdout.on('data', (data: Buffer) => {
             stdout += data.toString();
         });
 
         proc.stderr.on('data', (data: Buffer) => {
             stderr += data.toString();
         });
 
         proc.on('close', (code) => {
-            resolve({ stdout, stderr, exitCode: code ?? 1 });
+            if (!settled) {
+                settled = true;
+                clearTimeout(timer);
+                resolve({ stdout, stderr, exitCode: code ?? 1 });
+            }
         });
 
         proc.on('error', (err) => {
-            stderr += err.message;
-            resolve({ stdout, stderr, exitCode: 1 });
+            if (!settled) {
+                settled = true;
+                clearTimeout(timer);
+                stderr += err.message;
+                resolve({ stdout, stderr, exitCode: 1 });
+            }
         });
     });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts` around lines 84 - 115,
The execBv helper currently can hang if the spawned bv process never exits;
modify execBv to start a timer (e.g., const timer = setTimeout(...)) after spawn
that kills proc (proc.kill()) and appends a timeout message to stderr then
resolves with a non-zero exitCode, and ensure you clearTimeout(timer) in both
proc.on('close') and proc.on('error') handlers so the promise always resolves
either on normal exit, error, or timeout; keep the existing stdout/stderr
aggregation and preserve the proc/error handling semantics.

257-356: getNextTask only forwards the first label to bv --robot-next.

Line 271 passes labelsToUse[0] only. If the user specifies multiple labels (e.g. "auth,backend"), subsequent labels are silently dropped. If bv supports multiple --label flags or comma-separated values, consider forwarding all labels. If this is a known bv limitation, a brief comment would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts` around lines 257 - 356,
getNextTask currently only forwards the first label (labelsToUse[0]) to bv
--robot-next, dropping additional labels; update the code that builds args (in
getNextTask, where args is created before calling execBv) to pass all labels
supported by bv—either by pushing each label as its own --label flag (e.g., loop
over labelsToUse and args.push('--label', label)) or by joining labelsToUse into
a single comma-separated value if bv expects that; if bv truly only accepts one
label, add a short comment near labelsToUse explaining the limitation and
consider logging/validating multi-label input.
tests/plugins/beads-rust-bv-tracker.test.ts (3)

210-216: Extensive as unknown as casts for private member access are brittle.

Nearly every test relies on (plugin as unknown as {...}) to reach internal state (bvAvailable, delegate, scheduleTriageRefresh, lastTriageOutput, etc.). If any of these property names are renamed in the implementation, these tests will silently compile but fail at runtime. Consider one of:

  • Exposing a lightweight test-only interface or a __testHarness accessor on the plugin.
  • Centralising the cast into a single typed helper (e.g. asTestable(plugin)) so that at least there is a single place to update when internals change.
Example helper to reduce cast sprawl
interface TestableBvPlugin {
    bvAvailable: boolean;
    delegate: {
        getNextTask: () => Promise<TrackerTask | undefined>;
        getTask: (id: string) => Promise<TrackerTask>;
        getTasks: () => Promise<TrackerTask[]>;
        completeTask: (id: string) => Promise<{ success: boolean; message: string }>;
        updateTaskStatus: (id: string, status: string) => Promise<TrackerTask | undefined>;
        validateSetup: (a: Record<string, unknown>) => Promise<string | null>;
        detect: () => Promise<{ available: boolean; brVersion: undefined }>;
    };
    scheduleTriageRefresh: (force?: boolean) => void;
    lastTriageOutput: unknown;
    refreshTriage: () => Promise<void>;
    triageRefreshInFlight: Promise<void> | null;
}

function asTestable(plugin: BeadsRustBvTrackerPlugin): TestableBvPlugin {
    return plugin as unknown as TestableBvPlugin;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/plugins/beads-rust-bv-tracker.test.ts` around lines 210 - 216, Tests
repeatedly use brittle "(plugin as unknown as {...})" casts to access private
internals (bvAvailable, delegate, scheduleTriageRefresh, lastTriageOutput,
etc.); centralize this by adding a single test-only accessor/helper and update
tests to use it—create a Testable interface (e.g., TestableBvPlugin) describing
the needed internals and implement an asTestable(plugin:
BeadsRustBvTrackerPlugin): TestableBvPlugin helper that performs the cast in one
place, then replace per-test casts (including in makePlugin) to call
asTestable(plugin) to set or read internals like bvAvailable, delegate,
scheduleTriageRefresh, refreshTriage, and lastTriageOutput.

42-56: Spawn mock discards args, limiting argument verification.

The mock only receives command and ignores all arguments (args, options). This means tests cannot verify that the correct flags (e.g. --robot-next, --label backend) are being passed to br or bv. This directly undermines the "forwards label filter to bv" test (line 335), which ends up asserting expect(true).toBe(true) because it has no way to inspect the invocation.

Consider capturing args alongside the command so that tests can assert on them:

Sketch of an args-aware mock
-            spawn: (command: string) => {
+            spawn: (command: string, args?: string[]) => {
                 const proc = new EventEmitter() as EventEmitter & {
                     stdout: EventEmitter;
                     stderr: EventEmitter;
                 };
                 proc.stdout = new EventEmitter();
                 proc.stderr = new EventEmitter();
+
+                capturedSpawnCalls.push({ command, args: args ?? [] });
 
                 const matchIndex = spawnResponses.findIndex(
                     (r) => r.command === command || r.command === '*'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/plugins/beads-rust-bv-tracker.test.ts` around lines 42 - 56, The spawn
mock currently only accepts a single "command" string and discards "args" and
"options", preventing tests from asserting flags; update the mock signature in
the spawn implementation used in tests to accept (command: string, args?:
string[], options?: any), capture and forward the args/options into the created
proc object (or into the matched spawnResponses entries) so spawnResponses can
be matched/inspected by both command and args; adjust the matching logic around
spawnResponses.findIndex to consider r.command and r.args (or store the actual
args on the returned response) so tests can assert on passed flags when calling
spawn.

617-620: Polling loop timeout could be fragile under CI load.

The 20 × 5ms (100ms) budget is tight for CI environments with constrained CPU. If this test becomes flaky, consider bumping the iteration count or sleep duration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/plugins/beads-rust-bv-tracker.test.ts` around lines 617 - 620, The
polling loop that waits for refreshCalls === 2 and state.triageRefreshInFlight
=== null is too tight for CI; increase the allowed wait by changing the loop
from 20×5ms to a larger budget (for example, 200 iterations with 10ms sleeps) or
otherwise use a test utility with a 2s timeout to wait for the condition (i.e.,
replace the loop around refreshCalls and state.triageRefreshInFlight with a
longer loop or a waitFor that times out after ~2000ms).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts`:
- Around line 228-243: Protect against missing triage data by adding runtime
checks and validation: in the block that uses this.lastTriageOutput (inside
getTasks), verify that this.lastTriageOutput is defined and that
this.lastTriageOutput.triage &&
Array.isArray(this.lastTriageOutput.triage.recommendations) before building
recMap and iterating tasks; skip or treat as empty when absent. Also harden
refreshTriage (the method that parses stdout into BvTriageOutput) to validate
the parsed object shape (ensure parsed.triage is an object and
parsed.triage.recommendations is an array) before assigning to
this.lastTriageOutput, and fall back to a safe default (e.g., undefined or an
object with empty recommendations) when validation fails so getTasks cannot
encounter undefined properties.

In `@tests/plugins/beads-rust-bv-tracker.test.ts`:
- Around line 1-9: Update the file-level JSDoc so the first line of the
top-of-file comment begins with the required prefix "ABOUTME: " (e.g., change
the existing comment intro to start with "ABOUTME: Tests for
BeadsRustBvTrackerPlugin..."); locate the file-level JSDoc at the top of the
test module (the header comment above the test declarations) and ensure the
prefix and brief purpose text are present exactly as required by the linting
guideline.
- Around line 335-353: The test title claims it forwards labels to bv but only
asserts true; update the test to actually assert spawn received the label flag:
when invoking plugin.getNextTask({ labels: ['backend'] }) have your spawn/mock
(the existing queueSpawnResponse or spawn interceptor) push the called command
and argv into capturedArgs, then assert that the bv invocation includes the
label flag (e.g. contains '--label' and 'backend' or a single '--label backend'
arg). Modify the test named "forwards label filter to bv" to use capturedArgs
and an expectation on capturedArgs for the bv command instead of
expect(true).toBe(true); keep plugin.getNextTask and queueSpawnResponse usage.

In `@website/content/docs/plugins/trackers/beads-rust-bv.mdx`:
- Around line 14-40: MDX v2 requires JSX tags that wrap block-level markdown to
have blank lines after the opening tag and before the closing tag; update every
<Step> element that contains a fenced code block (``` or ```bash) so there is an
empty line immediately after the opening <Step> and an empty line immediately
before the closing </Step> (e.g., for the <Step> blocks around the br init,
cargo install bv, and both version-check code fences, and all similar Steps in
the "Basic Usage" section) to resolve the "Expected a closing tag for <Step>"
compilation error.

---

Outside diff comments:
In `@src/templates/engine.ts`:
- Around line 648-654: The templates map is missing the 'beads-rust' entry so
installBuiltinTemplates won't install BEADS_RUST_TEMPLATE; update the templates
Record named templates (in src/templates/engine.ts) to include the 'beads-rust':
BEADS_RUST_TEMPLATE key/value alongside the existing entries (e.g.,
'beads-rust-bv': BEADS_RUST_BV_TEMPLATE) so that getBuiltinTemplate/
installBuiltinTemplates can locate and install the beads-rust template.

---

Nitpick comments:
In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts`:
- Around line 84-115: The execBv helper currently can hang if the spawned bv
process never exits; modify execBv to start a timer (e.g., const timer =
setTimeout(...)) after spawn that kills proc (proc.kill()) and appends a timeout
message to stderr then resolves with a non-zero exitCode, and ensure you
clearTimeout(timer) in both proc.on('close') and proc.on('error') handlers so
the promise always resolves either on normal exit, error, or timeout; keep the
existing stdout/stderr aggregation and preserve the proc/error handling
semantics.
- Around line 257-356: getNextTask currently only forwards the first label
(labelsToUse[0]) to bv --robot-next, dropping additional labels; update the code
that builds args (in getNextTask, where args is created before calling execBv)
to pass all labels supported by bv—either by pushing each label as its own
--label flag (e.g., loop over labelsToUse and args.push('--label', label)) or by
joining labelsToUse into a single comma-separated value if bv expects that; if
bv truly only accepts one label, add a short comment near labelsToUse explaining
the limitation and consider logging/validating multi-label input.

In `@tests/plugins/beads-rust-bv-tracker.test.ts`:
- Around line 210-216: Tests repeatedly use brittle "(plugin as unknown as
{...})" casts to access private internals (bvAvailable, delegate,
scheduleTriageRefresh, lastTriageOutput, etc.); centralize this by adding a
single test-only accessor/helper and update tests to use it—create a Testable
interface (e.g., TestableBvPlugin) describing the needed internals and implement
an asTestable(plugin: BeadsRustBvTrackerPlugin): TestableBvPlugin helper that
performs the cast in one place, then replace per-test casts (including in
makePlugin) to call asTestable(plugin) to set or read internals like
bvAvailable, delegate, scheduleTriageRefresh, refreshTriage, and
lastTriageOutput.
- Around line 42-56: The spawn mock currently only accepts a single "command"
string and discards "args" and "options", preventing tests from asserting flags;
update the mock signature in the spawn implementation used in tests to accept
(command: string, args?: string[], options?: any), capture and forward the
args/options into the created proc object (or into the matched spawnResponses
entries) so spawnResponses can be matched/inspected by both command and args;
adjust the matching logic around spawnResponses.findIndex to consider r.command
and r.args (or store the actual args on the returned response) so tests can
assert on passed flags when calling spawn.
- Around line 617-620: The polling loop that waits for refreshCalls === 2 and
state.triageRefreshInFlight === null is too tight for CI; increase the allowed
wait by changing the loop from 20×5ms to a larger budget (for example, 200
iterations with 10ms sleeps) or otherwise use a test utility with a 2s timeout
to wait for the condition (i.e., replace the loop around refreshCalls and
state.triageRefreshInFlight with a longer loop or a waitFor that times out after
~2000ms).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4477972 and 5a5f7e1.

📒 Files selected for processing (9)
  • src/plugins/trackers/builtin/beads-rust-bv/index.ts
  • src/plugins/trackers/builtin/index.ts
  • src/templates/builtin.ts
  • src/templates/engine.ts
  • src/templates/index.ts
  • src/templates/types.ts
  • tests/plugins/beads-rust-bv-tracker.test.ts
  • website/content/docs/plugins/trackers/beads-rust-bv.mdx
  • website/lib/navigation.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts`:
- Around line 235-236: The loop that builds recMap from
this.lastTriageOutput.triage.recommendations should validate each entry before
using rec.id: update the block that iterates recommendations (and the similar
loop around the getTasks-related code) to skip or log any non-object or null
entries and any entries missing a string/number id, e.g., check typeof rec ===
'object' && rec !== null && ('id' in rec) before calling recMap.set(rec.id,
rec); ensure you perform the same defensive validation in the analogous code
handling recommendations at the 471-479 area so malformed array items cannot
cause runtime errors in getTasks.
- Around line 467-489: The debounce timestamp lastTriageRefreshAt is only set on
the successful-parse path, so failures (non-zero exitCode or JSON parse errors)
bypass the throttle and can repeatedly spawn execBv; update lastTriageRefreshAt
in all code paths that handle bv triage (both the exitCode !== 0 branch and the
catch block after JSON.parse) and ensure lastTriageOutput is set to a safe
default ({ triage: { recommendations: [] } }) on failures; apply the same change
to the other similar triage-handling block (the second execBv/parsing block) so
both paths always set lastTriageRefreshAt regardless of success or failure.

In `@website/content/docs/plugins/trackers/beads-rust-bv.mdx`:
- Around line 109-111: Update the documentation to state that the plugin option
labels accepts both a comma-separated string and an array by documenting its
type as "string | string[]"; update the inline config snippet (the block showing
epicId, labels, workingDir) and the options table entries referencing labels to
use "string | string[]" and add a short example or note explaining that a
comma-separated string and a string array are both supported so users
configuring via structured config know both formats work; target the labels
entry in this file (labels variable in the config snippet) and the options table
rows that describe labels.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a5f7e1 and c15332e.

📒 Files selected for processing (3)
  • src/plugins/trackers/builtin/beads-rust-bv/index.ts
  • tests/plugins/beads-rust-bv-tracker.test.ts
  • website/content/docs/plugins/trackers/beads-rust-bv.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/plugins/beads-rust-bv-tracker.test.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/plugins/trackers/builtin/beads-rust-bv/index.ts (1)

84-115: Consider adding a timeout to prevent indefinite blocking.

If bv hangs (e.g., due to a deadlock or waiting for input), execBv will block indefinitely. This could freeze the TUI. Consider adding a timeout mechanism.

💡 Proposed fix using AbortController
 async function execBv(
     args: string[],
-    cwd?: string
+    cwd?: string,
+    timeoutMs = 30_000
 ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
     return new Promise((resolve) => {
+        const controller = new AbortController();
+        const timeout = setTimeout(() => controller.abort(), timeoutMs);
+
         const proc = spawn('bv', args, {
             cwd,
             env: { ...process.env },
             stdio: ['ignore', 'pipe', 'pipe'],
+            signal: controller.signal,
         });

         let stdout = '';
         let stderr = '';

         proc.stdout.on('data', (data: Buffer) => {
             stdout += data.toString();
         });

         proc.stderr.on('data', (data: Buffer) => {
             stderr += data.toString();
         });

         proc.on('close', (code) => {
+            clearTimeout(timeout);
             resolve({ stdout, stderr, exitCode: code ?? 1 });
         });

         proc.on('error', (err) => {
+            clearTimeout(timeout);
             stderr += err.message;
             resolve({ stdout, stderr, exitCode: 1 });
         });
     });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts` around lines 84 - 115,
The execBv function can hang if the spawned 'bv' process stalls; add a timeout
using an AbortController (or a timer) to terminate the child and resolve the
promise when exceeded. Modify execBv to create an AbortController or setTimeout
that calls proc.kill() (or proc.kill('SIGKILL')) and appends a timeout message
to stderr, then resolve with stdout, stderr and a non-zero exitCode (e.g., 124)
when the timer fires; ensure the timer is cleared on normal close/error and that
the AbortController signal is passed to spawn if using the Abort API so the
child is reliably cancelled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts`:
- Around line 84-115: The execBv function can hang if the spawned 'bv' process
stalls; add a timeout using an AbortController (or a timer) to terminate the
child and resolve the promise when exceeded. Modify execBv to create an
AbortController or setTimeout that calls proc.kill() (or proc.kill('SIGKILL'))
and appends a timeout message to stderr, then resolve with stdout, stderr and a
non-zero exitCode (e.g., 124) when the timer fires; ensure the timer is cleared
on normal close/error and that the AbortController signal is passed to spawn if
using the Abort API so the child is reliably cancelled.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c15332e and 786112b.

📒 Files selected for processing (2)
  • src/plugins/trackers/builtin/beads-rust-bv/index.ts
  • website/content/docs/plugins/trackers/beads-rust-bv.mdx

@vercel
Copy link

vercel bot commented Mar 6, 2026

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 Mar 13, 2026 1:42pm

Request Review

@subsy
Copy link
Owner

subsy commented Mar 13, 2026

Code review

Found 4 issues:

  1. getNextTask does not validate filter.status against the returned task — the sibling beads-bv plugin had this exact bug fixed in commit 35d2e127 ("Fall back to base tracker for beads-bv status mismatches"), which added three status-filter checks across all return paths. beads-rust-bv omits all of them, so a caller passing filter.status = ['in_progress'] can receive a task with the wrong status.

override async getNextTask(
filter?: TaskFilter
): Promise<TrackerTask | undefined> {
if (!this.bvAvailable) {
return this.delegate.getNextTask(filter);
}
try {
const args = ['--robot-next'];
// Forward label filter.
const labelsToUse =
filter?.labels && filter.labels.length > 0 ? filter.labels : this.labels;
if (labelsToUse.length > 0) {
args.push('--label', labelsToUse[0]!);
}
const { stdout, exitCode, stderr } = await execBv(
args,
this.workingDir
);
if (exitCode !== 0) {
console.error('bv --robot-next failed:', stderr);
return this.delegate.getNextTask(filter);
}
let nextOutputRaw: unknown;
try {
nextOutputRaw = JSON.parse(stdout) as BvRobotNextOutput;
} catch (err) {
console.error('Failed to parse bv --robot-next output:', err);
return this.delegate.getNextTask(filter);
}
if (hasMessageField(nextOutputRaw)) {
// No actionable items.
return this.delegate.getNextTask(filter);
}
if (!hasValidTaskId(nextOutputRaw)) {
console.error(
'Invalid bv --robot-next output (missing task id):',
nextOutputRaw
);
return this.delegate.getNextTask(filter);
}
const nextOutput = nextOutputRaw as BvRobotNextTask;
// Check epic membership: if an epic filter is active, ensure bv's pick
// belongs to it, otherwise fall back.
const epicFilter = filter?.parentId;
if (epicFilter) {
const fullTask = await this.delegate.getTask(nextOutput.id);
if (!fullTask || fullTask.parentId !== epicFilter) {
return this.delegate.getNextTask(filter);
}
// Augment and return.
fullTask.metadata = {
...fullTask.metadata,
bvScore: nextOutput.score,
bvReasons: nextOutput.reasons,
bvUnblocks: nextOutput.unblocks,
};
return fullTask;
}
// Schedule background triage refresh for metadata enrichment.
this.scheduleTriageRefresh();
// Fetch full task details.
const fullTask = await this.delegate.getTask(nextOutput.id);
if (fullTask) {
fullTask.metadata = {
...fullTask.metadata,
bvScore: nextOutput.score,

  1. scheduleTriageRefresh() is never called when the epic-filter path succeeds — the if (epicFilter) block returns early at line ~334 before reaching the scheduleTriageRefresh() call at line 338, leaving triage data permanently stale for users who filter by epic.

const epicFilter = filter?.parentId;
if (epicFilter) {
const fullTask = await this.delegate.getTask(nextOutput.id);
if (!fullTask || fullTask.parentId !== epicFilter) {
return this.delegate.getNextTask(filter);
}
// Augment and return.
fullTask.metadata = {
...fullTask.metadata,
bvScore: nextOutput.score,
bvReasons: nextOutput.reasons,
bvUnblocks: nextOutput.unblocks,
};
return fullTask;
}
// Schedule background triage refresh for metadata enrichment.
this.scheduleTriageRefresh();
// Fetch full task details.

  1. lastTriageRefreshAt = Date.now() is set unconditionally at line 509, outside the if (exitCode === 0) success block at line 476 — a failed bv --robot-triage invocation still arms the 30-second rate limiter, suppressing a corrective retry. The beads-bv plugin correctly updates this timestamp only on success.

const { stdout, exitCode } = await execBv(args, this.workingDir);
if (exitCode === 0) {
try {
const parsed = JSON.parse(stdout) as BvTriageOutput;
if (
parsed &&
typeof parsed === 'object' &&
parsed.triage &&
typeof parsed.triage === 'object' &&
Array.isArray(parsed.triage.recommendations)
) {
// Filter out any malformed items within the recommendations array.
parsed.triage.recommendations = parsed.triage.recommendations.filter(
(rec): rec is BvTriageRecommendation =>
typeof rec === 'object' &&
rec !== null &&
'id' in rec &&
typeof (rec as BvTriageRecommendation).id === 'string'
);
this.lastTriageOutput = parsed;
} else {
this.lastTriageOutput = { triage: { recommendations: [] } };
}
} catch {
// Ignore parse errors — fall back to empty recommendations.
this.lastTriageOutput = { triage: { recommendations: [] } };
}
} else {
// Non-zero exit: treat as empty to avoid stale data.
this.lastTriageOutput = { triage: { recommendations: [] } };
}
// Always update the timestamp so scheduleTriageRefresh's 30-second
// debounce is respected even when bv fails, preventing rapid retries.
this.lastTriageRefreshAt = Date.now();
}
override getTemplate(): string {

  1. selectionReason is never populated — the plugin stores reasons in task.metadata.bvReasons and the template prominently advertises a "Why This Task Was Selected" section via {{#if selectionReason}}, but src/engine/index.ts builds extendedContext with only recentProgress, codebasePatterns, and prd. Nothing maps task.metadata.bvReasonsextendedContext.selectionReason, so the section silently renders empty.

// Build extended template context with PRD data and patterns
const extendedContext = {
recentProgress,
codebasePatterns,
prd: prdContext ?? undefined,
};

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Add filter.status validation to getNextTask (regression vs beads-bv fix 35d2e12)
- Call scheduleTriageRefresh in epic-filter path to prevent stale triage data
- Move lastTriageRefreshAt update inside success branch to allow retries on failure
- Wire task.metadata.bvReasons to selectionReason in engine buildPrompt
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts`:
- Around line 282-289: The fallback to delegate.getNextTask(filter) can return a
task whose status is outside the requested statusFilter; update the calls (the
branches using statusFilter and calling this.delegate.getNextTask(filter)) to
validate the returned task's status against the original statusFilter and only
return it if it matches — otherwise continue searching or return null/undefined
as the contract requires. Concretely, after calling
this.delegate.getNextTask(filter) in the current function, check
returnedTask?.status and ensure statusFilter.some(s => s ===
returnedTask.status) (or the equivalent open/in_progress checks used elsewhere)
before returning; if it doesn't match, loop/try next or return no task. Ensure
the same fix is applied to the other similar branches noted (the delegate calls
around the same checks).
- Around line 501-529: The timestamp this.lastTriageRefreshAt is being updated
regardless of parse success; change the logic so the timestamp is only set when
parsing and validation succeed: inside the exitCode === 0 block, move the
this.lastTriageRefreshAt = Date.now() into the branch where parsed is validated
and this.lastTriageOutput is set to the parsed value (the block that filters
recommendations and assigns this.lastTriageOutput), and do not update the
timestamp in the catch or the "invalid parsed" else that sets an empty triage;
reference symbols: exitCode, stdout, BvTriageOutput, BvTriageRecommendation,
this.lastTriageOutput, this.lastTriageRefreshAt.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22614337-b5c9-4a30-8615-4bb371ae10c3

📥 Commits

Reviewing files that changed from the base of the PR and between 786112b and c4bcf13.

📒 Files selected for processing (2)
  • src/engine/index.ts
  • src/plugins/trackers/builtin/beads-rust-bv/index.ts

@subsy
Copy link
Owner

subsy commented Mar 13, 2026

Pushed a fix commit (c4bcf13c) addressing 4 issues found during code review:

1. getNextTask missing filter.status validation
The sibling beads-bv plugin had this exact bug fixed in 35d2e127 — the new plugin omitted all three status-filter checks, allowing tasks with the wrong status to be returned.

2. scheduleTriageRefresh() skipped in epic-filter path
The if (epicFilter) block returned early before reaching scheduleTriageRefresh(), leaving triage data permanently stale for epic-filtered sessions.

3. lastTriageRefreshAt set unconditionally on failure
Moved the timestamp update inside the success branch so failed bv --robot-triage calls don't suppress retries for 30 seconds. Matches the beads-bv implementation.

4. selectionReason never wired to template
The plugin stored bvReasons in task.metadata and the template had a {{#if selectionReason}} section, but nothing connected them. Added the mapping in buildPrompt().

- Wrap all delegate.getNextTask fallbacks with status validation via
  delegateGetNextTaskFiltered() so delegate can't return tasks outside
  the caller's statusFilter
- Move lastTriageRefreshAt update into the successful parse+validate
  branch only, so parse failures and invalid structures also allow
  immediate retries
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/plugins/trackers/builtin/beads-rust-bv/index.ts`:
- Around line 84-115: The execBv function currently waits only for
'close'/'error' and can hang if the spawned bv process stalls; add a 15-second
timeout using setTimeout when the process is spawned, call proc.kill('SIGKILL')
when the timer fires, append a timeout message to stderr and resolve the promise
with a non-zero exitCode, and ensure the timeout is cleared in both the
proc.on('close') and proc.on('error') handlers to avoid leaks; update references
in execBv to manage the timer variable and ensure consistent resolution path
when killed by timeout.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 157f1247-594a-4fba-8d6f-8ebc4ca60b6f

📥 Commits

Reviewing files that changed from the base of the PR and between c4bcf13 and 2016c55.

📒 Files selected for processing (1)
  • src/plugins/trackers/builtin/beads-rust-bv/index.ts

AI Agent added 4 commits March 13, 2026 11:20
The installBuiltinTemplates test expected 4 templates but beads-rust-bv
was added to the map without updating the assertion.
If bv stalls (deadlock, waiting for input), execBv now kills the process
after 15 seconds and resolves with a non-zero exit code instead of
hanging indefinitely.
beads-rust-bv-tracker.test.ts mocks node:child_process and
node:fs/promises via mock.module() which is process-wide in Bun.
Running it alongside other tests causes 27 failures in json-tracker,
gemini-agent detect, and Config Push tests. Matches the existing
isolation pattern used for beads-bv and beads-rust tests.
@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 63.19846% with 191 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.63%. Comparing base (0bc869f) to head (aa0028b).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
...rc/plugins/trackers/builtin/beads-rust-bv/index.ts 62.94% 186 Missing ⚠️
src/templates/engine.ts 50.00% 3 Missing ⚠️
src/engine/index.ts 75.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #342      +/-   ##
==========================================
+ Coverage   47.39%   47.63%   +0.24%     
==========================================
  Files         103      104       +1     
  Lines       33404    33923     +519     
==========================================
+ Hits        15831    16160     +329     
- Misses      17573    17763     +190     
Files with missing lines Coverage Δ
src/plugins/trackers/builtin/index.ts 95.00% <100.00%> (+0.55%) ⬆️
src/templates/builtin.ts 100.00% <100.00%> (ø)
src/templates/index.ts 100.00% <ø> (ø)
src/engine/index.ts 58.30% <75.00%> (+0.06%) ⬆️
src/templates/engine.ts 71.36% <50.00%> (-0.32%) ⬇️
...rc/plugins/trackers/builtin/beads-rust-bv/index.ts 62.94% <62.94%> (ø)

... and 1 file 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.

@subsy
Copy link
Owner

subsy commented Mar 13, 2026

Also pushed a few more commits to get CI green:

  • Test count fix (ec71e4bd): installBuiltinTemplates test expected 4 templates, now 5 with beads-rust-bv added.

  • execBv timeout (ed91e121): Added a 15-second timeout so the TUI doesn't freeze if bv stalls.

  • Delegate status validation (2016c557): Wrapped all delegate.getNextTask(filter) fallbacks with status validation, and tightened lastTriageRefreshAt to only update on successful parse+validate (not just exitCode 0).

  • CI test isolation (aa0028b4): The 27 test failures were caused by mock.module('node:fs/promises') in the new test file leaking into other tests when run in the same Bun process. Isolated it the same way beads-bv and beads-rust tests already are.

@subsy subsy merged commit a4ce56c into subsy:main Mar 13, 2026
8 checks passed
@subsy
Copy link
Owner

subsy commented Mar 13, 2026

Thanks for the contribution @cachemoney 🙌

@coderabbitai coderabbitai bot mentioned this pull request Mar 13, 2026
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.

2 participants