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
@@ -32,6 +32,7 @@ import {
32
32
  decodeTaskId,
33
33
  encodeTaskId,
34
34
  TASK_ID_PATTERN,
35
+ numericFileSort,
35
36
  } from '../utils/paths.js';
36
37
  import { sanitizeProjectName } from '../utils/validation.js';
37
38
  import {
@@ -43,11 +44,8 @@ import {
43
44
  getRepoBasename,
44
45
  getRepoRoot,
45
46
  createWorktree,
46
- createWorktreeFromBranch,
47
- branchExists,
48
47
  validateWorktree,
49
48
  removeWorktree,
50
- computeWorktreeBaseDir,
51
49
  pullMainBranch,
52
50
  resolveWorktreeProjectByIdentifier,
53
51
  } from '../core/worktree.js';
@@ -90,8 +88,10 @@ export function createPlanCommand(): Command {
90
88
  const autoMode = options.auto ?? false;
91
89
  const worktreeMode = options.worktree ?? getWorktreeDefault();
92
90
 
91
+ const provider = options.provider as import('../types/config.js').HarnessProvider | undefined;
92
+
93
93
  if (options.resume) {
94
- await runResumeCommand(options.resume, model);
94
+ await runResumeCommand(options.resume, model, provider);
95
95
  } else if (options.amend) {
96
96
  if (!projectName) {
97
97
  logger.error('--amend requires a project identifier');
@@ -99,16 +99,16 @@ export function createPlanCommand(): Command {
99
99
  logger.error(' or: raf plan --amend <project>');
100
100
  process.exit(1);
101
101
  }
102
- await runAmendCommand(projectName, model, autoMode, worktreeMode);
102
+ await runAmendCommand(projectName, model, autoMode, provider);
103
103
  } else {
104
- await runPlanCommand(projectName, model, autoMode, worktreeMode);
104
+ await runPlanCommand(projectName, model, autoMode, worktreeMode, provider);
105
105
  }
106
106
  });
107
107
 
108
108
  return command;
109
109
  }
110
110
 
111
- async function runPlanCommand(projectName?: string, model?: string, autoMode: boolean = false, worktreeMode: boolean = false): Promise<void> {
111
+ async function runPlanCommand(projectName?: string, model?: string, autoMode: boolean = false, worktreeMode: boolean = false, provider?: import('../types/config.js').HarnessProvider): Promise<void> {
112
112
  // Validate environment
113
113
  const validation = validateEnvironment();
114
114
  reportValidation(validation);
@@ -124,7 +124,6 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
124
124
  const mainResult = resolveProjectIdentifierWithDetails(rafDir, projectName);
125
125
 
126
126
  let existingFolder: string | null = null;
127
- let existingWorktreeMode = false;
128
127
 
129
128
  if (mainResult.path) {
130
129
  existingFolder = path.basename(mainResult.path);
@@ -134,7 +133,6 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
134
133
  const wtResult = resolveWorktreeProjectByIdentifier(repoBasename, projectName);
135
134
  if (wtResult) {
136
135
  existingFolder = wtResult.folder;
137
- existingWorktreeMode = true;
138
136
  }
139
137
  }
140
138
  }
@@ -153,7 +151,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
153
151
  });
154
152
 
155
153
  if (answer === 'amend') {
156
- await runAmendCommand(existingFolder, model, autoMode, existingWorktreeMode);
154
+ await runAmendCommand(existingFolder, model, autoMode, provider);
157
155
  return;
158
156
  } else if (answer === 'cancel') {
159
157
  logger.info('Aborted.');
@@ -286,7 +284,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
286
284
  }
287
285
 
288
286
  // Set up shutdown handler
289
- const claudeRunner = createRunner({ model });
287
+ const claudeRunner = createRunner({ model, provider });
290
288
  shutdownHandler.init();
291
289
  shutdownHandler.registerClaudeRunner(claudeRunner);
292
290
 
@@ -342,7 +340,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
342
340
  // Check for created plan files
343
341
  const plansDir = getPlansDir(projectPath);
344
342
  const planFiles = fs.existsSync(plansDir)
345
- ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
343
+ ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort(numericFileSort)
346
344
  : [];
347
345
 
348
346
  if (planFiles.length === 0) {
@@ -367,7 +365,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
367
365
  if (worktreeMode) {
368
366
  logger.info(`Worktree: ${worktreePath}`);
369
367
  logger.info(`Branch: ${worktreeBranch}`);
370
- logger.info(`Run 'raf do ${finalProjectName} --worktree' to execute the plans.`);
368
+ logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
371
369
  } else {
372
370
  logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
373
371
  }
@@ -397,7 +395,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
397
395
  }
398
396
  }
399
397
 
400
- async function runAmendCommand(identifier: string, model?: string, autoMode: boolean = false, worktreeMode: boolean = false): Promise<void> {
398
+ async function runAmendCommand(identifier: string, model?: string, autoMode: boolean = false, provider?: import('../types/config.js').HarnessProvider): Promise<void> {
401
399
  // Validate environment
402
400
  const validation = validateEnvironment();
403
401
  reportValidation(validation);
@@ -406,112 +404,26 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
406
404
  process.exit(1);
407
405
  }
408
406
 
407
+ // Auto-detect project location: check worktrees first, then main repo
409
408
  let worktreePath: string | null = null;
410
- let projectPath: string;
411
-
412
- if (worktreeMode) {
413
- // Worktree mode: resolve project from worktree directory
414
- const repoBasename = getRepoBasename();
415
- if (!repoBasename) {
416
- logger.error('--worktree requires a git repository');
417
- process.exit(1);
418
- }
419
-
420
- const repoRoot = getRepoRoot()!;
421
- const rafDir = getRafDir();
422
- const rafRelativePath = path.relative(repoRoot, rafDir);
423
-
424
- const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
425
-
426
- // Search through existing worktree directories for the project
427
- let matchedWorktreeDir: string | null = null;
428
- let matchedProjectPath: string | null = null;
429
-
430
- if (fs.existsSync(worktreeBaseDir)) {
431
- const entries = fs.readdirSync(worktreeBaseDir, { withFileTypes: true });
432
-
433
- for (const entry of entries) {
434
- if (!entry.isDirectory()) continue;
435
- const wtPath = path.join(worktreeBaseDir, entry.name);
436
- const wtRafDir = path.join(wtPath, rafRelativePath);
437
- if (!fs.existsSync(wtRafDir)) continue;
438
-
439
- const resolution = resolveProjectIdentifierWithDetails(wtRafDir, identifier);
440
- if (resolution.path) {
441
- if (matchedWorktreeDir) {
442
- logger.error(`Ambiguous: project "${identifier}" found in multiple worktrees`);
443
- process.exit(1);
444
- }
445
- matchedWorktreeDir = wtPath;
446
- matchedProjectPath = resolution.path;
447
- }
448
- }
449
- }
450
-
451
- if (!matchedWorktreeDir || !matchedProjectPath) {
452
- // Worktree not found — try to recreate it
453
- // First, resolve the project from the main repo to get the folder name
454
- const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
455
- if (!mainResolution.path) {
456
- logger.error(`Project not found in any worktree or main repo: ${identifier}`);
457
- logger.error(`Searched in: ${worktreeBaseDir}`);
458
- process.exit(1);
459
- }
460
-
461
- const folderName = path.basename(mainResolution.path);
409
+ let projectPath: string | undefined;
462
410
 
463
- if (branchExists(folderName)) {
464
- // Branch exists — recreate worktree from it
465
- const result = createWorktreeFromBranch(repoBasename, folderName);
466
- if (!result.success) {
467
- logger.error(`Failed to recreate worktree from branch: ${result.error}`);
468
- process.exit(1);
469
- }
470
- matchedWorktreeDir = result.worktreePath;
471
- matchedProjectPath = path.join(result.worktreePath, rafRelativePath, folderName);
472
- logger.info(`Recreated worktree from branch: ${folderName}`);
473
- } else {
474
- // No branch — create fresh worktree and copy project files
475
- // Sync main branch before creating worktree (if enabled)
476
- if (getSyncMainBranch()) {
477
- const syncResult = pullMainBranch();
478
- if (syncResult.success) {
479
- if (syncResult.hadChanges) {
480
- logger.info(`Synced ${syncResult.mainBranch} from remote`);
481
- }
482
- } else {
483
- logger.warn(`Could not sync main branch: ${syncResult.error}`);
484
- }
485
- }
411
+ const repoBasename = getRepoBasename();
412
+ const rafDir = getRafDir();
486
413
 
487
- const result = createWorktree(repoBasename, folderName);
488
- if (!result.success) {
489
- logger.error(`Failed to create worktree: ${result.error}`);
490
- process.exit(1);
491
- }
492
- matchedWorktreeDir = result.worktreePath;
493
- const wtProjectPath = path.join(result.worktreePath, rafRelativePath, folderName);
494
- // Copy project folder from main repo into the new worktree
495
- fs.cpSync(mainResolution.path, wtProjectPath, { recursive: true });
496
- matchedProjectPath = wtProjectPath;
497
- logger.info(`Created fresh worktree and copied project files: ${folderName}`);
498
- }
414
+ // 1. Try worktree resolution first (if we're in a git repo)
415
+ if (repoBasename) {
416
+ const wtResult = resolveWorktreeProjectByIdentifier(repoBasename, identifier);
417
+ if (wtResult) {
418
+ const repoRoot = getRepoRoot()!;
419
+ const rafRelativePath = path.relative(repoRoot, rafDir);
420
+ worktreePath = wtResult.worktreeRoot;
421
+ projectPath = path.join(wtResult.worktreeRoot, rafRelativePath, wtResult.folder);
499
422
  }
423
+ }
500
424
 
501
- worktreePath = matchedWorktreeDir;
502
- projectPath = matchedProjectPath;
503
-
504
- // Validate the worktree is valid
505
- const relProjectPath = path.relative(worktreePath, projectPath);
506
- const wtValidation = validateWorktree(worktreePath, relProjectPath);
507
- if (!wtValidation.isValidWorktree) {
508
- logger.error(`Invalid worktree at: ${worktreePath}`);
509
- logger.error('The worktree may have been removed or corrupted.');
510
- process.exit(1);
511
- }
512
- } else {
513
- // Standard mode: resolve from main repo
514
- const rafDir = getRafDir();
425
+ // 2. Fall back to main repo
426
+ if (!projectPath) {
515
427
  const resolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
516
428
 
517
429
  if (!resolution.path) {
@@ -570,7 +482,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
570
482
  // Show existing tasks summary
571
483
  logger.info('Amending existing project:');
572
484
  logger.info(` Path: ${projectPath}`);
573
- if (worktreeMode && worktreePath) {
485
+ if (worktreePath) {
574
486
  logger.info(` Worktree: ${worktreePath}`);
575
487
  }
576
488
  logger.info(` Existing tasks: ${existingTasks.length}`);
@@ -616,7 +528,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
616
528
  fs.writeFileSync(inputPath, updatedInput);
617
529
 
618
530
  // Set up shutdown handler
619
- const claudeRunner = createRunner({ model });
531
+ const claudeRunner = createRunner({ model, provider });
620
532
  shutdownHandler.init();
621
533
  shutdownHandler.registerClaudeRunner(claudeRunner);
622
534
 
@@ -636,7 +548,6 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
636
548
  existingTasks,
637
549
  nextTaskNumber,
638
550
  newTaskDescription: cleanInput,
639
- worktreeMode,
640
551
  });
641
552
 
642
553
  try {
@@ -652,7 +563,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
652
563
 
653
564
  // Check for new plan files
654
565
  const allPlanFiles = fs.existsSync(plansDir)
655
- ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
566
+ ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort(numericFileSort)
656
567
  : [];
657
568
 
658
569
  const newPlanFiles = allPlanFiles.filter((f) => {
@@ -687,11 +598,7 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
687
598
  if (newPlanFiles.length > 0) {
688
599
  logger.newline();
689
600
  logger.info(`Total tasks: ${allPlanFiles.length}`);
690
- if (worktreeMode) {
691
- logger.info(`Run 'raf do ${identifier} --worktree' to execute the new tasks.`);
692
- } else {
693
- logger.info(`Run 'raf do ${identifier}' to execute the new tasks.`);
694
- }
601
+ logger.info(`Run 'raf do ${identifier}' to execute the new tasks.`);
695
602
  }
696
603
  } catch (error) {
697
604
  logger.error(`Amendment failed: ${error}`);
@@ -729,7 +636,7 @@ ${taskList}
729
636
  `;
730
637
  }
731
638
 
732
- async function runResumeCommand(identifier: string, model?: string): Promise<void> {
639
+ async function runResumeCommand(identifier: string, model?: string, provider?: import('../types/config.js').HarnessProvider): Promise<void> {
733
640
  // Validate environment
734
641
  const validation = validateEnvironment();
735
642
  reportValidation(validation);
@@ -799,7 +706,7 @@ async function runResumeCommand(identifier: string, model?: string): Promise<voi
799
706
  logger.newline();
800
707
 
801
708
  // Set up shutdown handler
802
- const claudeRunner = createRunner({ model });
709
+ const claudeRunner = createRunner({ model, provider });
803
710
  shutdownHandler.init();
804
711
  shutdownHandler.registerClaudeRunner(claudeRunner);
805
712
 
@@ -817,7 +724,7 @@ async function runResumeCommand(identifier: string, model?: string): Promise<voi
817
724
  // Check for created/updated plan files after resume session
818
725
  const plansDir = getPlansDir(projectPath);
819
726
  const planFiles = fs.existsSync(plansDir)
820
- ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
727
+ ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort(numericFileSort)
821
728
  : [];
822
729
 
823
730
  if (planFiles.length > 0) {
@@ -12,6 +12,7 @@ import {
12
12
  getInputPath,
13
13
  listProjects,
14
14
  TASK_ID_PATTERN,
15
+ numericFileSort,
15
16
  } from '../utils/paths.js';
16
17
  import { sanitizeProjectName } from '../utils/validation.js';
17
18
  import { logger } from '../utils/logger.js';
@@ -143,7 +144,7 @@ export class ProjectManager {
143
144
  }
144
145
 
145
146
  const outcomes: Array<{ taskId: string; content: string }> = [];
146
- const files = fs.readdirSync(outcomesDir).filter((f) => f.endsWith('.md')).sort();
147
+ const files = fs.readdirSync(outcomesDir).filter((f) => f.endsWith('.md')).sort(numericFileSort);
147
148
 
148
149
  for (const file of files) {
149
150
  const match = file.match(new RegExp(`^(${TASK_ID_PATTERN})-`));
@@ -4,7 +4,7 @@ import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import { logger } from '../utils/logger.js';
6
6
  import { getModel, getModelShortName } from '../utils/config.js';
7
- import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN } from '../utils/paths.js';
7
+ import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN, numericFileSort } from '../utils/paths.js';
8
8
 
9
9
  export interface PrCreateResult {
10
10
  success: boolean;
@@ -208,7 +208,7 @@ export function readProjectContext(projectPath: string): {
208
208
 
209
209
  const outcomesDir = getOutcomesDir(projectPath);
210
210
  if (fs.existsSync(outcomesDir)) {
211
- const files = fs.readdirSync(outcomesDir).filter(f => f.endsWith('.md')).sort();
211
+ const files = fs.readdirSync(outcomesDir).filter(f => f.endsWith('.md')).sort(numericFileSort);
212
212
  const taskIdPattern = new RegExp(`^(${TASK_ID_PATTERN})-`);
213
213
  for (const file of files) {
214
214
  const match = file.match(taskIdPattern);
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import { getPlansDir, getOutcomesDir, getInputPath, TASK_ID_PATTERN } from '../utils/paths.js';
3
+ import { getPlansDir, getOutcomesDir, getInputPath, TASK_ID_PATTERN, numericFileSort } from '../utils/paths.js';
4
4
  import { parsePlanFrontmatter, type PlanFrontmatter } from '../utils/frontmatter.js';
5
5
 
6
6
  export type DerivedTaskStatus = 'pending' | 'completed' | 'failed' | 'blocked';
@@ -198,14 +198,14 @@ export function deriveProjectState(projectPath: string): DerivedProjectState {
198
198
 
199
199
  const planFiles = fs.readdirSync(plansDir)
200
200
  .filter((f) => f.endsWith('.md'))
201
- .sort();
201
+ .sort(numericFileSort);
202
202
 
203
203
  // Build a map of outcome statuses
204
204
  const outcomeStatuses = new Map<string, DerivedTaskStatus>();
205
205
  if (fs.existsSync(outcomesDir)) {
206
206
  const outcomeFiles = fs.readdirSync(outcomesDir)
207
207
  .filter((f) => f.endsWith('.md'))
208
- .sort();
208
+ .sort(numericFileSort);
209
209
 
210
210
  for (const outcomeFile of outcomeFiles) {
211
211
  const match = outcomeFile.match(new RegExp(`^(${TASK_ID_PATTERN})-`));
@@ -13,17 +13,34 @@ import type { RenderResult } from './stream-renderer.js';
13
13
 
14
14
  export interface CodexEvent {
15
15
  type: string;
16
- /** AgentMessage fields */
16
+ /** Claude flat-format fields (AgentMessage) */
17
17
  content?: string;
18
- /** CommandExecution fields */
18
+ /** Claude flat-format fields (CommandExecution) */
19
19
  command?: string;
20
20
  exit_code?: number;
21
- /** FileChange fields */
21
+ /** Claude flat-format fields (FileChange) */
22
22
  file_path?: string;
23
23
  change_kind?: string;
24
- /** McpToolCall fields */
24
+ /** Claude flat-format fields (McpToolCall) */
25
25
  tool_name?: string;
26
26
  server_name?: string;
27
+ /** Real Codex nested item (for item.completed / item.started) */
28
+ item?: {
29
+ type: string;
30
+ text?: string;
31
+ command?: string;
32
+ exit_code?: number;
33
+ path?: string;
34
+ change_kind?: string;
35
+ };
36
+ /** Error message (for error / turn.failed events) */
37
+ message?: string;
38
+ /** Usage data (for turn.completed events) */
39
+ usage?: {
40
+ input_tokens?: number;
41
+ output_tokens?: number;
42
+ total_tokens?: number;
43
+ };
27
44
  }
28
45
 
29
46
  function truncate(text: string, maxLen: number): string {
@@ -65,6 +82,23 @@ export function renderCodexStreamEvent(line: string): RenderResult {
65
82
  // Skip todo list events - minimal noise
66
83
  return { display: '', textContent: '' };
67
84
 
85
+ // Real Codex CLI event types (nested format)
86
+ case 'item.completed':
87
+ return renderItemCompleted(event);
88
+
89
+ case 'item.started':
90
+ // Skip started events — we render on completion
91
+ return { display: '', textContent: '' };
92
+
93
+ case 'turn.completed':
94
+ return renderTurnCompleted(event);
95
+
96
+ case 'error':
97
+ return renderError(event);
98
+
99
+ case 'turn.failed':
100
+ return renderTurnFailed(event);
101
+
68
102
  default:
69
103
  // Skip unknown event types gracefully
70
104
  return { display: '', textContent: '' };
@@ -106,3 +140,71 @@ function renderMcpToolCall(event: CodexEvent): RenderResult {
106
140
  textContent: '',
107
141
  };
108
142
  }
143
+
144
+ // --- Real Codex CLI event handlers (nested format) ---
145
+
146
+ function renderItemCompleted(event: CodexEvent): RenderResult {
147
+ const item = event.item;
148
+ if (!item) return { display: '', textContent: '' };
149
+
150
+ switch (item.type) {
151
+ case 'agent_message': {
152
+ const text = item.text ?? '';
153
+ return {
154
+ display: text ? text + '\n' : '',
155
+ textContent: text,
156
+ };
157
+ }
158
+ case 'command_execution': {
159
+ const cmd = item.command ?? '';
160
+ const exitCode = item.exit_code ?? 0;
161
+ const status = exitCode === 0 ? '✓' : `✗ exit ${exitCode}`;
162
+ return {
163
+ display: ` → Running: ${truncate(cmd, 120)} [${status}]\n`,
164
+ textContent: '',
165
+ };
166
+ }
167
+ case 'file_change': {
168
+ const filePath = item.path ?? 'unknown';
169
+ const kind = item.change_kind ?? 'modified';
170
+ return {
171
+ display: ` → File ${kind}: ${filePath}\n`,
172
+ textContent: '',
173
+ };
174
+ }
175
+ default:
176
+ return { display: '', textContent: '' };
177
+ }
178
+ }
179
+
180
+ function renderTurnCompleted(event: CodexEvent): RenderResult {
181
+ if (event.usage) {
182
+ const { input_tokens, output_tokens } = event.usage;
183
+ const parts: string[] = [];
184
+ if (input_tokens) parts.push(`in: ${input_tokens}`);
185
+ if (output_tokens) parts.push(`out: ${output_tokens}`);
186
+ if (parts.length > 0) {
187
+ return {
188
+ display: ` → Usage: ${parts.join(', ')}\n`,
189
+ textContent: '',
190
+ };
191
+ }
192
+ }
193
+ return { display: '', textContent: '' };
194
+ }
195
+
196
+ function renderError(event: CodexEvent): RenderResult {
197
+ const msg = event.message ?? 'Unknown error';
198
+ return {
199
+ display: ` ✗ Error: ${msg}\n`,
200
+ textContent: msg,
201
+ };
202
+ }
203
+
204
+ function renderTurnFailed(event: CodexEvent): RenderResult {
205
+ const msg = event.message ?? 'Turn failed';
206
+ return {
207
+ display: ` ✗ Failed: ${msg}\n`,
208
+ textContent: msg,
209
+ };
210
+ }
@@ -6,7 +6,6 @@ export interface AmendPromptParams {
6
6
  existingTasks: Array<DerivedTask & { taskName: string }>;
7
7
  nextTaskNumber: number;
8
8
  newTaskDescription: string;
9
- worktreeMode?: boolean;
10
9
  }
11
10
 
12
11
  export interface AmendPromptResult {
@@ -25,9 +24,7 @@ export function getAmendPrompt(params: AmendPromptParams): AmendPromptResult {
25
24
  existingTasks,
26
25
  nextTaskNumber,
27
26
  newTaskDescription,
28
- worktreeMode,
29
27
  } = params;
30
- const worktreeFlag = worktreeMode ? ' --worktree' : '';
31
28
 
32
29
  const existingTasksSummary = existingTasks
33
30
  .map((task) => {
@@ -210,7 +207,7 @@ After creating all new plan files:
210
207
  \`\`\`
211
208
  Planning complete! To exit this session and run your tasks:
212
209
  1. Press Ctrl-C twice to exit
213
- 2. Then run: raf do <project>${worktreeFlag}
210
+ 2. Then run: raf do <project>
214
211
  \`\`\`
215
212
 
216
213
  ## Important Rules
@@ -90,7 +90,7 @@ Example:
90
90
 
91
91
  - **Type**: boolean
92
92
  - **Default**: `false`
93
- - **Description**: When `true`, `raf plan` and `raf do` default to worktree mode (isolated git worktree). Can be overridden per-command with `--worktree` flag.
93
+ - **Description**: When `true`, `raf plan` defaults to worktree mode (isolated git worktree). Can be overridden with `--worktree` flag. `raf do` auto-detects worktree projects regardless of this setting.
94
94
 
95
95
  ### `syncMainBranch` — Sync Main Branch with Remote
96
96
 
@@ -15,9 +15,7 @@ export interface PlanningPromptResult {
15
15
  * - userMessage: Reference to input.md file (via positional argument, triggers the LLM to start)
16
16
  */
17
17
  export function getPlanningPrompt(params: PlanningPromptParams): PlanningPromptResult {
18
- const { projectPath, worktreeMode } = params;
19
- const worktreeFlag = worktreeMode ? ' --worktree' : '';
20
-
18
+ const { projectPath } = params;
21
19
  const systemPrompt = `You are a project planning assistant for RAF (Ralph's Automation Framework). Your task is to analyze the user's project description and create detailed task plans.
22
20
 
23
21
  ## Your Goals
@@ -177,7 +175,7 @@ After creating all plan files:
177
175
  \`\`\`
178
176
  Planning complete! To exit this session and run your tasks:
179
177
  1. Press Ctrl-C twice to exit
180
- 2. Then run: raf do <project>${worktreeFlag}
178
+ 2. Then run: raf do <project>
181
179
  \`\`\`
182
180
 
183
181
  ## Important Rules
@@ -169,7 +169,6 @@ export interface DoCommandOptions {
169
169
  force?: boolean;
170
170
  model?: ClaudeModelName;
171
171
  sonnet?: boolean;
172
- worktree?: boolean;
173
172
  provider?: HarnessProvider;
174
173
  }
175
174
 
@@ -214,6 +214,16 @@ export function listProjects(rafDir: string): Array<{ number: number; name: stri
214
214
  return projects.sort((a, b) => a.number - b.number);
215
215
  }
216
216
 
217
+ /**
218
+ * Numeric comparator for filenames with a leading integer prefix (e.g. "10-task.md").
219
+ * Ensures files sort as 1, 2, 3, ..., 10, ... instead of 1, 10, 2, ...
220
+ */
221
+ export function numericFileSort(a: string, b: string): number {
222
+ const numA = parseInt(a, 10);
223
+ const numB = parseInt(b, 10);
224
+ return numA - numB;
225
+ }
226
+
217
227
  export function getPlansDir(projectPath: string): string {
218
228
  return path.join(projectPath, 'plans');
219
229
  }