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.
Files changed (65) hide show
  1. package/RAF/38-dual-wielder/decisions.md +9 -0
  2. package/RAF/38-dual-wielder/input.md +6 -1
  3. package/RAF/38-dual-wielder/outcomes/8-e2e-test-codex-provider.md +139 -0
  4. package/RAF/38-dual-wielder/plans/8-e2e-test-codex-provider.md +95 -0
  5. package/RAF/39-pathless-rover/decisions.md +16 -0
  6. package/RAF/39-pathless-rover/input.md +2 -0
  7. package/RAF/39-pathless-rover/outcomes/1-fix-codex-stream-renderer.md +21 -0
  8. package/RAF/39-pathless-rover/outcomes/2-wire-provider-flag.md +28 -0
  9. package/RAF/39-pathless-rover/outcomes/3-remove-worktree-flag-do.md +41 -0
  10. package/RAF/39-pathless-rover/outcomes/4-remove-worktree-flag-plan-amend.md +30 -0
  11. package/RAF/39-pathless-rover/outcomes/5-update-prompts-and-docs.md +26 -0
  12. package/RAF/39-pathless-rover/plans/1-fix-codex-stream-renderer.md +43 -0
  13. package/RAF/39-pathless-rover/plans/2-wire-provider-flag.md +48 -0
  14. package/RAF/39-pathless-rover/plans/3-remove-worktree-flag-do.md +41 -0
  15. package/RAF/39-pathless-rover/plans/4-remove-worktree-flag-plan-amend.md +43 -0
  16. package/RAF/39-pathless-rover/plans/5-update-prompts-and-docs.md +31 -0
  17. package/RAF/40-numeric-order-fix/decisions.md +7 -0
  18. package/RAF/40-numeric-order-fix/input.md +19 -0
  19. package/RAF/40-numeric-order-fix/outcomes/1-fix-numeric-sort-order.md +18 -0
  20. package/RAF/40-numeric-order-fix/outcomes/2-add-npm-keywords.md +10 -0
  21. package/RAF/40-numeric-order-fix/plans/1-fix-numeric-sort-order.md +48 -0
  22. package/RAF/40-numeric-order-fix/plans/2-add-npm-keywords.md +23 -0
  23. package/README.md +5 -8
  24. package/dist/commands/do.d.ts.map +1 -1
  25. package/dist/commands/do.js +41 -193
  26. package/dist/commands/do.js.map +1 -1
  27. package/dist/commands/plan.d.ts.map +1 -1
  28. package/dist/commands/plan.js +32 -120
  29. package/dist/commands/plan.js.map +1 -1
  30. package/dist/core/project-manager.d.ts.map +1 -1
  31. package/dist/core/project-manager.js +2 -2
  32. package/dist/core/project-manager.js.map +1 -1
  33. package/dist/core/pull-request.js +2 -2
  34. package/dist/core/pull-request.js.map +1 -1
  35. package/dist/core/state-derivation.js +3 -3
  36. package/dist/core/state-derivation.js.map +1 -1
  37. package/dist/parsers/codex-stream-renderer.d.ts +21 -4
  38. package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
  39. package/dist/parsers/codex-stream-renderer.js +77 -0
  40. package/dist/parsers/codex-stream-renderer.js.map +1 -1
  41. package/dist/prompts/amend.d.ts +0 -1
  42. package/dist/prompts/amend.d.ts.map +1 -1
  43. package/dist/prompts/amend.js +2 -3
  44. package/dist/prompts/amend.js.map +1 -1
  45. package/dist/prompts/planning.d.ts.map +1 -1
  46. package/dist/prompts/planning.js +2 -3
  47. package/dist/prompts/planning.js.map +1 -1
  48. package/dist/types/config.d.ts +0 -1
  49. package/dist/types/config.d.ts.map +1 -1
  50. package/dist/utils/paths.d.ts +5 -0
  51. package/dist/utils/paths.d.ts.map +1 -1
  52. package/dist/utils/paths.js +9 -0
  53. package/dist/utils/paths.js.map +1 -1
  54. package/package.json +7 -2
  55. package/src/commands/do.ts +42 -220
  56. package/src/commands/plan.ts +34 -127
  57. package/src/core/project-manager.ts +2 -1
  58. package/src/core/pull-request.ts +2 -2
  59. package/src/core/state-derivation.ts +3 -3
  60. package/src/parsers/codex-stream-renderer.ts +106 -4
  61. package/src/prompts/amend.ts +1 -4
  62. package/src/prompts/config-docs.md +1 -1
  63. package/src/prompts/planning.ts +2 -4
  64. package/src/types/config.ts +0 -1
  65. package/src/utils/paths.ts +10 -0
@@ -10,10 +10,10 @@ 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, formatProjectChoice } from '../ui/project-picker.js';
13
+ import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects } from '../ui/project-picker.js';
14
14
  import type { PendingProjectInfo } from '../ui/project-picker.js';
15
15
  import { logger } from '../utils/logger.js';
16
- import { getConfig, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
16
+ import { getConfig, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
17
17
  import type { PlanFrontmatter } from '../utils/frontmatter.js';
18
18
  import { getVersion } from '../utils/version.js';
19
19
  import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
@@ -29,7 +29,6 @@ import { TokenTracker } from '../utils/token-tracker.js';
29
29
  import { VerboseToggle } from '../utils/verbose-toggle.js';
30
30
  import {
31
31
  deriveProjectState,
32
- discoverProjects,
33
32
  getNextExecutableTask,
34
33
  getDerivedStats,
35
34
  getDerivedStatsForTasks,
@@ -44,10 +43,6 @@ import {
44
43
  getRepoRoot,
45
44
  getRepoBasename,
46
45
  getCurrentBranch,
47
- computeWorktreePath,
48
- computeWorktreeBaseDir,
49
- validateWorktree,
50
- listWorktreeProjects,
51
46
  mergeWorktreeBranch,
52
47
  removeWorktree,
53
48
  resolveWorktreeProjectByIdentifier,
@@ -97,6 +92,7 @@ function resolveTaskModel(
97
92
  frontmatterWarnings: string[] | undefined,
98
93
  ceilingModel: string,
99
94
  isRetry: boolean,
95
+ provider?: import('../types/config.js').HarnessProvider,
100
96
  ): TaskModelResolution {
101
97
  const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
102
98
 
@@ -122,7 +118,7 @@ function resolveTaskModel(
122
118
 
123
119
  // Effort-based resolution - apply ceiling
124
120
  if (frontmatter.effort) {
125
- const mappedModel = resolveEffortToModel(frontmatter.effort);
121
+ const mappedModel = resolveEffortToModel(frontmatter.effort, provider);
126
122
  const model = applyModelCeiling(mappedModel, ceilingModel);
127
123
  return { model, missingFrontmatter: false, warnings };
128
124
  }
@@ -201,8 +197,6 @@ export function createDoCommand(): Command {
201
197
  .option('-f, --force', 'Re-run all tasks regardless of status')
202
198
  .option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
203
199
  .option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
204
- .option('-w, --worktree', 'Execute tasks in a git worktree')
205
- .option('--no-worktree', 'Disable worktree mode (overrides config)')
206
200
  .option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
207
201
  .action(async (project: string | undefined, options: DoCommandOptions) => {
208
202
  await runDoCommand(project, options);
@@ -214,8 +208,6 @@ export function createDoCommand(): Command {
214
208
  async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
215
209
  const rafDir = getRafDir();
216
210
  let projectIdentifier = projectIdentifierArg;
217
- let worktreeMode = options.worktree ?? getWorktreeDefault();
218
-
219
211
  // Validate and resolve model option
220
212
  let model: string;
221
213
  try {
@@ -225,48 +217,11 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
225
217
  process.exit(1);
226
218
  }
227
219
 
228
- // Variables for worktree context (set when --worktree is used)
220
+ // Variables for worktree context (derived from where the project is found)
229
221
  let worktreeRoot: string | undefined;
230
222
  let originalBranch: string | undefined;
231
223
  let mainBranchName: string | null = null;
232
224
 
233
- if (worktreeMode) {
234
- // Validate git repo
235
- const repoRoot = getRepoRoot();
236
- if (!repoRoot) {
237
- logger.error('--worktree requires a git repository');
238
- process.exit(1);
239
- }
240
- const repoBasename = getRepoBasename()!;
241
- const rafRelativePath = path.relative(repoRoot, rafDir);
242
-
243
- // Record original branch before any worktree operations
244
- originalBranch = getCurrentBranch() ?? undefined;
245
-
246
- // Sync main branch before worktree operations (if enabled)
247
- if (getSyncMainBranch()) {
248
- const syncResult = pullMainBranch();
249
- mainBranchName = syncResult.mainBranch;
250
- if (syncResult.success) {
251
- if (syncResult.hadChanges) {
252
- logger.info(`Synced ${syncResult.mainBranch} from remote`);
253
- }
254
- } else {
255
- logger.warn(`Could not sync main branch: ${syncResult.error}`);
256
- }
257
- }
258
-
259
- if (!projectIdentifier) {
260
- // Auto-discovery flow
261
- const selected = await discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath);
262
- if (!selected) {
263
- process.exit(0);
264
- }
265
- worktreeRoot = selected.worktreeRoot;
266
- projectIdentifier = selected.projectFolder;
267
- }
268
- }
269
-
270
225
  // Handle no project identifier (non-worktree mode) - show interactive picker
271
226
  if (!projectIdentifier) {
272
227
  // Discover worktree projects for the current repo (if in a git repo)
@@ -298,9 +253,8 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
298
253
  // Use the selected project
299
254
  projectIdentifier = selectedProject.folder;
300
255
 
301
- // If a worktree project was selected, auto-switch to worktree mode
256
+ // If a worktree project was selected, record worktree context
302
257
  if (selectedProject.source === 'worktree' && selectedProject.worktreeRoot) {
303
- worktreeMode = true;
304
258
  worktreeRoot = selectedProject.worktreeRoot;
305
259
  originalBranch = getCurrentBranch() ?? undefined;
306
260
  }
@@ -316,89 +270,20 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
316
270
  // Resolve project identifier
317
271
  let resolvedProject: { identifier: string; path: string; name: string } | undefined;
318
272
 
319
- if (worktreeMode) {
320
- // Worktree mode: resolve project inside the worktree
273
+ if (worktreeRoot) {
274
+ // Worktree was set by the picker — resolve project inside the worktree
321
275
  const repoRoot = getRepoRoot()!;
322
- const repoBasename = getRepoBasename()!;
323
276
  const rafRelativePath = path.relative(repoRoot, rafDir);
324
-
325
- // If worktreeRoot was set by auto-discovery, use it directly
326
- if (worktreeRoot) {
327
- const wtRafDir = path.join(worktreeRoot, rafRelativePath);
328
- const result = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
329
- if (!result.path) {
330
- logger.error(`Project not found in worktree: ${projectIdentifier}`);
331
- process.exit(1);
332
- }
333
- const projectName = extractProjectName(result.path) ?? projectIdentifier;
334
- resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
335
- } else {
336
- // Explicit identifier: resolve from main repo to get folder name, then validate worktree
337
- const mainResult = resolveProjectIdentifierWithDetails(rafDir, projectIdentifier);
338
-
339
- let projectFolderName: string;
340
- if (mainResult.path) {
341
- // Found in main repo - use its folder name
342
- projectFolderName = path.basename(mainResult.path);
343
- } else {
344
- // Not found in main repo - try to find it in worktrees directly
345
- // This handles projects that only exist in worktrees
346
- const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
347
- if (!fs.existsSync(worktreeBaseDir)) {
348
- logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
349
- process.exit(1);
350
- }
351
-
352
- // Search worktrees for the project
353
- const wtProjects = listWorktreeProjects(repoBasename);
354
- let found = false;
355
- for (const wtProjectDir of wtProjects) {
356
- const wtPath = computeWorktreePath(repoBasename, wtProjectDir);
357
- const wtRafDir = path.join(wtPath, rafRelativePath);
358
- if (!fs.existsSync(wtRafDir)) continue;
359
-
360
- const resolution = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
361
- if (resolution.path) {
362
- projectFolderName = path.basename(resolution.path);
363
- worktreeRoot = wtPath;
364
- found = true;
365
- break;
366
- }
367
- }
368
-
369
- if (!found) {
370
- logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
371
- process.exit(1);
372
- }
373
- }
374
-
375
- // Compute worktree path if not already set
376
- if (!worktreeRoot) {
377
- worktreeRoot = computeWorktreePath(repoBasename, projectFolderName!);
378
- }
379
-
380
- // Validate the worktree
381
- const wtProjectRelPath = path.join(rafRelativePath, projectFolderName!);
382
- const validation = validateWorktree(worktreeRoot, wtProjectRelPath);
383
-
384
- if (!validation.exists || !validation.isValidWorktree) {
385
- logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
386
- logger.error(`Expected worktree at: ${worktreeRoot}`);
387
- process.exit(1);
388
- }
389
-
390
- if (!validation.hasProjectFolder || !validation.hasPlans) {
391
- logger.error(`Worktree exists but project content is missing.`);
392
- logger.error(`Expected project folder at: ${validation.projectPath ?? path.join(worktreeRoot, wtProjectRelPath)}`);
393
- process.exit(1);
394
- }
395
-
396
- const projectPath = validation.projectPath!;
397
- const projectName = extractProjectName(projectPath) ?? projectIdentifier;
398
- resolvedProject = { identifier: projectIdentifier, path: projectPath, name: projectName };
277
+ const wtRafDir = path.join(worktreeRoot, rafRelativePath);
278
+ const result = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
279
+ if (!result.path) {
280
+ logger.error(`Project not found in worktree: ${projectIdentifier}`);
281
+ process.exit(1);
399
282
  }
283
+ const projectName = extractProjectName(result.path) ?? projectIdentifier;
284
+ resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
400
285
  } else {
401
- // Standard mode: check worktrees first (worktree takes priority), then main repo
286
+ // Auto-detect: check worktrees first (worktree takes priority), then main repo
402
287
  const repoRoot = getRepoRoot();
403
288
  const repoBasename = repoRoot ? getRepoBasename() : null;
404
289
 
@@ -411,8 +296,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
411
296
  const wtProjectPath = path.join(wtRafDir, wtResolution.folder);
412
297
 
413
298
  if (fs.existsSync(wtProjectPath)) {
414
- // Auto-switch to worktree mode
415
- worktreeMode = true;
416
299
  worktreeRoot = wtResolution.worktreeRoot;
417
300
  originalBranch = getCurrentBranch() ?? undefined;
418
301
 
@@ -456,9 +339,26 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
456
339
  // Configure logger
457
340
  logger.configure({ verbose, debug });
458
341
 
459
- // Show post-execution picker before task execution (worktree mode only)
342
+ // Worktree setup: sync main branch and show post-execution picker
460
343
  let postAction: PostExecutionAction = 'leave';
461
- if (worktreeMode && worktreeRoot) {
344
+ if (worktreeRoot) {
345
+ if (!originalBranch) {
346
+ originalBranch = getCurrentBranch() ?? undefined;
347
+ }
348
+
349
+ // Sync main branch before worktree operations (if enabled)
350
+ if (getSyncMainBranch()) {
351
+ const syncResult = pullMainBranch();
352
+ mainBranchName = syncResult.mainBranch;
353
+ if (syncResult.success) {
354
+ if (syncResult.hadChanges) {
355
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
356
+ }
357
+ } else {
358
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
359
+ }
360
+ }
361
+
462
362
  try {
463
363
  postAction = await pickPostExecutionAction(worktreeRoot);
464
364
  } catch (error) {
@@ -499,6 +399,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
499
399
  maxRetries,
500
400
  autoCommit,
501
401
  model,
402
+ provider: options.provider,
502
403
  worktreeCwd: worktreeRoot,
503
404
  }
504
405
  );
@@ -509,7 +410,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
509
410
  }
510
411
 
511
412
  // Execute post-execution action based on picker choice
512
- if (worktreeMode && worktreeRoot) {
413
+ if (worktreeRoot) {
513
414
  const worktreeBranch = path.basename(worktreeRoot);
514
415
 
515
416
  if (result.success) {
@@ -650,88 +551,6 @@ async function executePostAction(
650
551
  }
651
552
  }
652
553
 
653
- /**
654
- * Auto-discovery flow for `raf do --worktree` without a project identifier.
655
- * Shows both worktree AND main-repo pending projects in an interactive picker.
656
- * Worktree projects above a threshold filter are included; main-repo projects
657
- * are always included. Duplicates are deduped with worktree taking precedence.
658
- *
659
- * @returns Selected project info or null if cancelled/no projects
660
- */
661
- async function discoverAndPickWorktreeProject(
662
- repoBasename: string,
663
- rafDir: string,
664
- rafRelativePath: string,
665
- ): Promise<{ worktreeRoot?: string; projectFolder: string } | null> {
666
- // Get worktree pending projects (uses shared utility from project-picker)
667
- const wtPendingProjects = getPendingWorktreeProjects(repoBasename, rafRelativePath);
668
-
669
- // Find the highest-numbered completed project in the MAIN tree for threshold filter
670
- const mainProjects = discoverProjects(rafDir);
671
- let highestCompletedNumber = 0;
672
-
673
- for (const project of mainProjects) {
674
- const state = deriveProjectState(project.path);
675
- if (isProjectComplete(state) && state.tasks.length > 0) {
676
- if (project.number > highestCompletedNumber) {
677
- highestCompletedNumber = project.number;
678
- }
679
- }
680
- }
681
-
682
- // Filter threshold: highest completed - 3 (or 0 if none completed)
683
- const threshold = highestCompletedNumber > 3 ? highestCompletedNumber - 3 : 0;
684
-
685
- // Apply threshold filter to worktree projects only
686
- const filteredWtProjects = wtPendingProjects.filter((p) => p.number >= threshold);
687
-
688
- // Get main-repo pending projects (no threshold filter)
689
- const mainPendingProjects = getPendingProjects(rafDir);
690
-
691
- // Merge and deduplicate: worktree versions take precedence
692
- const allProjects = [...mainPendingProjects, ...filteredWtProjects];
693
- const seen = new Map<string, PendingProjectInfo>();
694
- for (const project of allProjects) {
695
- const existing = seen.get(project.folder);
696
- if (!existing || project.source === 'worktree') {
697
- seen.set(project.folder, project);
698
- }
699
- }
700
- const deduped = Array.from(seen.values());
701
-
702
- // Sort by project number
703
- deduped.sort((a, b) => a.number - b.number);
704
-
705
- if (deduped.length === 0) {
706
- logger.info('No pending projects found.');
707
- return null;
708
- }
709
-
710
- // Show interactive picker
711
- const choices = deduped.map((p) => ({
712
- name: formatProjectChoice(p),
713
- value: p,
714
- }));
715
-
716
- try {
717
- const selected = await select({
718
- message: 'Select a project to execute:',
719
- choices,
720
- });
721
-
722
- return {
723
- worktreeRoot: selected.worktreeRoot,
724
- projectFolder: selected.folder,
725
- };
726
- } catch (error) {
727
- // Handle Ctrl+C (user cancellation)
728
- if (error instanceof Error && error.message.includes('User force closed')) {
729
- return null;
730
- }
731
- throw error;
732
- }
733
- }
734
-
735
554
  interface SingleProjectOptions {
736
555
  timeout: number;
737
556
  verbose: boolean;
@@ -740,6 +559,8 @@ interface SingleProjectOptions {
740
559
  maxRetries: number;
741
560
  autoCommit: boolean;
742
561
  model: string;
562
+ /** CLI provider to use (claude, codex). */
563
+ provider?: import('../types/config.js').HarnessProvider;
743
564
  /** Worktree root directory. When set, the runner uses cwd in the worktree. */
744
565
  worktreeCwd?: string;
745
566
  }
@@ -749,7 +570,7 @@ async function executeSingleProject(
749
570
  projectName: string,
750
571
  options: SingleProjectOptions
751
572
  ): Promise<ProjectExecutionResult> {
752
- const { timeout, verbose, debug, force, maxRetries, autoCommit, model, worktreeCwd } = options;
573
+ const { timeout, verbose, debug, force, maxRetries, autoCommit, model, provider, worktreeCwd } = options;
753
574
 
754
575
  if (!validatePlansExist(projectPath)) {
755
576
  return {
@@ -1022,6 +843,7 @@ async function executeSingleProject(
1022
843
  undefined, // warnings already logged above
1023
844
  ceilingModel,
1024
845
  isRetry,
846
+ provider,
1025
847
  );
1026
848
 
1027
849
  // Update current model for timer callback display
@@ -1033,7 +855,7 @@ async function executeSingleProject(
1033
855
  }
1034
856
 
1035
857
  // Create a runner for this attempt's model
1036
- const taskRunner = createRunner({ model: modelResolution.model });
858
+ const taskRunner = createRunner({ model: modelResolution.model, provider });
1037
859
  shutdownHandler.registerClaudeRunner(taskRunner);
1038
860
 
1039
861
  if (verbose && isRetry) {