rafcode 3.0.0 → 3.2.1
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.
- package/RAF/38-dual-wielder/decisions.md +9 -0
- package/RAF/38-dual-wielder/input.md +6 -1
- package/RAF/38-dual-wielder/outcomes/8-e2e-test-codex-provider.md +139 -0
- package/RAF/38-dual-wielder/plans/8-e2e-test-codex-provider.md +95 -0
- package/RAF/39-pathless-rover/decisions.md +16 -0
- package/RAF/39-pathless-rover/input.md +2 -0
- package/RAF/39-pathless-rover/outcomes/1-fix-codex-stream-renderer.md +21 -0
- package/RAF/39-pathless-rover/outcomes/2-wire-provider-flag.md +28 -0
- package/RAF/39-pathless-rover/outcomes/3-remove-worktree-flag-do.md +41 -0
- package/RAF/39-pathless-rover/outcomes/4-remove-worktree-flag-plan-amend.md +30 -0
- package/RAF/39-pathless-rover/outcomes/5-update-prompts-and-docs.md +26 -0
- package/RAF/39-pathless-rover/plans/1-fix-codex-stream-renderer.md +43 -0
- package/RAF/39-pathless-rover/plans/2-wire-provider-flag.md +48 -0
- package/RAF/39-pathless-rover/plans/3-remove-worktree-flag-do.md +41 -0
- package/RAF/39-pathless-rover/plans/4-remove-worktree-flag-plan-amend.md +43 -0
- package/RAF/39-pathless-rover/plans/5-update-prompts-and-docs.md +31 -0
- package/RAF/40-numeric-order-fix/decisions.md +7 -0
- package/RAF/40-numeric-order-fix/input.md +19 -0
- package/RAF/40-numeric-order-fix/outcomes/1-fix-numeric-sort-order.md +18 -0
- package/RAF/40-numeric-order-fix/outcomes/2-add-npm-keywords.md +10 -0
- package/RAF/40-numeric-order-fix/plans/1-fix-numeric-sort-order.md +48 -0
- package/RAF/40-numeric-order-fix/plans/2-add-npm-keywords.md +23 -0
- package/README.md +5 -8
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +41 -193
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +32 -120
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/project-manager.d.ts.map +1 -1
- package/dist/core/project-manager.js +2 -2
- package/dist/core/project-manager.js.map +1 -1
- package/dist/core/pull-request.js +2 -2
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/state-derivation.js +3 -3
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/parsers/codex-stream-renderer.d.ts +21 -4
- package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.js +77 -0
- package/dist/parsers/codex-stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts +0 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +2 -3
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +2 -3
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +0 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +9 -0
- package/dist/utils/paths.js.map +1 -1
- package/package.json +7 -2
- package/src/commands/do.ts +42 -220
- package/src/commands/plan.ts +34 -127
- package/src/core/project-manager.ts +2 -1
- package/src/core/pull-request.ts +2 -2
- package/src/core/state-derivation.ts +3 -3
- package/src/parsers/codex-stream-renderer.ts +106 -4
- package/src/prompts/amend.ts +1 -4
- package/src/prompts/config-docs.md +1 -1
- package/src/prompts/planning.ts +2 -4
- package/src/types/config.ts +0 -1
- package/src/utils/paths.ts +10 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
effort: low
|
|
3
|
+
---
|
|
4
|
+
# Task: Update Prompts, Docs, and Config Docs for Removed --worktree Flag
|
|
5
|
+
|
|
6
|
+
## Objective
|
|
7
|
+
Update all user-facing text (prompts, help text, docs, config documentation) to reflect that `--worktree` is no longer needed for `raf do` and `raf plan --amend`.
|
|
8
|
+
|
|
9
|
+
## Dependencies
|
|
10
|
+
3, 4
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
- Update planning prompt output that suggests `raf do <project> --worktree` → just `raf do <project>`
|
|
14
|
+
- Update amend prompt output if it references `--worktree`
|
|
15
|
+
- Update config-docs.md if it documents the `--worktree` flag behavior for `do` or `amend`
|
|
16
|
+
- Update README.md CLI usage sections
|
|
17
|
+
- Remove any references to `--worktree` in error messages that were in `do.ts`
|
|
18
|
+
|
|
19
|
+
## Implementation Steps
|
|
20
|
+
1. Read `src/prompts/planning.ts` — update the exit message that suggests `--worktree`
|
|
21
|
+
2. Read `src/prompts/amend.ts` — update if it references `--worktree`
|
|
22
|
+
3. Read `src/prompts/config-docs.md` — update worktree config documentation
|
|
23
|
+
4. Read `README.md` — update CLI usage for `raf do` and `raf plan`
|
|
24
|
+
5. Grep for any remaining `--worktree` references across the codebase and update as needed
|
|
25
|
+
|
|
26
|
+
## Acceptance Criteria
|
|
27
|
+
- [ ] No prompts suggest `--worktree` for `raf do`
|
|
28
|
+
- [ ] No prompts suggest `--worktree` for `raf plan --amend`
|
|
29
|
+
- [ ] README accurately reflects current CLI flags
|
|
30
|
+
- [ ] Config docs accurately describe worktree behavior
|
|
31
|
+
- [ ] No stale `--worktree` references in codebase (except for `raf plan` new project creation where it's still valid)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Project Decisions
|
|
2
|
+
|
|
3
|
+
## Should the fix be a shared helper function or inline fixes at each call site?
|
|
4
|
+
Shared helper function — create one numeric sort utility and use it everywhere for DRY and consistency.
|
|
5
|
+
|
|
6
|
+
## Which Codex-related keywords should be added to package.json?
|
|
7
|
+
codex, openai-codex, claude-code, agentic-coding, coding-agent — broader set including agentic coding terms.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
- [ ] raf do executes task in lexigraphical order (1 then 10). should execute in order of numbers **➜** **BindNotes** **git:(****main****)** raf do
|
|
2
|
+
|
|
3
|
+
✔ **Select a project to execute:** 14 mosaic-drift (0/19 tasks) [worktree]
|
|
4
|
+
|
|
5
|
+
✔ **After tasks complete, what should happen with branch "14-mosaic-drift"?** Merge into current branch
|
|
6
|
+
|
|
7
|
+
Rebased onto main
|
|
8
|
+
|
|
9
|
+
RAF v3.0.0 | Ceiling: claude-opus-4-6
|
|
10
|
+
|
|
11
|
+
▶ mosaic-drift (19 tasks)
|
|
12
|
+
|
|
13
|
+
Press Tab to toggle verbose mode
|
|
14
|
+
|
|
15
|
+
✓ 1-archived-notebook-name-validation (opus) 5m 6s
|
|
16
|
+
|
|
17
|
+
Tokens: 25 in / 7,798 out | Cost: $1.06
|
|
18
|
+
|
|
19
|
+
● 10-localize-dutch (sonnet) 43s
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Outcome: Fix numeric sort order for plan/outcome files
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
Added a `numericFileSort` comparator helper and replaced all lexicographical `.sort()` calls on plan/outcome file lists with numeric sorting.
|
|
5
|
+
|
|
6
|
+
## Changes Made
|
|
7
|
+
|
|
8
|
+
- **`src/utils/paths.ts`**: Added `numericFileSort(a, b)` export that uses `parseInt` to compare leading numeric prefixes.
|
|
9
|
+
- **`src/core/state-derivation.ts`**: Updated plan files sort (line ~201) and outcome files sort (line ~208) to use `numericFileSort`.
|
|
10
|
+
- **`src/commands/plan.ts`**: Updated three `.sort()` calls (lines ~342, ~565, ~726) to use `numericFileSort`.
|
|
11
|
+
- **`src/core/project-manager.ts`**: Updated outcome files sort (line ~146) to use `numericFileSort`.
|
|
12
|
+
- **`src/core/pull-request.ts`**: Updated outcome files sort (line ~211) to use `numericFileSort`.
|
|
13
|
+
|
|
14
|
+
## Verification
|
|
15
|
+
- Build passes with `npm run build` (no TypeScript errors).
|
|
16
|
+
- Files named `1-x.md` through `19-x.md` now sort as 1, 2, 3, ..., 10, ..., 19.
|
|
17
|
+
|
|
18
|
+
<promise>COMPLETE</promise>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Outcome: Add Codex and agentic coding keywords to package.json
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
Added five new discoverability keywords to `package.json` to make the package findable via Codex and agentic coding search terms on npm.
|
|
5
|
+
|
|
6
|
+
## Changes Made
|
|
7
|
+
|
|
8
|
+
- **`package.json`**: Added `codex`, `openai-codex`, `claude-code`, `agentic-coding`, `coding-agent` to the `keywords` array. All existing keywords remain intact.
|
|
9
|
+
|
|
10
|
+
<promise>COMPLETE</promise>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
effort: low
|
|
3
|
+
---
|
|
4
|
+
# Task: Fix numeric sort order for plan/outcome files
|
|
5
|
+
|
|
6
|
+
## Objective
|
|
7
|
+
Replace all lexicographical `.sort()` calls on numbered file lists with numeric sorting so tasks execute in correct order (1, 2, 10 instead of 1, 10, 2).
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
`raf do` reads plan files named like `1-task.md`, `10-task.md` and sorts them with `.sort()`, which uses string comparison. This causes task 10 to execute before task 2. The codebase already has correct numeric sorting for projects (`a.number - b.number`), but plan/outcome file sorting was missed.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
- Create a shared numeric sort helper function in `src/utils/paths.ts` (where `TASK_ID_PATTERN` already lives)
|
|
14
|
+
- The helper should extract the leading number from filenames like `1-task-name.md` and sort numerically
|
|
15
|
+
- Replace all lexicographical `.sort()` calls on plan/outcome files across the codebase
|
|
16
|
+
|
|
17
|
+
## Implementation Steps
|
|
18
|
+
|
|
19
|
+
1. Add a `numericFileSort` comparator function to `src/utils/paths.ts`:
|
|
20
|
+
```typescript
|
|
21
|
+
export function numericFileSort(a: string, b: string): number {
|
|
22
|
+
const numA = parseInt(a, 10);
|
|
23
|
+
const numB = parseInt(b, 10);
|
|
24
|
+
return numA - numB;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
`parseInt` on `"10-task-name.md"` returns `10` — it stops at the first non-numeric character.
|
|
28
|
+
|
|
29
|
+
2. Update the following `.sort()` calls to use `numericFileSort`:
|
|
30
|
+
- `src/core/state-derivation.ts` ~line 201: plan files `.sort()` → `.sort(numericFileSort)`
|
|
31
|
+
- `src/core/state-derivation.ts` ~line 208: outcome files `.sort()` → `.sort(numericFileSort)`
|
|
32
|
+
- `src/commands/plan.ts` ~lines 342, 565, 726: plan file sorting after creation
|
|
33
|
+
- `src/project-manager.ts` ~line 146: outcome files sorting
|
|
34
|
+
- `src/pull-request.ts` ~line 211: outcome files sorting for PR generation
|
|
35
|
+
|
|
36
|
+
3. Add the import `import { numericFileSort } from '../utils/paths'` (adjust relative path) to each file that needs it.
|
|
37
|
+
|
|
38
|
+
4. **Do NOT touch** `src/core/worktree.ts` line 353 — worktree directory sorting may not follow the same numeric prefix pattern; verify before changing.
|
|
39
|
+
|
|
40
|
+
## Acceptance Criteria
|
|
41
|
+
- [ ] `numericFileSort` helper exists in `src/utils/paths.ts`
|
|
42
|
+
- [ ] All plan/outcome file `.sort()` calls use numeric comparison
|
|
43
|
+
- [ ] Files named `1-x.md` through `19-x.md` sort as 1,2,3,...,10,...,19 (not 1,10,11,...,19,2,3,...)
|
|
44
|
+
- [ ] Project builds without errors (`npm run build` or equivalent)
|
|
45
|
+
|
|
46
|
+
## Notes
|
|
47
|
+
- `parseInt("10-task-name.md", 10)` correctly returns `10` — no regex needed.
|
|
48
|
+
- The existing `TASK_ID_PATTERN` in `paths.ts` confirms the numeric prefix convention.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
effort: low
|
|
3
|
+
---
|
|
4
|
+
# Task: Add Codex and agentic coding keywords to package.json
|
|
5
|
+
|
|
6
|
+
## Objective
|
|
7
|
+
Add discoverability keywords to package.json so the package is findable via Codex and agentic coding search terms on npm.
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
The package currently has keywords like "claude", "anthropic", "ai", but is missing Codex-related and agentic coding terms that users might search for.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
- Add the following keywords to the `keywords` array in `package.json`: `codex`, `openai-codex`, `claude-code`, `agentic-coding`, `coding-agent`
|
|
14
|
+
- Keep existing keywords intact
|
|
15
|
+
|
|
16
|
+
## Implementation Steps
|
|
17
|
+
1. Open `package.json`
|
|
18
|
+
2. Add the five new keywords to the existing `keywords` array: `codex`, `openai-codex`, `claude-code`, `agentic-coding`, `coding-agent`
|
|
19
|
+
|
|
20
|
+
## Acceptance Criteria
|
|
21
|
+
- [ ] All five new keywords are present in `package.json` `keywords` array
|
|
22
|
+
- [ ] Existing keywords are unchanged
|
|
23
|
+
- [ ] `package.json` is valid JSON
|
package/README.md
CHANGED
|
@@ -85,11 +85,9 @@ raf plan --worktree # Plan in an isolated git worktree
|
|
|
85
85
|
Execute project tasks. Without arguments, shows a picker to select a pending project.
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
raf do # Interactive picker
|
|
88
|
+
raf do # Interactive picker (includes worktree projects)
|
|
89
89
|
raf do abcdef # Execute by project ID
|
|
90
90
|
raf do my-project # Execute by name
|
|
91
|
-
raf do --worktree # Pick and execute a worktree project
|
|
92
|
-
raf do my-feature -w # Execute in worktree (picker will ask what to do after)
|
|
93
91
|
```
|
|
94
92
|
|
|
95
93
|
Note: In non-verbose mode, the completion summary reflects the tasks executed in that run (the remaining tasks at start), so the elapsed time maps to those tasks.
|
|
@@ -208,8 +206,8 @@ Worktree mode runs planning and execution in an isolated git worktree, keeping y
|
|
|
208
206
|
# Plan in a worktree (creates branch and worktree directory)
|
|
209
207
|
raf plan my-feature --worktree
|
|
210
208
|
|
|
211
|
-
# Execute tasks in the worktree
|
|
212
|
-
raf do my-feature
|
|
209
|
+
# Execute tasks in the worktree (auto-detected, no flag needed)
|
|
210
|
+
raf do my-feature
|
|
213
211
|
```
|
|
214
212
|
|
|
215
213
|
### Post-execution picker
|
|
@@ -238,10 +236,11 @@ If `gh` is missing or unauthenticated, the option falls back to "Leave branch" w
|
|
|
238
236
|
|
|
239
237
|
- `--worktree` creates a git worktree at `~/.raf/worktrees/<repo>/<project>/` with a new branch named after the project folder (e.g., `abcdef-my-feature`)
|
|
240
238
|
- All planning artifacts, code changes, and commits happen in the worktree branch
|
|
239
|
+
- `raf do` auto-detects whether a project lives in a worktree — no `--worktree` flag needed
|
|
241
240
|
- After successful post-actions (merge, PR, or leave), the worktree directory is cleaned up automatically — the git branch is preserved
|
|
242
241
|
- On merge conflicts, the merge is aborted and you get instructions for manual resolution
|
|
243
242
|
- If tasks fail, the worktree is kept for inspection
|
|
244
|
-
- Use `--no-worktree` to disable worktree mode for a single invocation (useful when `worktree: true` is set in config)
|
|
243
|
+
- Use `--no-worktree` on `raf plan` to disable worktree mode for a single invocation (useful when `worktree: true` is set in config)
|
|
245
244
|
|
|
246
245
|
## Command Reference
|
|
247
246
|
|
|
@@ -266,8 +265,6 @@ If `gh` is missing or unauthenticated, the option falls back to "Leave branch" w
|
|
|
266
265
|
| `-p, --provider <name>` | LLM provider to use (`claude`, `codex`) |
|
|
267
266
|
| `-m, --model <name>` | Model to use (e.g. `sonnet`, `codex/gpt54`) |
|
|
268
267
|
| `--sonnet` | Shorthand for `--model sonnet` |
|
|
269
|
-
| `-w, --worktree` | Execute tasks in a git worktree |
|
|
270
|
-
| `--no-worktree` | Disable worktree mode (overrides config) |
|
|
271
268
|
|
|
272
269
|
Alias: `raf act`
|
|
273
270
|
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"do.d.ts","sourceRoot":"","sources":["../../src/commands/do.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsDpC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;AAuE3D;;;;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;AAkOD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuBhG"}
|
package/dist/commands/do.js
CHANGED
|
@@ -10,18 +10,18 @@ import { getExecutionPrompt } from '../prompts/execution.js';
|
|
|
10
10
|
import { parseOutput, isRetryableFailure } from '../parsers/output-parser.js';
|
|
11
11
|
import { validatePlansExist, resolveModelOption } from '../utils/validation.js';
|
|
12
12
|
import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFromPlanFile, resolveProjectIdentifierWithDetails, getOutcomeFilePath } from '../utils/paths.js';
|
|
13
|
-
import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects
|
|
13
|
+
import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects } from '../ui/project-picker.js';
|
|
14
14
|
import { logger } from '../utils/logger.js';
|
|
15
|
-
import { getConfig,
|
|
15
|
+
import { getConfig, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
|
|
16
16
|
import { getVersion } from '../utils/version.js';
|
|
17
17
|
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
18
18
|
import { createStatusLine } from '../utils/status-line.js';
|
|
19
19
|
import { formatProjectHeader, formatSummary, formatTaskProgress, formatTaskTokenSummary, formatTokenTotalSummary, } from '../utils/terminal-symbols.js';
|
|
20
20
|
import { TokenTracker } from '../utils/token-tracker.js';
|
|
21
21
|
import { VerboseToggle } from '../utils/verbose-toggle.js';
|
|
22
|
-
import { deriveProjectState,
|
|
22
|
+
import { deriveProjectState, getNextExecutableTask, getDerivedStats, getDerivedStatsForTasks, isProjectComplete, hasProjectFailed, parseOutcomeStatus, } from '../core/state-derivation.js';
|
|
23
23
|
import { analyzeFailure } from '../core/failure-analyzer.js';
|
|
24
|
-
import { getRepoRoot, getRepoBasename, getCurrentBranch,
|
|
24
|
+
import { getRepoRoot, getRepoBasename, getCurrentBranch, mergeWorktreeBranch, removeWorktree, resolveWorktreeProjectByIdentifier, pushMainBranch, pullMainBranch, detectMainBranch, rebaseOntoMain, } from '../core/worktree.js';
|
|
25
25
|
import { createPullRequest, prPreflight } from '../core/pull-request.js';
|
|
26
26
|
/**
|
|
27
27
|
* Resolve the execution model for a task from its frontmatter metadata.
|
|
@@ -36,7 +36,7 @@ import { createPullRequest, prPreflight } from '../core/pull-request.js';
|
|
|
36
36
|
* @param ceilingModel - The ceiling model (usually models.execute from config)
|
|
37
37
|
* @param isRetry - Whether this is a retry attempt (escalates to ceiling)
|
|
38
38
|
*/
|
|
39
|
-
function resolveTaskModel(frontmatter, frontmatterWarnings, ceilingModel, isRetry) {
|
|
39
|
+
function resolveTaskModel(frontmatter, frontmatterWarnings, ceilingModel, isRetry, provider) {
|
|
40
40
|
const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
|
|
41
41
|
// Retry escalation: always use the ceiling model on retry
|
|
42
42
|
if (isRetry) {
|
|
@@ -57,7 +57,7 @@ function resolveTaskModel(frontmatter, frontmatterWarnings, ceilingModel, isRetr
|
|
|
57
57
|
}
|
|
58
58
|
// Effort-based resolution - apply ceiling
|
|
59
59
|
if (frontmatter.effort) {
|
|
60
|
-
const mappedModel = resolveEffortToModel(frontmatter.effort);
|
|
60
|
+
const mappedModel = resolveEffortToModel(frontmatter.effort, provider);
|
|
61
61
|
const model = applyModelCeiling(mappedModel, ceilingModel);
|
|
62
62
|
return { model, missingFrontmatter: false, warnings };
|
|
63
63
|
}
|
|
@@ -99,8 +99,6 @@ export function createDoCommand() {
|
|
|
99
99
|
.option('-f, --force', 'Re-run all tasks regardless of status')
|
|
100
100
|
.option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
|
|
101
101
|
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
102
|
-
.option('-w, --worktree', 'Execute tasks in a git worktree')
|
|
103
|
-
.option('--no-worktree', 'Disable worktree mode (overrides config)')
|
|
104
102
|
.option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
|
|
105
103
|
.action(async (project, options) => {
|
|
106
104
|
await runDoCommand(project, options);
|
|
@@ -110,7 +108,6 @@ export function createDoCommand() {
|
|
|
110
108
|
async function runDoCommand(projectIdentifierArg, options) {
|
|
111
109
|
const rafDir = getRafDir();
|
|
112
110
|
let projectIdentifier = projectIdentifierArg;
|
|
113
|
-
let worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
114
111
|
// Validate and resolve model option
|
|
115
112
|
let model;
|
|
116
113
|
try {
|
|
@@ -120,44 +117,10 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
120
117
|
logger.error(error.message);
|
|
121
118
|
process.exit(1);
|
|
122
119
|
}
|
|
123
|
-
// Variables for worktree context (
|
|
120
|
+
// Variables for worktree context (derived from where the project is found)
|
|
124
121
|
let worktreeRoot;
|
|
125
122
|
let originalBranch;
|
|
126
123
|
let mainBranchName = null;
|
|
127
|
-
if (worktreeMode) {
|
|
128
|
-
// Validate git repo
|
|
129
|
-
const repoRoot = getRepoRoot();
|
|
130
|
-
if (!repoRoot) {
|
|
131
|
-
logger.error('--worktree requires a git repository');
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
const repoBasename = getRepoBasename();
|
|
135
|
-
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
136
|
-
// Record original branch before any worktree operations
|
|
137
|
-
originalBranch = getCurrentBranch() ?? undefined;
|
|
138
|
-
// Sync main branch before worktree operations (if enabled)
|
|
139
|
-
if (getSyncMainBranch()) {
|
|
140
|
-
const syncResult = pullMainBranch();
|
|
141
|
-
mainBranchName = syncResult.mainBranch;
|
|
142
|
-
if (syncResult.success) {
|
|
143
|
-
if (syncResult.hadChanges) {
|
|
144
|
-
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (!projectIdentifier) {
|
|
152
|
-
// Auto-discovery flow
|
|
153
|
-
const selected = await discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath);
|
|
154
|
-
if (!selected) {
|
|
155
|
-
process.exit(0);
|
|
156
|
-
}
|
|
157
|
-
worktreeRoot = selected.worktreeRoot;
|
|
158
|
-
projectIdentifier = selected.projectFolder;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
124
|
// Handle no project identifier (non-worktree mode) - show interactive picker
|
|
162
125
|
if (!projectIdentifier) {
|
|
163
126
|
// Discover worktree projects for the current repo (if in a git repo)
|
|
@@ -183,9 +146,8 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
183
146
|
}
|
|
184
147
|
// Use the selected project
|
|
185
148
|
projectIdentifier = selectedProject.folder;
|
|
186
|
-
// If a worktree project was selected,
|
|
149
|
+
// If a worktree project was selected, record worktree context
|
|
187
150
|
if (selectedProject.source === 'worktree' && selectedProject.worktreeRoot) {
|
|
188
|
-
worktreeMode = true;
|
|
189
151
|
worktreeRoot = selectedProject.worktreeRoot;
|
|
190
152
|
originalBranch = getCurrentBranch() ?? undefined;
|
|
191
153
|
}
|
|
@@ -200,83 +162,21 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
200
162
|
}
|
|
201
163
|
// Resolve project identifier
|
|
202
164
|
let resolvedProject;
|
|
203
|
-
if (
|
|
204
|
-
// Worktree
|
|
165
|
+
if (worktreeRoot) {
|
|
166
|
+
// Worktree was set by the picker — resolve project inside the worktree
|
|
205
167
|
const repoRoot = getRepoRoot();
|
|
206
|
-
const repoBasename = getRepoBasename();
|
|
207
168
|
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
logger.error(`Project not found in worktree: ${projectIdentifier}`);
|
|
214
|
-
process.exit(1);
|
|
215
|
-
}
|
|
216
|
-
const projectName = extractProjectName(result.path) ?? projectIdentifier;
|
|
217
|
-
resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
// Explicit identifier: resolve from main repo to get folder name, then validate worktree
|
|
221
|
-
const mainResult = resolveProjectIdentifierWithDetails(rafDir, projectIdentifier);
|
|
222
|
-
let projectFolderName;
|
|
223
|
-
if (mainResult.path) {
|
|
224
|
-
// Found in main repo - use its folder name
|
|
225
|
-
projectFolderName = path.basename(mainResult.path);
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
// Not found in main repo - try to find it in worktrees directly
|
|
229
|
-
// This handles projects that only exist in worktrees
|
|
230
|
-
const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
|
|
231
|
-
if (!fs.existsSync(worktreeBaseDir)) {
|
|
232
|
-
logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
// Search worktrees for the project
|
|
236
|
-
const wtProjects = listWorktreeProjects(repoBasename);
|
|
237
|
-
let found = false;
|
|
238
|
-
for (const wtProjectDir of wtProjects) {
|
|
239
|
-
const wtPath = computeWorktreePath(repoBasename, wtProjectDir);
|
|
240
|
-
const wtRafDir = path.join(wtPath, rafRelativePath);
|
|
241
|
-
if (!fs.existsSync(wtRafDir))
|
|
242
|
-
continue;
|
|
243
|
-
const resolution = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
|
|
244
|
-
if (resolution.path) {
|
|
245
|
-
projectFolderName = path.basename(resolution.path);
|
|
246
|
-
worktreeRoot = wtPath;
|
|
247
|
-
found = true;
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (!found) {
|
|
252
|
-
logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// Compute worktree path if not already set
|
|
257
|
-
if (!worktreeRoot) {
|
|
258
|
-
worktreeRoot = computeWorktreePath(repoBasename, projectFolderName);
|
|
259
|
-
}
|
|
260
|
-
// Validate the worktree
|
|
261
|
-
const wtProjectRelPath = path.join(rafRelativePath, projectFolderName);
|
|
262
|
-
const validation = validateWorktree(worktreeRoot, wtProjectRelPath);
|
|
263
|
-
if (!validation.exists || !validation.isValidWorktree) {
|
|
264
|
-
logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
|
|
265
|
-
logger.error(`Expected worktree at: ${worktreeRoot}`);
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
if (!validation.hasProjectFolder || !validation.hasPlans) {
|
|
269
|
-
logger.error(`Worktree exists but project content is missing.`);
|
|
270
|
-
logger.error(`Expected project folder at: ${validation.projectPath ?? path.join(worktreeRoot, wtProjectRelPath)}`);
|
|
271
|
-
process.exit(1);
|
|
272
|
-
}
|
|
273
|
-
const projectPath = validation.projectPath;
|
|
274
|
-
const projectName = extractProjectName(projectPath) ?? projectIdentifier;
|
|
275
|
-
resolvedProject = { identifier: projectIdentifier, path: projectPath, name: projectName };
|
|
169
|
+
const wtRafDir = path.join(worktreeRoot, rafRelativePath);
|
|
170
|
+
const result = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
|
|
171
|
+
if (!result.path) {
|
|
172
|
+
logger.error(`Project not found in worktree: ${projectIdentifier}`);
|
|
173
|
+
process.exit(1);
|
|
276
174
|
}
|
|
175
|
+
const projectName = extractProjectName(result.path) ?? projectIdentifier;
|
|
176
|
+
resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
|
|
277
177
|
}
|
|
278
178
|
else {
|
|
279
|
-
//
|
|
179
|
+
// Auto-detect: check worktrees first (worktree takes priority), then main repo
|
|
280
180
|
const repoRoot = getRepoRoot();
|
|
281
181
|
const repoBasename = repoRoot ? getRepoBasename() : null;
|
|
282
182
|
// Try worktree resolution first (preferred when project exists in both)
|
|
@@ -287,8 +187,6 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
287
187
|
const wtRafDir = path.join(wtResolution.worktreeRoot, rafRelativePath);
|
|
288
188
|
const wtProjectPath = path.join(wtRafDir, wtResolution.folder);
|
|
289
189
|
if (fs.existsSync(wtProjectPath)) {
|
|
290
|
-
// Auto-switch to worktree mode
|
|
291
|
-
worktreeMode = true;
|
|
292
190
|
worktreeRoot = wtResolution.worktreeRoot;
|
|
293
191
|
originalBranch = getCurrentBranch() ?? undefined;
|
|
294
192
|
const projectName = extractProjectName(wtResolution.folder) ?? projectIdentifier;
|
|
@@ -326,9 +224,25 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
326
224
|
const autoCommit = config.autoCommit;
|
|
327
225
|
// Configure logger
|
|
328
226
|
logger.configure({ verbose, debug });
|
|
329
|
-
//
|
|
227
|
+
// Worktree setup: sync main branch and show post-execution picker
|
|
330
228
|
let postAction = 'leave';
|
|
331
|
-
if (
|
|
229
|
+
if (worktreeRoot) {
|
|
230
|
+
if (!originalBranch) {
|
|
231
|
+
originalBranch = getCurrentBranch() ?? undefined;
|
|
232
|
+
}
|
|
233
|
+
// Sync main branch before worktree operations (if enabled)
|
|
234
|
+
if (getSyncMainBranch()) {
|
|
235
|
+
const syncResult = pullMainBranch();
|
|
236
|
+
mainBranchName = syncResult.mainBranch;
|
|
237
|
+
if (syncResult.success) {
|
|
238
|
+
if (syncResult.hadChanges) {
|
|
239
|
+
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
332
246
|
try {
|
|
333
247
|
postAction = await pickPostExecutionAction(worktreeRoot);
|
|
334
248
|
}
|
|
@@ -365,6 +279,7 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
365
279
|
maxRetries,
|
|
366
280
|
autoCommit,
|
|
367
281
|
model,
|
|
282
|
+
provider: options.provider,
|
|
368
283
|
worktreeCwd: worktreeRoot,
|
|
369
284
|
});
|
|
370
285
|
}
|
|
@@ -374,7 +289,7 @@ async function runDoCommand(projectIdentifierArg, options) {
|
|
|
374
289
|
process.exit(1);
|
|
375
290
|
}
|
|
376
291
|
// Execute post-execution action based on picker choice
|
|
377
|
-
if (
|
|
292
|
+
if (worktreeRoot) {
|
|
378
293
|
const worktreeBranch = path.basename(worktreeRoot);
|
|
379
294
|
if (result.success) {
|
|
380
295
|
await executePostAction(postAction, worktreeRoot, worktreeBranch, originalBranch, resolvedProject.path);
|
|
@@ -497,75 +412,8 @@ async function executePostAction(action, worktreeRoot, worktreeBranch, originalB
|
|
|
497
412
|
}
|
|
498
413
|
}
|
|
499
414
|
}
|
|
500
|
-
/**
|
|
501
|
-
* Auto-discovery flow for `raf do --worktree` without a project identifier.
|
|
502
|
-
* Shows both worktree AND main-repo pending projects in an interactive picker.
|
|
503
|
-
* Worktree projects above a threshold filter are included; main-repo projects
|
|
504
|
-
* are always included. Duplicates are deduped with worktree taking precedence.
|
|
505
|
-
*
|
|
506
|
-
* @returns Selected project info or null if cancelled/no projects
|
|
507
|
-
*/
|
|
508
|
-
async function discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath) {
|
|
509
|
-
// Get worktree pending projects (uses shared utility from project-picker)
|
|
510
|
-
const wtPendingProjects = getPendingWorktreeProjects(repoBasename, rafRelativePath);
|
|
511
|
-
// Find the highest-numbered completed project in the MAIN tree for threshold filter
|
|
512
|
-
const mainProjects = discoverProjects(rafDir);
|
|
513
|
-
let highestCompletedNumber = 0;
|
|
514
|
-
for (const project of mainProjects) {
|
|
515
|
-
const state = deriveProjectState(project.path);
|
|
516
|
-
if (isProjectComplete(state) && state.tasks.length > 0) {
|
|
517
|
-
if (project.number > highestCompletedNumber) {
|
|
518
|
-
highestCompletedNumber = project.number;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
// Filter threshold: highest completed - 3 (or 0 if none completed)
|
|
523
|
-
const threshold = highestCompletedNumber > 3 ? highestCompletedNumber - 3 : 0;
|
|
524
|
-
// Apply threshold filter to worktree projects only
|
|
525
|
-
const filteredWtProjects = wtPendingProjects.filter((p) => p.number >= threshold);
|
|
526
|
-
// Get main-repo pending projects (no threshold filter)
|
|
527
|
-
const mainPendingProjects = getPendingProjects(rafDir);
|
|
528
|
-
// Merge and deduplicate: worktree versions take precedence
|
|
529
|
-
const allProjects = [...mainPendingProjects, ...filteredWtProjects];
|
|
530
|
-
const seen = new Map();
|
|
531
|
-
for (const project of allProjects) {
|
|
532
|
-
const existing = seen.get(project.folder);
|
|
533
|
-
if (!existing || project.source === 'worktree') {
|
|
534
|
-
seen.set(project.folder, project);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
const deduped = Array.from(seen.values());
|
|
538
|
-
// Sort by project number
|
|
539
|
-
deduped.sort((a, b) => a.number - b.number);
|
|
540
|
-
if (deduped.length === 0) {
|
|
541
|
-
logger.info('No pending projects found.');
|
|
542
|
-
return null;
|
|
543
|
-
}
|
|
544
|
-
// Show interactive picker
|
|
545
|
-
const choices = deduped.map((p) => ({
|
|
546
|
-
name: formatProjectChoice(p),
|
|
547
|
-
value: p,
|
|
548
|
-
}));
|
|
549
|
-
try {
|
|
550
|
-
const selected = await select({
|
|
551
|
-
message: 'Select a project to execute:',
|
|
552
|
-
choices,
|
|
553
|
-
});
|
|
554
|
-
return {
|
|
555
|
-
worktreeRoot: selected.worktreeRoot,
|
|
556
|
-
projectFolder: selected.folder,
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
catch (error) {
|
|
560
|
-
// Handle Ctrl+C (user cancellation)
|
|
561
|
-
if (error instanceof Error && error.message.includes('User force closed')) {
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
throw error;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
415
|
async function executeSingleProject(projectPath, projectName, options) {
|
|
568
|
-
const { timeout, verbose, debug, force, maxRetries, autoCommit, model, worktreeCwd } = options;
|
|
416
|
+
const { timeout, verbose, debug, force, maxRetries, autoCommit, model, provider, worktreeCwd } = options;
|
|
569
417
|
if (!validatePlansExist(projectPath)) {
|
|
570
418
|
return {
|
|
571
419
|
projectName,
|
|
@@ -798,7 +646,7 @@ async function executeSingleProject(projectPath, projectName, options) {
|
|
|
798
646
|
const isRetry = attempts > 1;
|
|
799
647
|
// Resolve the model for this attempt (escalates to ceiling on retry)
|
|
800
648
|
const modelResolution = resolveTaskModel(task.frontmatter, undefined, // warnings already logged above
|
|
801
|
-
ceilingModel, isRetry);
|
|
649
|
+
ceilingModel, isRetry, provider);
|
|
802
650
|
// Update current model for timer callback display
|
|
803
651
|
currentModel = modelResolution.model;
|
|
804
652
|
// Log missing frontmatter warning on first attempt only
|
|
@@ -806,7 +654,7 @@ async function executeSingleProject(projectPath, projectName, options) {
|
|
|
806
654
|
logger.warn(` No effort frontmatter found — using ceiling model`);
|
|
807
655
|
}
|
|
808
656
|
// Create a runner for this attempt's model
|
|
809
|
-
const taskRunner = createRunner({ model: modelResolution.model });
|
|
657
|
+
const taskRunner = createRunner({ model: modelResolution.model, provider });
|
|
810
658
|
shutdownHandler.registerClaudeRunner(taskRunner);
|
|
811
659
|
if (verbose && isRetry) {
|
|
812
660
|
const retryModel = resolveFullModelId(modelResolution.model);
|