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
package/src/commands/plan.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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}
|
|
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,
|
|
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
|
-
|
|
464
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
502
|
-
|
|
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 (
|
|
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
|
-
|
|
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})-`));
|
package/src/core/pull-request.ts
CHANGED
|
@@ -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
|
-
/**
|
|
16
|
+
/** Claude flat-format fields (AgentMessage) */
|
|
17
17
|
content?: string;
|
|
18
|
-
/**
|
|
18
|
+
/** Claude flat-format fields (CommandExecution) */
|
|
19
19
|
command?: string;
|
|
20
20
|
exit_code?: number;
|
|
21
|
-
/**
|
|
21
|
+
/** Claude flat-format fields (FileChange) */
|
|
22
22
|
file_path?: string;
|
|
23
23
|
change_kind?: string;
|
|
24
|
-
/**
|
|
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
|
+
}
|
package/src/prompts/amend.ts
CHANGED
|
@@ -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
|
|
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`
|
|
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
|
|
package/src/prompts/planning.ts
CHANGED
|
@@ -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
|
|
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
|
|
178
|
+
2. Then run: raf do <project>
|
|
181
179
|
\`\`\`
|
|
182
180
|
|
|
183
181
|
## Important Rules
|
package/src/types/config.ts
CHANGED
package/src/utils/paths.ts
CHANGED
|
@@ -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
|
}
|