ralph: phase automation — self-reporting status.json + idle nudger + stage UX polish - #216
Conversation
Foundation for ralph-phase-automation. Agents write a per-item
status.json with {stage, is_waiting_for_user, brief_description,
timestamp} at every meaningful transition, letting ralph distinguish
"idle because stuck" from "idle because waiting for me" in every
input mode, not just ask_questions.
- ItemStatus interface + ITEM_STATUS_STALE_MS constant (24h).
- TrackerService.getItemStatusPath / getItemStatus / writeItemStatus
/ isItemStatusStale with loose schema validation and 120-char
brief_description clamping.
- Unit tests cover round-trip, missing file, malformed JSON,
missing required fields, truncation, and the staleness rule.
Also lands the discovery notes and requirements for this item so
reviewers can see the design context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each stage guide now appends a common "Agent status protocol" section telling the agent to keep tracker/items/<slug>/status.json in sync at every transition (stage start, pause, resume, advance). The appended block renders stage-specific instructions for three new settings, exposed via the existing TrackerStagesScreen: - input_mode (ask_questions | inline | batch | doc_review): picks how the agent asks for input during the stage. Non-ask_questions modes tell the agent to flip is_waiting_for_user in status.json. - gate_on_advance (none | review_and_advance | wait_for_approval): how the stage transition is gated. Defaults: discovery=none, requirements=wait_for_approval, implement=review_and_advance, cleanup=wait_for_approval. - submit (auto | approve) on cleanup only: PR creation gate. TrackerService gains getItemStage (prefers status.json, falls back to index.json buckets, defaults to backlog) and listItemsByStage. moveItem now mirrors the canonical stage into status.json so ralph and the kanban stay coherent. Existing body generator is kept private as defaultStageFileBody so only the public wrapper appends the protocol. Covered by 26 new unit tests (status.json round-trip, staleness, stage derivation, moveItem mirroring, protocol rendering for every mode × gate × stage combination). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The full ralph runtime: - src/cores/RalphCore.ts samples (ai_status, stage, status.json) per active worktree session. Fires stage-aware nudges via TmuxService when the agent is idle for >= idleThresholdMs AND the stage hasn't changed AND status.json isn't flagged is_waiting_for_user fresh AND nudges_this_stage < cap AND ralph is enabled for the project. Logs every nudge to logs/ralph.log. - src/cores/RalphCore.ts also exposes loadRalphConfig/saveRalphConfig for the per-project tracker/ralph.json settings. - TrackerStagesScreen gains a "Ralph" tab with three dropdowns: enabled (on/off), idle threshold (1/3/10/30 min), max nudges per stage (1/3/5/10). Changes persist immediately. - WorktreeContext runs RalphCore alongside WorktreeCore and decorates each worktree with a .ralph field containing is_waiting_for_user, brief_description, nudge count, cap state. WorktreeRow renders a compact suffix in the project/feature cell: "⏸ <brief>" when waiting, "n:X/Y" when nudged, "!" when capped. - 18 new unit tests cover every safety invariant (no nudge on waiting/working/fresh-flag/disabled/missing-session), detection (fires on sustained idle + unchanged stage), counter increments, cap behaviour, resets on stage change and on fresh waiting flip, stale-flag ignore, nudge text content, and log-entry format. Full suite: 664 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a comprehensive end-to-end ralph test (tests/unit/ralph.test.ts) covering the full agent lifecycle from acceptance criteria §38: waiting → clear → nudge → cap → advance → reset. Verifies the sequence of nudge firing, suppression while waiting, cap enforcement, and counter/cap reset on stage advancement. Also writes implementation.md with what was built, key design decisions (status.json vs MCP, canonical stage choice, agent-owned flag lifecycle), and notes for the cleanup stage. Full suite: 665 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
input_mode is a personal preference for how the agent delivers questions (ask_questions tool vs inline chat vs batched vs doc review) — not something that should change per stage. Moved out of COMMON_STAGE_OPTIONS (so stage tabs now only carry gate_on_advance) and into WorkStyle.inputMode, surfaced as a new row on the Style tab. - New InputModeStyle type + inputMode field on WorkStyle with default 'ask_questions'. loadWorkStyle already splats defaults, so existing work-style.json files pick up the new field on read. - defaultStageFileContent and renderStageProtocol take an optional WorkStyle third argument and read inputMode from it. - TrackerStagesScreen: preview + cycleStageOption pass the live workStyle. Cycling the new inputMode row on the Style tab regenerates all four stage guide files on disk so the protocol tail reflects the change immediately. - generateWorkStyleFileContent renders an "Input mode" row in the working-style.md output and drops the now-redundant "use the ask_questions tool" footer. - RalphCore.buildNudgeText now gets inputMode from WorkStyle, not from stage settings. - Tests updated to pass WorkStyle where they used to set input_mode in stage settings. Full suite: 665 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Writing a stage-review is a natural thing to do whenever the stage
did real work — not a separate gate mode. Merge review_and_advance
into auto_advance and drop it as a user-visible option.
gate_on_advance now has two values: auto_advance (default for
discovery + implement) or require_approval (default for
requirements + cleanup). The auto_advance instruction tells the
agent to append a short "## Stage review" section to the stage's
output file whenever the stage produced meaningful findings /
decisions / changes, and to skip it for trivial no-op stages.
Review authorship is implicit, not a separate toggle.
Legacy gate values ('none', 'review_and_advance', 'wait_for_approval')
in saved settings are mapped on read so existing projects don't
break. 'none' and 'review_and_advance' collapse to auto_advance;
'wait_for_approval' maps to require_approval. Covered by a test.
Full suite: 666 tests pass, typecheck clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The reminder now reads like a colleague checking in, not a system notification. The agent sees no "ralph" or bracketed system label at all — we want it read as conversation so the agent engages with it the same way it would with a human message. The rewritten text also explicitly reminds the agent of two things every time: - Update status.json with the current stage and either is_waiting_for_user: true + brief_description (if blocked on the user) or false + a short working-on note (if still going). - Flipping is_waiting_for_user: true stops these check-ins from firing — so the agent knows the escape hatch. New tests lock in: no "ralph" token in agent-facing text, the reminder mentions is_waiting_for_user and brief_description, and the message doesn't open with a system-style bracket prefix. Full ralph suite: 22 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TrackerBoardScreen now reads each item's status.json per card and treats a fresh is_waiting_for_user: true as equivalent to the tmux 'waiting' state for rendering — same yellow glyph, same bold treatment. The agent's brief_description replaces the generic "waiting for you" label when present, so the kanban answers "what is this one waiting on?" at a glance. When not waiting, if the agent reported a brief_description (e.g., "drafting section 2", "writing RalphCore"), we show that instead of "running" / "session idle" so idle agents with known activity aren't flattened to the same label. Stale status.json records (> 24h, per TrackerService's rule) are ignored, matching ralph's own suppression semantics — so a crashed agent whose flag never got cleared doesn't permanently mark the card as "waiting". Full suite: 669 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A single truncated line was too tight for the agent's brief_description — things like "drafting section 2 of requirements.md" got clipped mid-word. Each card now wraps the secondary text onto up to 2 lines, breaking on spaces when possible and appending '…' when content overflows. ROWS_PER_ITEM goes from 3 to 4 so the scroll math reserves a fixed slot for the second line. Short descriptions leave the second line blank — trading a bit of whitespace for predictable layout and no overlap when multiple items render in the same column at different lengths. Full suite: 669 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related improvements driven by the user ask:
1. "Stage done, waiting for approval to advance" is now a distinct
status.json state, not just generic is_waiting_for_user:
- New optional field awaiting_advance_approval: boolean on
ItemStatus. When true, the agent's work for the stage is done
and they're specifically waiting on the user's sign-off to
advance (relevant only under gate_on_advance = require_approval).
- Ralph suppresses nudges on this flag the same way it suppresses
on is_waiting_for_user. Covered by a new unit test.
- TrackerBoardScreen renders this state in green with a ✓ glyph
and a bold "READY TO ADVANCE — <brief>" secondary line, so the
human spots items ready for sign-off at a glance and never
confuses them with stuck-mid-stage waits (yellow "!").
- Stage protocol instructs the agent to set both
is_waiting_for_user AND awaiting_advance_approval when the
require_approval gate kicks in at stage end, and flip both
back to false on the advance.
2. brief_description should be about substance, not the stage:
- Stage column already tells the user what stage an item is in,
so "in requirements" / "doing discovery" is dead weight. The
protocol now explicitly calls out what brief_description is
for (the concrete work or blocker) with good/bad examples,
and the nudge check-in echoes the same framing.
Full suite: 673 tests pass, typecheck clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Discovery had 4 knobs (skip / depth / web_search / questions) + the common gate = 5 rows, giving 243 combinations most of which made no sense. The real axes were "how hard do I research?" and "how do I close out?". Dropped the other three into baked-in behaviours. Settings (2 rows): - effort: skim | standard | deep — research scope only (code + web). Not about interrupting the user. - report: just_advance | confirm_if_notable | always_confirm — how discovery closes out. Supersedes the common gate_on_advance for this stage because `confirm_if_notable` (the middle value) is what the two-value gate can't express. Baked-in (not tunable): - Trivial items (typo / rename / doc tweak): write one-line notes.md and advance regardless of effort. - Clarifying questions are rare and narrow: only when the problem statement itself is too vague to interpret. "Should it be X or Y?" is requirements-work, not discovery. Also compressed the stage body. Research/report steps are short mode-specific sentences; output-fields list uses minimal labels (Problem / Findings / Recommendation) instead of long prose. The generated discovery guide goes from ~650 words to ~180 — lower context cost per agent re-read, same high-entropy content. Legacy settings (skip/depth/web_search/questions) are ignored on read — existing projects just fall back to defaults rather than breaking. Stale tests updated to the new keys. Full suite: 674 tests pass, typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude has been piping the initial prompt in through `claude "prompt"` for a while, but the else-branch for codex/gemini just ran the resume command and dropped the prompt on the floor. Wire each tool's native flag into a claude-style `resume || fresh` fallback so the prompt lands regardless of whether there's a prior session to resume. - codex: `codex resume --last ... "prompt" || codex ... "prompt"` — with --last filling the [SESSION_ID] slot, the trailing positional maps to [PROMPT]. - gemini: `gemini --resume latest ... -i "prompt" || gemini ... -i "prompt"` — -i/--prompt-interactive runs the prompt then stays interactive. Plain launches (no initial prompt) are unchanged to keep the auto-resume test's expected command intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous guard required \`wt.session?.attached === true\`, which conflates "user is viewing the tmux pane" with "agent is running". A backgrounded tmux session still runs the agent, tmux capture-pane works regardless of attachment, and send-keys delivers into detached sessions fine. The authoritative "agent is running and ready" signal is \`ai_status === 'idle'\`, which getAIStatus already returns as 'not_running' whenever no tool process is detected — so plain shells and dead sessions can't slip through. Flipped the corresponding test: a backgrounded session with \`ai_status: 'idle'\` now gets nudged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dropped the duplicated header/skip-rules block, inlined the output fields as a single sentence, and tightened the research + report steps. The body goes from ~180 words to ~80 while keeping every test assertion (effort-specific keywords, Options in deep only, Trivial/Clarifying rules, report-mode phrasing) intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Requirements had 4 per-stage settings (style × 3, detail × 3, approval × 3, user_stories × 3) plus the common gate_on_advance — too many dials, and the overlap with the project-global inputMode meant users tuned the same thing in two places. Reduce to 2 knobs: - style: interview | draft_first (dropped freeform — fuzzy, rarely useful; check-in cadence comes from inputMode anyway). - detail: minimal | standard | thorough (unchanged — legitimate scope dial). Dropped as settings: - approval (per_section | end_only | none) — already covered by the project-global inputMode on the Style tab. `doc_review` ≈ end-only, `ask_questions` ≈ per-section check-ins, etc. - user_stories (skip | include | lead) — niche formatting preference; belongs in the spec body when the item is user-facing, not as a flag. Body compressed from ~400 words to ~90 (same treatment as discovery): dropped the Steps/Output/Advancing section headers, inlined the output sections as a single ordered list, and let the protocol tail handle stage advancement via status.json (so the index.json reference here became redundant — two tests that asserted it now cover implement + cleanup only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…approve Polishes a handful of UX rough edges the stages/kanban flow accumulated: **Gate labels clarify what "advance" actually does.** Replaced the common "Gate on advance / Auto-advance / Require approval" with per-stage labels that name the next step: "After requirements / Move on · Ask me first", "After implementation / …", "After cleanup and submit / …". Options stay terse (labels carry the stage context) so the settings table isn't redundant. **Cleanup stage is now "Cleanup and submit"** in all user-facing strings (kanban column, stage tab, `actionLabel`, stage-guide title). The stage key on disk (`cleanup`) and file name (`cleanup.md`) are unchanged so saved configs keep working. **Dropped the 1..5 stage-file numbering.** `stages/2-discovery.md` etc. become `stages/discovery.md`, `0-overview.md` becomes `overview.md`, and the `# Stage 2: Discovery` headings become `# Discovery`. The numbers were duplicating STAGE_ORDER and making rename/reorder painful. Removed STAGE_FILE_NUMBERS and the parallel stageFileNumber() helper. **`backlog` stage no longer gets a generated guide file.** It's been merged into discovery for display for a while; the body generator was pure tech debt. Narrowed `defaultStageFileContent` / `defaultStageFileBody` / `getStageFilePath` to `Exclude<TrackerStage, 'archive' | 'backlog'>` so the compiler blocks accidental calls. **`ItemStatus` collapses two overlapping booleans into one tri-state.** `is_waiting_for_user` + `awaiting_advance_approval` → `state: 'working' | 'waiting_for_input' | 'waiting_for_approval'`. The state is what the kanban cares about (green ✓ READY TO ADVANCE for approval, yellow ! for input); ralph treats both waiting states as nudge-suppressors. Parser still accepts the legacy boolean schema so on-disk files keep reading. **One-key approve from the kanban.** A `waiting_for_approval` card shows a "· [m] approve" hint when selected; the existing `m` key (advance to next stage) already does the right thing now that `moveItem` resets `state` to `'working'` and clears `brief_description`. Tests: 672 passing (was 675 — removed old backlog-body tests, added 3 new ones covering the tri-state enum, legacy-boolean back-compat, and waiting_for_approval nudge suppression). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up from the simplify review on the tri-state migration, plus a few UX tweaks from reviewing the board in-person: **WorktreeInfo.ralph.state** now carries the tri-state end-to-end (was re-collapsed to a boolean), so WorktreeRow in the main view can render waiting_for_input (⏸) vs waiting_for_approval (✓) distinctly instead of showing the same chip for both. **Extracted `isItemWaiting` / `isItemReadyToAdvance`** helpers on TrackerService — collapses three copies of the same "fresh + non-working state" check (RalphCore, WorktreeContext, TrackerBoardScreen). **Fixed stale `is_waiting_for_user` prose** in three live user-facing spots that still told agents to write the old boolean schema: - INPUT_MODE_LABELS (renders into `working-style.md`) - Ralph tab help text - `isItemStatusStale` + `nudgesThisStage` doc comments **Deleted the dead `COLUMN_TITLES` alias** — with "Cleanup" already spelled "Cleanup and submit" in STAGE_LABELS, the override was a pure pass-through. Board column now reads STAGE_LABELS directly. **Prominent approve hint on the kanban.** The trailing `· [m] approve` suffix was hard to spot — moved it to a dedicated `press [m] to approve and advance` line under the selected ready-to-advance card. Shortened the green banner from `READY TO ADVANCE — …` to `Ready — …`. **`brief_description` limit 120 → 200 chars.** The focused card now shows up to 4 wrapped lines (non-focused cards stay at 2) so the full description fits without truncation. Updated the protocol schema doc and the corresponding test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #216: Ralph phase automationOverviewThis PR introduces three interconnected features:
Overall the architecture is solid and follows project conventions well. Bugs / Correctness1. return {
stage: parsed.stage as Exclude<TrackerStage, 'archive'>, // ← unsafe cast
...
};Only const VALID_STAGES = new Set(['backlog','discovery','requirements','implement','cleanup']);
if (!VALID_STAGES.has(parsed.stage as string)) return null;2. Silent breakage for existing installations — stage file rename (
getStageFilePath(projectPath, stage) {
const newPath = path.join(dir, `${stage}.md`);
const oldPath = path.join(dir, `${this.STAGE_FILE_NUMBERS[stage]}-${stage}.md`);
return fs.existsSync(newPath) ? newPath : oldPath; // migrate lazily on write
}Or at minimum document in the PR that 3. Unreachable-looking but reachable branch in } else if (!this.state.worktrees[key]) {
this.state.worktrees[key] = cur;
changed = true;
}This branch fires for new worktrees where Performance4. const worktreesWithRalph = React.useMemo(() => {
const projects = gitRef.current.discoverProjects(); // ← fs scan
...
return state.worktrees.map(wt => {
...
const cfg = loadRalphConfig(projectPath); // ← fs.readFileSync per worktree
...
});
}, [state.worktrees, ralphState]);
5. const itemStatus = service.getItemStatus(projectPath, item.slug);This is a synchronous Minor Issues6. The 7. let breakAt = remaining.lastIndexOf(' ', width);
if (breakAt <= 0) breakAt = width;When 8. Missing
Test CoverageThe
Summary
The nudger logic itself is well-reasoned and the guard conditions (disabled, working, waiting, capped, archive, no session) are all correctly tested. The stage validation and migration issues are the most important to resolve before this lands. 🤖 Generated with Claude Code |
- Whitelist `stage` against STAGE_ORDER in `getItemStatus` before it flows to `getStageFilePath` — a crafted status.json with `stage: "../evil"` could have reached path.join otherwise. - Dedupe `loadRalphConfig` in `worktreesWithRalph`: was readFileSync per worktree per refresh, now one read per unique project per refresh. - Clarify the "register blank state" branch in `RalphCore.sampleOnce` with a why-comment so it doesn't read as dead code. - `buildNudgeText` had cleanup mapped to `implementation.md` as its "output" — but cleanup polishes the impl in place and drafts the PR, there's no single output file. Say "the cleanup checklist (tests, review, PR prep)" instead. - Test: verify getItemStatus rejects stages outside the enum (including `../../evil`, `archive`, unknown, empty). Not addressed (out of scope / pre-existing): - `getItemStatus` readFileSync in kanban render loop — pre-existing hot path, separate project to cache tracker status at the item level. - Stage-file rename back-compat (old `2-discovery.md` files sit orphaned). `ensureStageFiles` regenerates the new-named file on first access, which is the robust path; an explicit migration isn't worth the complexity. - `wrapToLines` single-wide-word edge case — pre-existing utility. - `getItemStage` ignoring staleness for stage derivation — intentional (stage doesn't auto-revert on crash; only the waiting signal does). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #216: ralph phase automationOverviewThis PR ships three interlocking pieces: (1) an agent-owned Code Quality and ArchitecturePositives
Potential Bugs1. Dead guard in The second stage-change check near the end of if (prev.lastStage !== null && prev.lastStage !== stage) return next;If the stage changed, 2. const ralphRef = React.useRef<RalphCore | null>(null);
if (!ralphRef.current) {
ralphRef.current = new RalphCore({...}); // allocates services on every render
}New service instances ( const ralphRef = React.useRef(new RalphCore({
tracker: new TrackerService(),
tmux: new TmuxService(),
...
}));3. The comment says "up to 4 rows: slug + 2 secondary lines + marginBottom" and Performance Considerations4. Synchronous file reads in
const projects = gitRef.current.discoverProjects(); // filesystem scan
const status = trackerRef.current.getItemStatus(projectPath, wt.feature); // readFileSync per worktreeWith 10 worktrees this is 10+ synchronous I/O calls every 2 seconds, all on the render thread. Consider moving these reads into 5. Multiple worktrees in the same project re-read Breaking Change — Stage File RenamingThe rename from Test CoverageStrengths
Gaps
Minor / Style
SummaryThe core design (self-reported
Test coverage is strong; closing the UI chip test and the |
- Remove the unreachable stage-change guard near the bottom of `sampleWorktree` — a stage change already zeroes `idleSince`, which the earlier `idleSince` guard catches, so the second check was dead code. - Dedupe `loadRalphConfig` in `sampleOnce`: N worktrees in the same project were causing N readFileSync calls per 60s tick. Cache per project. Skipped (not actually bugs / out of scope): - `ralphRef` init — guarded lazy init is correct and cheaper than `useRef(new X())` (the latter also evaluates once per render, just discards the result). - Selected-card row overflow vs `ROWS_PER_ITEM = 4`: overflow happens for one card and is acceptable UX for the "show the full 200-char description" feature. Cards below shift by at most a row. - Sync `getItemStatus` / `discoverProjects` in the kanban render memo — pre-existing, belongs in its own project to cache tracker state at the item level. - Stage file renaming "migration": `ensureStageFiles` only writes when the new path is missing, so user edits to orphaned numbered files survive on disk (they're just unreferenced). No data loss. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #216: ralph phase automationOverviewThis is a substantial, well-structured PR. The on-disk Bugs / Correctness Issues
The method says "Also surface slugs that have a status.json but aren't in the index yet", but the loop only iterates // current — only overrides index-known slugs; never adds new ones
for (const slug of knownSlugs) {
const status = this.getItemStatus(projectPath, slug);
if (status) out.set(slug, status.stage);
}
Performance ConcernsSync filesystem I/O inside
The config-dedup pattern ( Code Quality / Clarity
Increasing the slot size means all cards reserve 4 rows even when their secondary text fits in one. The focused-card expansion ( Stage file renaming has no automatic migration Dropping the numeric prefix ( SecurityThe path-traversal guard in Test CoverageThe 464-line
SummaryThe core design is sound and the tri-state nudge protocol is a genuine improvement. The two correctness issues ( |
- `listItemsByStage`: the "also surface slugs absent from the index" clause in the comment was never actually implemented — the loop only iterates index-known slugs. Rewrote the comment to describe what the code does; callers needing orphan-detection can scan the items dir. - `getItemStage`: added a comment explaining why the staleness guard (used elsewhere for ralph suppression) deliberately doesn't apply here. The last stage the agent wrote is still the correct stage even if the process later crashed — nothing auto-reverts, so no staleness check is appropriate. Skipping the sync-I/O-in-useMemo concern (pre-existing, separate project) and the ROWS_PER_ITEM=4 interaction with the 4-line selected card (acceptable UX trade-off for showing the full 200-char brief_description). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #216: Ralph phase automation + status.json + stage UX polishOverviewThis is a substantial, well-scoped PR that adds three related features: (1) an on-disk agent self-reporting contract ( ✅ Strengths
🔴 Issues1. In 2. const ralphRef = React.useRef<RalphCore | null>(null);
if (!ralphRef.current) {
ralphRef.current = new RalphCore({ ... });
}The 3. Word-wrapping logic is genuinely reusable (e.g. anywhere a 4. Increasing the constant from 3 → 4 means all existing kanban columns render shorter (one fewer item visible). The 5.
🟡 Minor / Style
🔒 Security / SafetyNo security concerns — this feature writes to local project files only and delivers text to tmux panes that the user already owns. The nudge text is constructed from trusted project config (not user-facing strings from external sources), so there is no injection risk. 🧪 Test CoverageThe PR description mentions 672 passing tests with new coverage for:
This is solid. Gaps to consider:
SummaryThe core design is sound and the PR is well-tested for its stated scope. The main issues to address before merge are the disk I/O on the React render path (issues #1 and #5) and the ROWS_PER_ITEM scroll math regression (issue #4). The others are minor polish items. |
Summary
Adds a ralph-style "is the agent stuck?" nudger for tracker items, plus the on-disk contract that powers it, plus a round of UX polish on the stages/kanban flow that made the new signals more useful.
The agent self-reports a live status. Each tracker item gets a
tracker/items/<slug>/status.jsonwith three fields that matter:stage,state: 'working' | 'waiting_for_input' | 'waiting_for_approval', andbrief_description(≤ 200 chars). The stage guide tells the agent exactly when to flip state; the kanban reads it to render useful glyphs; ralph reads it to decide whether the agent is stuck or legitimately waiting.Ralph samples every 60s and nudges when the agent is running but idle (
ai_status === 'idle'≥idleThresholdMs, default 3 min), hasn't advanced stages, and hasn't self-reported a non-working state. Caps atmaxNudgesPerStage(default 3) to avoid spam; resets on any stage change or fresh waiting flag. All guards covered by unit tests.Kanban + main-view surfaces the state. Waiting_for_input cards get a yellow
!with the agent'sbrief_description; waiting_for_approval cards get a green✓ Ready — <brief>banner and a dedicated "press [m] to approve and advance" hint when highlighted. The existingmkey now does the approve+advance in one keystroke (resets state to working, clears brief, advances stage).Stage UX cleanup bundled in since the new signals exposed friction:
cleanup) and filename (cleanup.md) are unchanged so saved configs keep working.stages/2-discovery.mdetc. becamestages/discovery.md,# Stage 2: Discoveryheadings became# Discovery.approvalflags.Back-compat.
normaliseItemStatereads legacyis_waiting_for_user/awaiting_advance_approvalbooleans and maps them to the new enum, so existing status.json files on disk keep working.Test plan
npx tsc --noEmit— cleannpx jest— 672 passing (new: status.json helpers, tri-state parse + legacy back-compat, ralph guard invariants including waiting_for_approval suppression, nudge text polite-check-in assertions, stage-guide generation per setting, moveItem resets state on advance)🤖 Generated with Claude Code