rafcode 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/RAF/ahrren-turbo-finder/decisions.md +19 -0
  3. package/RAF/ahrren-turbo-finder/input.md +2 -0
  4. package/RAF/ahrren-turbo-finder/outcomes/01-worktree-auto-detect.md +40 -0
  5. package/RAF/ahrren-turbo-finder/outcomes/02-medium-effort-do.md +34 -0
  6. package/RAF/ahrren-turbo-finder/plans/01-worktree-auto-detect.md +44 -0
  7. package/RAF/ahrren-turbo-finder/plans/02-medium-effort-do.md +39 -0
  8. package/RAF/ahrtxf-session-sentinel/decisions.md +19 -0
  9. package/RAF/ahrtxf-session-sentinel/input.md +1 -0
  10. package/RAF/ahrtxf-session-sentinel/outcomes/01-capture-session-id.md +37 -0
  11. package/RAF/ahrtxf-session-sentinel/outcomes/02-resume-flag.md +45 -0
  12. package/RAF/ahrtxf-session-sentinel/plans/01-capture-session-id.md +41 -0
  13. package/RAF/ahrtxf-session-sentinel/plans/02-resume-flag.md +51 -0
  14. package/dist/commands/do.d.ts.map +1 -1
  15. package/dist/commands/do.js +61 -20
  16. package/dist/commands/do.js.map +1 -1
  17. package/dist/core/claude-runner.d.ts +19 -0
  18. package/dist/core/claude-runner.d.ts.map +1 -1
  19. package/dist/core/claude-runner.js +199 -29
  20. package/dist/core/claude-runner.js.map +1 -1
  21. package/dist/core/shutdown-handler.d.ts.map +1 -1
  22. package/dist/core/shutdown-handler.js +4 -0
  23. package/dist/core/shutdown-handler.js.map +1 -1
  24. package/dist/core/worktree.d.ts +18 -0
  25. package/dist/core/worktree.d.ts.map +1 -1
  26. package/dist/core/worktree.js +61 -0
  27. package/dist/core/worktree.js.map +1 -1
  28. package/dist/parsers/stream-renderer.d.ts +3 -0
  29. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  30. package/dist/parsers/stream-renderer.js +1 -1
  31. package/dist/parsers/stream-renderer.js.map +1 -1
  32. package/dist/types/config.d.ts +1 -0
  33. package/dist/types/config.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/commands/do.ts +67 -21
  36. package/src/core/claude-runner.ts +244 -31
  37. package/src/core/shutdown-handler.ts +5 -0
  38. package/src/core/worktree.ts +77 -0
  39. package/src/parsers/stream-renderer.ts +4 -1
  40. package/src/types/config.ts +1 -0
  41. package/tests/unit/claude-runner-interactive.test.ts +24 -0
  42. package/tests/unit/claude-runner.test.ts +509 -55
  43. package/tests/unit/post-execution-picker.test.ts +1 -0
  44. package/tests/unit/stream-renderer.test.ts +30 -0
  45. package/tests/unit/worktree.test.ts +102 -0
@@ -26,7 +26,9 @@
26
26
  "Bash(EDITOR='cp /dev/stdin' node dist/index.js plan testproj 2)",
27
27
  "Bash(1 <<'EOF'\nThis is a test project description\nEOF)",
28
28
  "Bash(git stash:*)",
29
- "Bash(git checkout:*)"
29
+ "Bash(git checkout:*)",
30
+ "WebFetch(domain:docs.anthropic.com)",
31
+ "WebFetch(domain:news.ycombinator.com)"
30
32
  ]
31
33
  }
32
34
  }
@@ -0,0 +1,19 @@
1
+ # Project Decisions
2
+
3
+ ## For the worktree resolution fix: should `raf do turbo-finder` (no --worktree flag) automatically detect and use the worktree project? Or should it still require the --worktree flag but just fix name/ID matching within that flag?
4
+ Auto-detect worktrees — `raf do <name>` searches worktrees automatically if not found in main repo, then auto-enables worktree mode.
5
+
6
+ ## For reducing reasoning: which commands should use low reasoning effort?
7
+ Only do — Use low reasoning only for task execution (`raf do`), not for planning.
8
+
9
+ ## For worktree auto-detection: when `raf do <name>` finds the project in a worktree, should it also trigger the post-execution action picker (merge/PR/leave) like the --worktree flag does?
10
+ Yes, full worktree flow — Auto-detected worktree projects get the same post-action picker as explicitly --worktree projects.
11
+
12
+ ## For the reasoning effort on `raf do`: implementation approach?
13
+ Use env var approach — Set `CLAUDE_CODE_EFFORT_LEVEL=medium` environment variable when spawning Claude processes for task execution.
14
+
15
+ ## What effort level for `raf do`?
16
+ Medium — Use `CLAUDE_CODE_EFFORT_LEVEL=medium` (not low).
17
+
18
+ ## Given the env var UI bug (GitHub #23604), how should RAF set effort?
19
+ Use env var anyway — `CLAUDE_CODE_EFFORT_LEVEL=medium` likely works at the API level despite the `/model` UI not reflecting it. Simplest approach.
@@ -0,0 +1,2 @@
1
+ - [ ] fix: i can't do "raf do <project name without id>" it the project in the worktree. make sure it works just for project ids as well. rely on naming convention of worktree folder (same a project)
2
+ - [ ] reduce opus reasoning to low for plan do. search for docs on how to do that
@@ -0,0 +1,40 @@
1
+ # Outcome: Auto-detect worktree projects in `raf do`
2
+
3
+ ## Summary
4
+
5
+ Implemented automatic worktree project detection in `raf do <identifier>` so that the `--worktree` flag is no longer required when running worktree projects by name, ID, or full folder name.
6
+
7
+ ## Key Changes
8
+
9
+ ### `src/core/worktree.ts`
10
+ - Added `resolveWorktreeProjectByIdentifier()` function that matches a project identifier against worktree folder names using the same resolution strategy as `resolveProjectIdentifierWithDetails`:
11
+ 1. Full folder name match (exact, case-insensitive)
12
+ 2. Base26 prefix match (6-char ID)
13
+ 3. Project name match (portion after prefix)
14
+ - Added `WorktreeProjectResolution` interface for the return type
15
+ - Added import of `extractProjectNumber`, `extractProjectName`, `isBase26Prefix`, `decodeBase26` from paths utility
16
+
17
+ ### `src/commands/do.ts`
18
+ - Modified the standard (non-worktree) resolution path to check worktrees FIRST, then fall back to main repo
19
+ - When a worktree match is found, auto-enables worktree mode: sets `worktreeMode = true`, `worktreeRoot`, `originalBranch`
20
+ - This triggers the full worktree flow: post-execution action picker, worktree cwd, and cleanup
21
+ - Changed `resolvedProject` type to `| undefined` to support the two-step resolution (worktree then main)
22
+ - Added import for `resolveWorktreeProjectByIdentifier`
23
+
24
+ ### `tests/unit/worktree.test.ts`
25
+ - Added 11 test cases for `resolveWorktreeProjectByIdentifier`:
26
+ - Full folder name match (exact and case-insensitive)
27
+ - Base26 prefix match
28
+ - Project name match (case-insensitive)
29
+ - No match / no worktree projects
30
+ - Ambiguous name match returns null
31
+ - Correct worktreeRoot path
32
+
33
+ ### `tests/unit/post-execution-picker.test.ts`
34
+ - Added `resolveWorktreeProjectByIdentifier` to the worktree module mock to prevent import failure
35
+
36
+ ## Test Results
37
+
38
+ All 45 test suites pass (971 tests total, 0 failures).
39
+
40
+ <promise>COMPLETE</promise>
@@ -0,0 +1,34 @@
1
+ # Outcome: Set medium reasoning effort for `raf do` execution
2
+
3
+ ## Summary
4
+
5
+ Configured `raf do` to spawn Claude CLI processes with `CLAUDE_CODE_EFFORT_LEVEL=medium` environment variable, reducing reasoning overhead during automated task execution while preserving default (high) effort for interactive planning sessions.
6
+
7
+ ## Key Changes
8
+
9
+ ### `src/core/claude-runner.ts`
10
+ - Added optional `effortLevel` field (`'low' | 'medium' | 'high'`) to `ClaudeRunnerOptions` interface
11
+ - In `run()`: when `effortLevel` is set, spreads `process.env` with `CLAUDE_CODE_EFFORT_LEVEL` override; otherwise passes `process.env` directly
12
+ - In `runVerbose()`: same env injection logic as `run()`
13
+ - `runInteractive()` is unchanged — uses `process.env` as-is (no effort override for planning sessions)
14
+
15
+ ### `src/commands/do.ts`
16
+ - Passes `effortLevel: 'medium'` in both `claudeRunner.run()` and `claudeRunner.runVerbose()` calls during task execution
17
+
18
+ ### `tests/unit/claude-runner.test.ts`
19
+ - Added 6 tests in new `effort level` describe block:
20
+ - Verifies `CLAUDE_CODE_EFFORT_LEVEL` is set in env for `run()` when effortLevel provided
21
+ - Verifies `CLAUDE_CODE_EFFORT_LEVEL` is set in env for `runVerbose()` when effortLevel provided
22
+ - Verifies env is `process.env` directly (no override) when effortLevel not provided in `run()`
23
+ - Verifies env is `process.env` directly (no override) when effortLevel not provided in `runVerbose()`
24
+ - Verifies all three levels (low, medium, high) work correctly
25
+ - Verifies other env vars (e.g., PATH) are preserved when effortLevel is set
26
+
27
+ ### `tests/unit/claude-runner-interactive.test.ts`
28
+ - Added 1 test verifying `runInteractive()` does NOT set `CLAUDE_CODE_EFFORT_LEVEL` in its env
29
+
30
+ ## Test Results
31
+
32
+ All 45 test suites pass (978 tests total, 0 failures).
33
+
34
+ <promise>COMPLETE</promise>
@@ -0,0 +1,44 @@
1
+ # Task: Auto-detect worktree projects in `raf do`
2
+
3
+ ## Objective
4
+ Make `raf do <name>` and `raf do <id>` automatically find and run worktree projects without requiring the `--worktree` flag.
5
+
6
+ ## Context
7
+ Currently, `raf do <identifier>` without `--worktree` only searches the main repo's RAF directory. If a project lives exclusively in a worktree (or the user simply omits the flag), the command fails with "project not found". The worktree folder name matches the project folder name (e.g., `~/.raf/worktrees/<repo>/ahrren-turbo-finder/`), so resolution can leverage this naming convention.
8
+
9
+ When a worktree project is auto-detected, the full worktree flow should activate — including the post-execution action picker (merge/PR/leave), worktree cwd, and cleanup behavior — exactly as if `--worktree` had been passed.
10
+
11
+ ## Requirements
12
+ - When `raf do <name>` (e.g., `turbo-finder`) doesn't find the project in the main repo, fall back to searching worktree directories
13
+ - When `raf do <id>` (e.g., `ahrren`) doesn't find the project in the main repo, fall back to searching worktree directories
14
+ - When `raf do <full-folder>` (e.g., `ahrren-turbo-finder`) doesn't find the project in the main repo, fall back to searching worktree directories
15
+ - Use the worktree folder naming convention: worktree folders at `~/.raf/worktrees/<repo-basename>/<project-folder>/` where `<project-folder>` matches the project folder name format (`XXXXXX-name`)
16
+ - Match identifiers against worktree folder names using the same resolution logic as `resolveProjectIdentifierWithDetails` (full name, base26 ID, or project name)
17
+ - When a worktree project is auto-detected, enable the full worktree mode: set `worktreeMode = true`, `worktreeRoot`, `originalBranch`, and trigger the post-execution action picker
18
+ - If project exists in both main repo and worktree, prefer the worktree version (consistent with existing picker deduplication behavior)
19
+
20
+ ## Implementation Steps
21
+ 1. In `runDoCommand()` in `src/commands/do.ts`, modify the non-worktree resolution path (currently around line 305) to add a worktree fallback
22
+ 2. After `resolveProjectIdentifierWithDetails(rafDir, projectIdentifier)` fails (returns no path), check if the current directory is a git repo
23
+ 3. If in a git repo, use `listWorktreeProjects(repoBasename)` to get worktree project folders
24
+ 4. For each worktree folder, attempt to match the identifier using the same resolution strategy: full folder name match, base26 prefix match, or name-portion match
25
+ 5. If a match is found, set `worktreeMode = true`, compute `worktreeRoot`, record `originalBranch`, and proceed through the existing worktree validation and execution flow
26
+ 6. Consider also checking worktrees FIRST (before main repo) or in parallel, to match the picker behavior where worktree versions take priority over main repo versions
27
+ 7. Add unit tests for the new auto-detection logic
28
+ 8. Add integration-level tests verifying the full flow (identifier → worktree detection → worktree mode enabled)
29
+
30
+ ## Acceptance Criteria
31
+ - [ ] `raf do turbo-finder` finds and runs a worktree project named `ahrren-turbo-finder`
32
+ - [ ] `raf do ahrren` finds and runs a worktree project with ID `ahrren`
33
+ - [ ] `raf do ahrren-turbo-finder` finds and runs the worktree project by full folder name
34
+ - [ ] Auto-detected worktree projects trigger the post-execution action picker (merge/PR/leave)
35
+ - [ ] Auto-detected worktree projects execute with the worktree as cwd
36
+ - [ ] If project exists in both main and worktree, worktree version is preferred
37
+ - [ ] Existing `--worktree` flag behavior is unchanged
38
+ - [ ] All existing tests pass
39
+ - [ ] New tests cover the auto-detection scenarios
40
+
41
+ ## Notes
42
+ - The existing worktree resolution code (lines 240-304 in `do.ts`) already handles the case where `--worktree` is passed. The new code should reuse as much of that logic as possible rather than duplicating it.
43
+ - `listWorktreeProjects()` from `src/core/worktree.ts` returns sorted folder names. Resolution against these can use string matching without needing filesystem reads into each worktree's RAF dir.
44
+ - Be careful with the `projectFolderName` variable scoping — it's currently declared inside the worktree block and needs to be accessible when auto-detection sets worktree mode.
@@ -0,0 +1,39 @@
1
+ # Task: Set medium reasoning effort for `raf do` execution
2
+
3
+ ## Objective
4
+ Configure `raf do` to spawn Claude CLI processes with `CLAUDE_CODE_EFFORT_LEVEL=medium` to reduce reasoning overhead during task execution.
5
+
6
+ ## Dependencies
7
+ 01
8
+
9
+ ## Context
10
+ By default, Claude Opus uses "high" effort level which allocates more thinking tokens. For automated task execution via `raf do`, medium effort provides a good balance of speed, cost, and capability. The `CLAUDE_CODE_EFFORT_LEVEL` environment variable is the supported mechanism for controlling this in the Claude Code CLI.
11
+
12
+ RAF's `ClaudeRunner` already passes `process.env` to spawned processes (lines 296, 400, 512 in `claude-runner.ts`). The change needs to inject `CLAUDE_CODE_EFFORT_LEVEL=medium` into the environment for non-interactive (task execution) runs, while leaving interactive planning sessions (`raf plan`) at default effort.
13
+
14
+ ## Requirements
15
+ - Set `CLAUDE_CODE_EFFORT_LEVEL=medium` in the environment when spawning Claude processes for `raf do` task execution
16
+ - Do NOT affect `raf plan` (interactive planning sessions should keep default/high effort)
17
+ - Do NOT affect failure analysis (which uses Haiku via `ClaudeRunner`)
18
+ - The effort level should be configurable through `ClaudeRunnerOptions` or `ClaudeRunnerConfig` so it's not hardcoded deep in the runner
19
+ - Pass the env var by spreading it into the `env` object passed to `spawn()` and `pty.spawn()`
20
+
21
+ ## Implementation Steps
22
+ 1. Add an optional `effortLevel` field to `ClaudeRunnerOptions` or `ClaudeRunnerConfig` in `src/core/claude-runner.ts`
23
+ 2. In the `run()` and `runVerbose()` methods, merge `CLAUDE_CODE_EFFORT_LEVEL` into the environment when `effortLevel` is set
24
+ 3. In `src/commands/do.ts`, pass `effortLevel: 'medium'` when constructing or calling `ClaudeRunner` for task execution
25
+ 4. Ensure the interactive `runInteractive()` method does NOT apply the effort override (planning should stay at default)
26
+ 5. Add tests verifying the env var is passed correctly
27
+ 6. Add tests verifying that planning mode does not get the env var
28
+
29
+ ## Acceptance Criteria
30
+ - [ ] `raf do` spawns Claude with `CLAUDE_CODE_EFFORT_LEVEL=medium` in the environment
31
+ - [ ] `raf plan` does NOT set `CLAUDE_CODE_EFFORT_LEVEL` (uses default behavior)
32
+ - [ ] The effort level is configurable (not hardcoded in the spawn call)
33
+ - [ ] Existing tests pass
34
+ - [ ] New tests verify the env var injection
35
+
36
+ ## Notes
37
+ - The three spawn points in `claude-runner.ts` are: `pty.spawn()` at line 291 (interactive), `spawn()` at line 390 (non-interactive), and `spawn()` at line 499 (verbose). Only the non-interactive and verbose spawns should get the effort override.
38
+ - The env var `CLAUDE_CODE_EFFORT_LEVEL` is confirmed as the correct name per official docs at code.claude.com/docs/en/model-config. Note: there is a known UI bug (GitHub issue #23604) where the `/model` UI always shows "High effort" regardless of the env var, but the env var is believed to still affect actual API requests.
39
+ - An alternative approach (if the env var turns out to be truly broken) would be to write `"effortLevel": "medium"` to a `.claude/settings.json` in the working directory before spawning Claude. This is a fallback, not the primary approach.
@@ -0,0 +1,19 @@
1
+ # Project Decisions
2
+
3
+ ## What's the main use case for logging the session ID?
4
+ Resume interrupted sessions. Capture session ID so RAF can attempt to resume interrupted Claude sessions using `claude --resume <id>`, and also allow manual inspection. Add `raf do <project> --resume <session-id>` flag for resuming after Ctrl+C interruption.
5
+
6
+ ## Should the session ID be captured in all execution modes?
7
+ Verbose + non-interactive. Both modes that run tasks should capture session ID. Interactive planning mode can be skipped.
8
+
9
+ ## When a session is interrupted, should the session ID be displayed to the user?
10
+ Print to terminal. Display session ID in terminal output on interruption so user can copy it for `--resume`.
11
+
12
+ ## For `raf do --resume`, should it resume the exact interrupted task or restart from scratch?
13
+ Resume exact task. Pass `--resume` to Claude CLI for the specific interrupted task, continuing from where Claude left off mid-task. Add metadata (task ID) to support this. Format could be `--resume <task-id>:<session-id>` or assume it's the last unfinished task.
14
+
15
+ ## How should non-interactive mode get access to the session ID?
16
+ Always use `--output-format stream-json` for both verbose and non-interactive modes. Parse the init event to capture session_id. Only render/display the stream output when `--verbose` flag is passed. This gives us session IDs universally without changing user-visible behavior.
17
+
18
+ ## When resuming, should RAF pass the original task's system prompt again?
19
+ Rely on session state. Trust that Claude's `--resume` restores the full context including the original prompt. Don't re-send system prompt or task context.
@@ -0,0 +1 @@
1
+ check if it's possible to log claude session id if session got interrupted
@@ -0,0 +1,37 @@
1
+ # Outcome: Capture Session ID from Claude CLI Output
2
+
3
+ ## Summary
4
+
5
+ Implemented session ID extraction from Claude CLI's `system.init` NDJSON event in both `run()` and `runVerbose()` methods. The session ID is now captured, returned in `RunResult`, and printed to the terminal on interruption.
6
+
7
+ ## Key Changes
8
+
9
+ ### `src/parsers/stream-renderer.ts`
10
+ - Added `session_id` field to `StreamEvent` interface
11
+ - Added `sessionId` field to `RenderResult` interface
12
+ - Modified `renderStreamEvent()` to extract and return `session_id` from system init events
13
+
14
+ ### `src/core/claude-runner.ts`
15
+ - Added `sessionId?: string` field to `RunResult` interface
16
+ - Added `_sessionId` private field and public `sessionId` getter to `ClaudeRunner` class
17
+ - Refactored `run()` to use `--output-format stream-json --verbose` with silent NDJSON parsing (no stdout display), enabling session ID extraction
18
+ - Updated `runVerbose()` to capture `sessionId` from stream events
19
+ - Both methods return `sessionId` in `RunResult`
20
+ - Session ID is printed via `logger.info()` on timeout and context overflow in both methods
21
+
22
+ ### `src/core/shutdown-handler.ts`
23
+ - Added session ID logging in `handleShutdown()` — prints `Session ID: <id>` when a Claude session is interrupted via Ctrl+C/SIGTERM
24
+
25
+ ### `tests/unit/stream-renderer.test.ts`
26
+ - Added 3 new tests: session_id extraction, undefined for missing session_id, undefined for non-system events
27
+
28
+ ### `tests/unit/claude-runner.test.ts`
29
+ - Updated existing `run()` tests to emit NDJSON events (since `run()` now uses stream-json format)
30
+ - Updated flag assertion: `run()` now includes `--output-format stream-json --verbose`
31
+ - Added 5 new tests: sessionId extraction in run(), runVerbose(), undefined when missing, getter exposure, deduplication
32
+
33
+ ## Test Results
34
+
35
+ All 986 tests pass (45 test suites). No regressions introduced.
36
+
37
+ <promise>COMPLETE</promise>
@@ -0,0 +1,45 @@
1
+ # Outcome: Add --resume Flag to raf do Command
2
+
3
+ ## Summary
4
+
5
+ Added `--resume <session-id>` option to `raf do` that resumes an interrupted Claude session for a specific task. When used, Claude is spawned with `--resume` flag only (no prompt/model/system-prompt flags), and completion monitoring works identically to normal execution.
6
+
7
+ ## Key Changes
8
+
9
+ ### `src/types/config.ts`
10
+ - Added `resume?: string` field to `DoCommandOptions` interface
11
+
12
+ ### `src/commands/do.ts`
13
+ - Added `-r, --resume <session-id>` option to the `do` command definition
14
+ - Added `resumeSessionId` to `SingleProjectOptions` interface
15
+ - In the task execution loop: when `activeResumeSessionId` is set, calls `runResume()` instead of `run()`/`runVerbose()` for the first attempt
16
+ - Clears `activeResumeSessionId` after the first task completes, so subsequent tasks use normal execution
17
+ - Prints clear user-facing message: `Resuming task <id> with session <session-id>` in both verbose and minimal modes
18
+
19
+ ### `src/core/claude-runner.ts`
20
+ - Added `runResume(sessionId, options)` method that spawns Claude with:
21
+ - `--resume <session-id>` — restores the interrupted session
22
+ - `--dangerously-skip-permissions` — required for non-interactive operation
23
+ - `--output-format stream-json --verbose` — enables NDJSON event parsing
24
+ - Does NOT pass `--model`, `--append-system-prompt`, or `-p` (Claude restores these from session state)
25
+ - Same completion detection, timeout handling, context overflow detection, and session ID extraction as existing methods
26
+
27
+ ### `tests/unit/claude-runner.test.ts`
28
+ - Added 11 new tests in `runResume()` describe block:
29
+ - Spawns with `--resume` flag and session ID
30
+ - Does NOT include `--model`, `--append-system-prompt`, or `-p` flags
31
+ - Includes `--dangerously-skip-permissions` flag
32
+ - Includes `--output-format stream-json` and `--verbose` flags
33
+ - Collects output from NDJSON events
34
+ - Handles timeout correctly
35
+ - Detects completion markers
36
+ - Extracts session ID from resumed session
37
+ - Passes cwd to spawn (worktree support)
38
+ - Detects context overflow
39
+ - Sets CLAUDE_CODE_EFFORT_LEVEL env var when provided
40
+
41
+ ## Test Results
42
+
43
+ All 997 tests pass (45 test suites). No regressions introduced.
44
+
45
+ <promise>COMPLETE</promise>
@@ -0,0 +1,41 @@
1
+ # Task: Capture Session ID from Claude CLI Output
2
+
3
+ ## Objective
4
+ Extract and store the Claude session ID from stream-json output in both verbose and non-interactive execution modes.
5
+
6
+ ## Context
7
+ Claude CLI emits a `session_id` in its `system.init` NDJSON event when using `--output-format stream-json`. RAF currently discards this event entirely. We need to capture and surface this ID so it can be used for session resumption and debugging.
8
+
9
+ ## Requirements
10
+ - Unify both `run()` (non-interactive) and `runVerbose()` methods to use `--output-format stream-json` so that the system init event is always available
11
+ - In non-verbose mode, parse NDJSON events silently (extract textContent for output accumulation and detect completion markers) without rendering anything to stdout
12
+ - In verbose mode, continue rendering stream events to stdout as before
13
+ - Extract `session_id` from the `system.init` event (first event in the stream)
14
+ - Add `sessionId?: string` field to the `RunResult` type returned by both `run()` and `runVerbose()`
15
+ - Modify `renderStreamEvent()` in `stream-renderer.ts` to return the session_id when it encounters a system init event, rather than discarding it
16
+ - When a session is interrupted (Ctrl+C, timeout, context overflow), print the session ID to terminal: `Session ID: <id>` so the user can copy it
17
+ - Add tests for the session ID extraction from system init events
18
+ - Add tests verifying session ID is included in RunResult
19
+
20
+ ## Implementation Steps
21
+ 1. Update the `RunResult` type to include an optional `sessionId` field
22
+ 2. Modify `renderStreamEvent()` to extract and return `session_id` from system init events (add a new field to the return type, e.g. `sessionId?: string`)
23
+ 3. Refactor `run()` to use `--output-format stream-json` internally, parsing NDJSON lines the same way `runVerbose()` does, but without writing display output to stdout
24
+ 4. In both `run()` and `runVerbose()`, capture the session_id from the first system init event and include it in the returned `RunResult`
25
+ 5. In the shutdown handler and timeout/overflow paths, print the captured session ID to the terminal before exiting
26
+ 6. Write unit tests for session ID extraction from stream events
27
+ 7. Write integration-style tests verifying RunResult includes sessionId
28
+
29
+ ## Acceptance Criteria
30
+ - [ ] `run()` uses stream-json format internally and parses events silently
31
+ - [ ] `runVerbose()` continues to display stream events as before
32
+ - [ ] Both methods return `sessionId` in `RunResult` when available
33
+ - [ ] On interruption (Ctrl+C, timeout, overflow), session ID is printed to terminal
34
+ - [ ] Existing tests continue to pass
35
+ - [ ] New tests cover session ID extraction
36
+
37
+ ## Notes
38
+ - The system init event format is: `{ type: 'system', subtype: 'init', session_id: string, tools: string[], model: string }`
39
+ - Currently `stream-renderer.ts` line 96 returns `{ display: '', textContent: '' }` for all system events — this is where extraction should happen
40
+ - The `run()` method currently uses plain text output with `child_process.spawn` — it needs to switch to stream-json parsing similar to `runVerbose()`. Consider extracting shared NDJSON parsing logic to avoid duplication.
41
+ - Be careful: `run()` has its own completion marker detection and timeout logic that must continue to work with the new stream-json parsing
@@ -0,0 +1,51 @@
1
+ # Task: Add --resume Flag to raf do Command
2
+
3
+ ## Objective
4
+ Add a `--resume` option to `raf do` that resumes an interrupted Claude session for a specific task.
5
+
6
+ ## Context
7
+ After task 01 captures the session ID and displays it on interruption, users need a way to actually resume the interrupted session. This task adds `raf do <project> --resume <session-id>` which passes Claude CLI's `--resume` flag to continue a session from where it left off.
8
+
9
+ ## Dependencies
10
+ 01
11
+
12
+ ## Requirements
13
+ - Add `--resume <session-id>` option to the `raf do` command in Commander.js
14
+ - When `--resume` is provided, RAF should:
15
+ 1. Identify the task to resume — find the first task that is in-progress (has no outcome file, or has a partial/missing completion marker)
16
+ 2. Skip the normal task execution flow and instead spawn Claude with `--resume <session-id>` flag
17
+ 3. Do NOT pass `-p` (prompt), `--append-system-prompt`, or `--model` flags — rely on Claude's session state to restore these
18
+ 4. DO pass `--dangerously-skip-permissions` as it's required for non-interactive operation
19
+ 5. Still use `--output-format stream-json` so we can capture the new session's events and detect completion
20
+ 6. Continue monitoring for completion markers and outcome file as usual
21
+ - After the resumed session completes, normal post-task flow should apply (outcome validation, commit verification, next task, etc.)
22
+ - If `--resume` is used with `--worktree`, ensure the CWD is set to the worktree path
23
+ - Print a clear message indicating which task is being resumed: `Resuming task <id> with session <session-id>`
24
+ - Add a new method to `ClaudeRunner` (e.g. `runResume(sessionId, options)`) that spawns Claude with the `--resume` flag
25
+ - Cover the new flag and resume method with tests
26
+
27
+ ## Implementation Steps
28
+ 1. Add `--resume <session-id>` option to the `do` command definition in `src/commands/do.ts`
29
+ 2. Add a `runResume()` method to `ClaudeRunner` that spawns Claude with `--resume <session-id> --dangerously-skip-permissions --output-format stream-json` and the same completion monitoring as existing methods
30
+ 3. In the task execution loop, when `--resume` is provided:
31
+ - Determine the current task (first task without a valid outcome)
32
+ - Call `runResume()` instead of the normal `run()`/`runVerbose()` method
33
+ - After the resumed task completes, clear the `--resume` flag so subsequent tasks use normal execution
34
+ 4. Handle edge cases: invalid session ID format, all tasks already complete, resumed session fails
35
+ 5. Write tests for the new `runResume()` method
36
+ 6. Write tests for the `--resume` flag integration in the do command
37
+
38
+ ## Acceptance Criteria
39
+ - [ ] `raf do <project> --resume <session-id>` resumes the interrupted session
40
+ - [ ] Claude is spawned with `--resume` flag and without prompt/model/system-prompt flags
41
+ - [ ] Completion monitoring works the same as normal execution
42
+ - [ ] After resumed task completes, subsequent tasks run normally
43
+ - [ ] Works with `--worktree` mode
44
+ - [ ] Clear user-facing message on resume start
45
+ - [ ] Tests cover resume path
46
+
47
+ ## Notes
48
+ - Claude CLI's `--resume` flag restores the full session context including the system prompt, so we must NOT pass those flags again (they may conflict or be rejected)
49
+ - The `--resume` flag only applies to a single task — after it completes (or fails), remaining tasks use normal execution
50
+ - Consider what happens if the user provides a session ID for a task that already completed — should gracefully handle this
51
+ - Check Claude CLI docs/help to verify exact `--resume` flag syntax: `claude --resume <session-id>`
@@ -1 +1 @@
1
- {"version":3,"file":"do.d.ts","sourceRoot":"","sources":["../../src/commands/do.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgDpC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1D,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,GACf,MAAM,CAkBR;AA0BD,wBAAgB,eAAe,IAAI,OAAO,CAiBzC;AA0QD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuBhG"}
1
+ {"version":3,"file":"do.d.ts","sourceRoot":"","sources":["../../src/commands/do.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiDpC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1D,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,GACf,MAAM,CAkBR;AA0BD,wBAAgB,eAAe,IAAI,OAAO,CAkBzC;AAsSD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuBhG"}
@@ -18,7 +18,7 @@ import { createStatusLine } from '../utils/status-line.js';
18
18
  import { formatProjectHeader, formatSummary, formatTaskProgress, } from '../utils/terminal-symbols.js';
19
19
  import { deriveProjectState, discoverProjects, getNextExecutableTask, getDerivedStats, getDerivedStatsForTasks, isProjectComplete, hasProjectFailed, parseOutcomeStatus, } from '../core/state-derivation.js';
20
20
  import { analyzeFailure } from '../core/failure-analyzer.js';
21
- import { getRepoRoot, getRepoBasename, getCurrentBranch, computeWorktreePath, computeWorktreeBaseDir, validateWorktree, listWorktreeProjects, mergeWorktreeBranch, removeWorktree, } from '../core/worktree.js';
21
+ import { getRepoRoot, getRepoBasename, getCurrentBranch, computeWorktreePath, computeWorktreeBaseDir, validateWorktree, listWorktreeProjects, mergeWorktreeBranch, removeWorktree, resolveWorktreeProjectByIdentifier, } from '../core/worktree.js';
22
22
  import { createPullRequest, prPreflight } from '../core/pull-request.js';
23
23
  /**
24
24
  * Format failure history for console output.
@@ -52,6 +52,7 @@ export function createDoCommand() {
52
52
  .option('-m, --model <name>', 'Claude model to use (sonnet, haiku, opus)')
53
53
  .option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
54
54
  .option('-w, --worktree', 'Execute tasks in a git worktree')
55
+ .option('-r, --resume <session-id>', 'Resume an interrupted Claude session')
55
56
  .action(async (project, options) => {
56
57
  await runDoCommand(project, options);
57
58
  });
@@ -212,23 +213,45 @@ async function runDoCommand(projectIdentifierArg, options) {
212
213
  }
213
214
  }
214
215
  else {
215
- // Standard mode: resolve from main repo
216
- const result = resolveProjectIdentifierWithDetails(rafDir, projectIdentifier);
217
- if (!result.path) {
218
- if (result.error === 'ambiguous' && result.matches) {
219
- const matchList = result.matches
220
- .map((m) => ` - ${m.folder}`)
221
- .join('\n');
222
- logger.error(`${projectIdentifier}: Ambiguous project name. Multiple projects match:\n${matchList}\nPlease specify the project ID or full folder name.`);
216
+ // Standard mode: check worktrees first (worktree takes priority), then main repo
217
+ const repoRoot = getRepoRoot();
218
+ const repoBasename = repoRoot ? getRepoBasename() : null;
219
+ // Try worktree resolution first (preferred when project exists in both)
220
+ if (repoBasename) {
221
+ const wtResolution = resolveWorktreeProjectByIdentifier(repoBasename, projectIdentifier);
222
+ if (wtResolution) {
223
+ const rafRelativePath = path.relative(repoRoot, rafDir);
224
+ const wtRafDir = path.join(wtResolution.worktreeRoot, rafRelativePath);
225
+ const wtProjectPath = path.join(wtRafDir, wtResolution.folder);
226
+ if (fs.existsSync(wtProjectPath)) {
227
+ // Auto-switch to worktree mode
228
+ worktreeMode = true;
229
+ worktreeRoot = wtResolution.worktreeRoot;
230
+ originalBranch = getCurrentBranch() ?? undefined;
231
+ const projectName = extractProjectName(wtResolution.folder) ?? projectIdentifier;
232
+ resolvedProject = { identifier: projectIdentifier, path: wtProjectPath, name: projectName };
233
+ }
223
234
  }
224
- else {
225
- logger.error(`${projectIdentifier}: Project not found`);
235
+ }
236
+ // Fall back to main repo if worktree didn't match
237
+ if (!resolvedProject) {
238
+ const result = resolveProjectIdentifierWithDetails(rafDir, projectIdentifier);
239
+ if (!result.path) {
240
+ if (result.error === 'ambiguous' && result.matches) {
241
+ const matchList = result.matches
242
+ .map((m) => ` - ${m.folder}`)
243
+ .join('\n');
244
+ logger.error(`${projectIdentifier}: Ambiguous project name. Multiple projects match:\n${matchList}\nPlease specify the project ID or full folder name.`);
245
+ }
246
+ else {
247
+ logger.error(`${projectIdentifier}: Project not found`);
248
+ }
249
+ logger.info("Run 'raf status' to see available projects.");
250
+ process.exit(1);
226
251
  }
227
- logger.info("Run 'raf status' to see available projects.");
228
- process.exit(1);
252
+ const projectName = extractProjectName(result.path) ?? projectIdentifier;
253
+ resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
229
254
  }
230
- const projectName = extractProjectName(result.path) ?? projectIdentifier;
231
- resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
232
255
  }
233
256
  // Get configuration
234
257
  const config = getConfig();
@@ -236,6 +259,7 @@ async function runDoCommand(projectIdentifierArg, options) {
236
259
  const verbose = options.verbose ?? false;
237
260
  const debug = options.debug ?? false;
238
261
  const force = options.force ?? false;
262
+ const resumeSessionId = options.resume;
239
263
  const maxRetries = config.maxRetries;
240
264
  const autoCommit = config.autoCommit;
241
265
  // Configure logger
@@ -267,6 +291,7 @@ async function runDoCommand(projectIdentifierArg, options) {
267
291
  showModel: true,
268
292
  model,
269
293
  worktreeCwd: worktreeRoot,
294
+ resumeSessionId,
270
295
  });
271
296
  }
272
297
  catch (error) {
@@ -482,7 +507,8 @@ async function discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativeP
482
507
  }
483
508
  }
484
509
  async function executeSingleProject(projectPath, projectName, options) {
485
- const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model, worktreeCwd } = options;
510
+ const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model, worktreeCwd, resumeSessionId } = options;
511
+ let activeResumeSessionId = resumeSessionId;
486
512
  if (!validatePlansExist(projectPath)) {
487
513
  return {
488
514
  projectName,
@@ -653,7 +679,10 @@ async function executeSingleProject(projectPath, projectName, options) {
653
679
  const taskContext = `[Task ${taskNumber}/${totalTasks}: ${displayName}]`;
654
680
  logger.setContext(taskContext);
655
681
  // Log task execution status
656
- if (task.status === 'failed') {
682
+ if (activeResumeSessionId) {
683
+ logger.info(`Resuming task ${taskLabel} with session ${activeResumeSessionId}`);
684
+ }
685
+ else if (task.status === 'failed') {
657
686
  logger.info(`Retrying task ${taskLabel} (previously failed)...`);
658
687
  }
659
688
  else if (task.status === 'completed' && force) {
@@ -663,6 +692,9 @@ async function executeSingleProject(projectPath, projectName, options) {
663
692
  logger.info(`Executing task ${taskLabel}...`);
664
693
  }
665
694
  }
695
+ else if (activeResumeSessionId) {
696
+ logger.info(`Resuming task ${task.id} with session ${activeResumeSessionId}`);
697
+ }
666
698
  // Get previous outcomes for context
667
699
  const previousOutcomes = projectManager.readOutcomes(projectPath);
668
700
  // Get dependency outcomes - filter to only include outcomes for tasks this task depends on
@@ -721,9 +753,16 @@ async function executeSingleProject(projectPath, projectName, options) {
721
753
  outcomeFilePath,
722
754
  } : undefined;
723
755
  // Run Claude (use worktree root as cwd if in worktree mode)
724
- const result = verbose
725
- ? await claudeRunner.runVerbose(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd })
726
- : await claudeRunner.run(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd });
756
+ let result;
757
+ if (activeResumeSessionId && attempts === 1) {
758
+ // Resume mode: use --resume flag instead of normal prompt execution
759
+ result = await claudeRunner.runResume(activeResumeSessionId, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' });
760
+ }
761
+ else {
762
+ result = verbose
763
+ ? await claudeRunner.runVerbose(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' })
764
+ : await claudeRunner.run(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' });
765
+ }
727
766
  lastOutput = result.output;
728
767
  // Parse result
729
768
  const parsed = parseOutput(result.output);
@@ -877,6 +916,8 @@ ${stashName ? `- Stash: ${stashName}` : ''}
877
916
  if (verbose) {
878
917
  logger.newline();
879
918
  }
919
+ // Clear resume flag after first task — subsequent tasks use normal execution
920
+ activeResumeSessionId = undefined;
880
921
  // Clear context before next task
881
922
  logger.clearContext();
882
923
  // Re-derive state to get updated task statuses