rafcode 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +19 -4
- package/RAF/ahtahs-token-reaper/decisions.md +37 -0
- package/RAF/ahtahs-token-reaper/input.md +20 -0
- package/RAF/ahtahs-token-reaper/outcomes/01-extend-token-tracker-data-model.md +42 -0
- package/RAF/ahtahs-token-reaper/outcomes/02-accumulate-usage-in-retry-loop.md +31 -0
- package/RAF/ahtahs-token-reaper/outcomes/03-per-attempt-display-formatting.md +60 -0
- package/RAF/ahtahs-token-reaper/outcomes/04-add-model-name-to-claude-call-logs.md +57 -0
- package/RAF/ahtahs-token-reaper/outcomes/05-handle-invalid-config-in-raf-config.md +46 -0
- package/RAF/ahtahs-token-reaper/outcomes/06-fix-verbose-toggle-timer-display.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/01-extend-token-tracker-data-model.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/02-accumulate-usage-in-retry-loop.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/03-per-attempt-display-formatting.md +43 -0
- package/RAF/ahtahs-token-reaper/plans/04-add-model-name-to-claude-call-logs.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/05-handle-invalid-config-in-raf-config.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/06-fix-verbose-toggle-timer-display.md +40 -0
- package/RAF/ahvrih-rate-forge/decisions.md +70 -0
- package/RAF/ahvrih-rate-forge/input.md +44 -0
- package/RAF/ahvrih-rate-forge/outcomes/01-remove-claude-command-config.md +58 -0
- package/RAF/ahvrih-rate-forge/outcomes/02-fix-mixed-attempt-cost.md +46 -0
- package/RAF/ahvrih-rate-forge/outcomes/03-rate-limit-estimation.md +82 -0
- package/RAF/ahvrih-rate-forge/outcomes/04-show-version-in-do-logs.md +45 -0
- package/RAF/ahvrih-rate-forge/outcomes/05-sync-main-before-worktree.md +96 -0
- package/RAF/ahvrih-rate-forge/outcomes/06-sync-readme-with-codebase.md +45 -0
- package/RAF/ahvrih-rate-forge/outcomes/07-no-session-persistence.md +26 -0
- package/RAF/ahvrih-rate-forge/outcomes/08-plan-execution-metadata.md +130 -0
- package/RAF/ahvrih-rate-forge/plans/01-remove-claude-command-config.md +36 -0
- package/RAF/ahvrih-rate-forge/plans/02-fix-mixed-attempt-cost.md +33 -0
- package/RAF/ahvrih-rate-forge/plans/03-rate-limit-estimation.md +82 -0
- package/RAF/ahvrih-rate-forge/plans/04-show-version-in-do-logs.md +32 -0
- package/RAF/ahvrih-rate-forge/plans/05-sync-main-before-worktree.md +40 -0
- package/RAF/ahvrih-rate-forge/plans/06-sync-readme-with-codebase.md +61 -0
- package/RAF/ahvrih-rate-forge/plans/07-no-session-persistence.md +28 -0
- package/RAF/ahvrih-rate-forge/plans/08-plan-execution-metadata.md +123 -0
- package/README.md +27 -7
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +24 -7
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +122 -27
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +79 -3
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +6 -6
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +9 -10
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +3 -3
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/pull-request.d.ts.map +1 -1
- package/dist/core/pull-request.js +5 -3
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/state-derivation.d.ts +5 -0
- package/dist/core/state-derivation.d.ts.map +1 -1
- package/dist/core/state-derivation.js +14 -4
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/core/worktree.d.ts +32 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +215 -0
- package/dist/core/worktree.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +26 -11
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +26 -11
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +30 -13
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +14 -10
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +53 -4
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +197 -30
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +43 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +85 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +2 -3
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/session-parser.d.ts +44 -0
- package/dist/utils/session-parser.d.ts.map +1 -0
- package/dist/utils/session-parser.js +122 -0
- package/dist/utils/session-parser.js.map +1 -0
- package/dist/utils/terminal-symbols.d.ts +28 -5
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +77 -18
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +31 -1
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +94 -4
- package/dist/utils/token-tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +26 -7
- package/src/commands/do.ts +157 -29
- package/src/commands/plan.ts +89 -2
- package/src/core/claude-runner.ts +16 -17
- package/src/core/failure-analyzer.ts +3 -3
- package/src/core/pull-request.ts +5 -3
- package/src/core/state-derivation.ts +20 -4
- package/src/core/worktree.ts +230 -0
- package/src/prompts/amend.ts +26 -11
- package/src/prompts/config-docs.md +91 -29
- package/src/prompts/planning.ts +26 -11
- package/src/types/config.ts +46 -21
- package/src/utils/config.ts +222 -33
- package/src/utils/frontmatter.ts +110 -0
- package/src/utils/name-generator.ts +2 -3
- package/src/utils/session-parser.ts +161 -0
- package/src/utils/terminal-symbols.ts +105 -18
- package/src/utils/token-tracker.ts +109 -4
- package/tests/unit/claude-runner-interactive.test.ts +8 -6
- package/tests/unit/claude-runner.test.ts +5 -66
- package/tests/unit/config-command.test.ts +84 -5
- package/tests/unit/config.test.ts +292 -45
- package/tests/unit/frontmatter.test.ts +182 -0
- package/tests/unit/post-execution-picker.test.ts +5 -0
- package/tests/unit/session-parser.test.ts +301 -0
- package/tests/unit/terminal-symbols.test.ts +263 -33
- package/tests/unit/timer-verbose-integration.test.ts +170 -0
- package/tests/unit/token-tracker.test.ts +653 -17
- package/tests/unit/validation.test.ts +6 -4
- package/tests/unit/worktree.test.ts +242 -0
package/src/core/pull-request.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
-
import { getModel,
|
|
6
|
+
import { getModel, getModelShortName } from '../utils/config.js';
|
|
7
7
|
import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN } from '../utils/paths.js';
|
|
8
8
|
|
|
9
9
|
export interface PrCreateResult {
|
|
@@ -279,6 +279,8 @@ Respond with ONLY the PR body in this exact format (no extra text, no code fence
|
|
|
279
279
|
[1-3 bullet points describing how to verify these changes work correctly]`;
|
|
280
280
|
|
|
281
281
|
try {
|
|
282
|
+
const prModel = getModelShortName(getModel('prGeneration'));
|
|
283
|
+
logger.info(`Generating PR with ${prModel}...`);
|
|
282
284
|
const body = await callClaudeForPrBody(prompt, timeoutMs);
|
|
283
285
|
return body;
|
|
284
286
|
} catch (error) {
|
|
@@ -352,10 +354,9 @@ export function filterClaudeOutput(output: string): string {
|
|
|
352
354
|
* Call Claude to generate a PR body.
|
|
353
355
|
*/
|
|
354
356
|
async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<string> {
|
|
355
|
-
const cmd = getClaudeCommand();
|
|
356
357
|
let claudePath: string;
|
|
357
358
|
try {
|
|
358
|
-
claudePath = execSync(
|
|
359
|
+
claudePath = execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
359
360
|
} catch {
|
|
360
361
|
throw new Error('Claude CLI not found');
|
|
361
362
|
}
|
|
@@ -367,6 +368,7 @@ async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<s
|
|
|
367
368
|
const prModel = getModel('prGeneration');
|
|
368
369
|
const proc = spawn(claudePath, [
|
|
369
370
|
'--model', prModel,
|
|
371
|
+
'--no-session-persistence',
|
|
370
372
|
'--dangerously-skip-permissions',
|
|
371
373
|
'-p',
|
|
372
374
|
prompt,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { getPlansDir, getOutcomesDir, getInputPath, decodeBase26, TASK_ID_PATTERN } from '../utils/paths.js';
|
|
4
|
+
import { parsePlanFrontmatter, type PlanFrontmatter } from '../utils/frontmatter.js';
|
|
4
5
|
|
|
5
6
|
export type DerivedTaskStatus = 'pending' | 'completed' | 'failed' | 'blocked';
|
|
6
7
|
|
|
@@ -16,6 +17,10 @@ export interface DerivedTask {
|
|
|
16
17
|
planFile: string;
|
|
17
18
|
status: DerivedTaskStatus;
|
|
18
19
|
dependencies: string[];
|
|
20
|
+
/** Frontmatter metadata parsed from the plan file. */
|
|
21
|
+
frontmatter?: PlanFrontmatter;
|
|
22
|
+
/** Warnings from frontmatter parsing. */
|
|
23
|
+
frontmatterWarnings?: string[];
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export interface DerivedProjectState {
|
|
@@ -218,23 +223,34 @@ export function deriveProjectState(projectPath: string): DerivedProjectState {
|
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
|
|
221
|
-
// First pass: Match plan files to outcomes and parse dependencies
|
|
226
|
+
// First pass: Match plan files to outcomes and parse dependencies + frontmatter
|
|
222
227
|
for (const planFile of planFiles) {
|
|
223
228
|
const match = planFile.match(new RegExp(`^(${TASK_ID_PATTERN})-(.+)\\.md$`));
|
|
224
229
|
if (match && match[1]) {
|
|
225
230
|
const taskId = match[1];
|
|
226
231
|
const status = outcomeStatuses.get(taskId) ?? 'pending';
|
|
227
232
|
|
|
228
|
-
// Read plan file to extract dependencies
|
|
233
|
+
// Read plan file to extract dependencies and frontmatter
|
|
229
234
|
const planContent = fs.readFileSync(path.join(plansDir, planFile), 'utf-8');
|
|
230
235
|
const dependencies = parseDependencies(planContent);
|
|
236
|
+
const frontmatterResult = parsePlanFrontmatter(planContent);
|
|
231
237
|
|
|
232
|
-
|
|
238
|
+
const task: DerivedTask = {
|
|
233
239
|
id: taskId,
|
|
234
240
|
planFile: path.join('plans', planFile),
|
|
235
241
|
status,
|
|
236
242
|
dependencies,
|
|
237
|
-
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Only add frontmatter if valid metadata was found
|
|
246
|
+
if (frontmatterResult.hasFrontmatter) {
|
|
247
|
+
task.frontmatter = frontmatterResult.frontmatter;
|
|
248
|
+
}
|
|
249
|
+
if (frontmatterResult.warnings.length > 0) {
|
|
250
|
+
task.frontmatterWarnings = frontmatterResult.warnings;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
tasks.push(task);
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
256
|
|
package/src/core/worktree.ts
CHANGED
|
@@ -432,3 +432,233 @@ export function resolveWorktreeProjectByIdentifier(
|
|
|
432
432
|
// Ambiguous or no match
|
|
433
433
|
return null;
|
|
434
434
|
}
|
|
435
|
+
|
|
436
|
+
export interface SyncMainBranchResult {
|
|
437
|
+
success: boolean;
|
|
438
|
+
/** The detected main branch name (e.g., 'main' or 'master') */
|
|
439
|
+
mainBranch: string | null;
|
|
440
|
+
/** Whether any changes were pulled */
|
|
441
|
+
hadChanges: boolean;
|
|
442
|
+
error?: string;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Detect the main branch name from the remote.
|
|
447
|
+
* Uses refs/remotes/origin/HEAD, falling back to main/master.
|
|
448
|
+
* Reuses the same logic as detectBaseBranch from pull-request.ts.
|
|
449
|
+
*/
|
|
450
|
+
export function detectMainBranch(cwd?: string): string | null {
|
|
451
|
+
// Try to find the default branch from the remote
|
|
452
|
+
try {
|
|
453
|
+
const output = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
454
|
+
encoding: 'utf-8',
|
|
455
|
+
stdio: 'pipe',
|
|
456
|
+
...(cwd ? { cwd } : {}),
|
|
457
|
+
}).trim();
|
|
458
|
+
// Output is like "refs/remotes/origin/main"
|
|
459
|
+
const parts = output.split('/');
|
|
460
|
+
return parts[parts.length - 1] ?? null;
|
|
461
|
+
} catch {
|
|
462
|
+
// Fallback: check for common branch names
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Try common default branches
|
|
466
|
+
for (const branch of ['main', 'master']) {
|
|
467
|
+
try {
|
|
468
|
+
execSync(`git rev-parse --verify "refs/heads/${branch}"`, {
|
|
469
|
+
encoding: 'utf-8',
|
|
470
|
+
stdio: 'pipe',
|
|
471
|
+
...(cwd ? { cwd } : {}),
|
|
472
|
+
});
|
|
473
|
+
return branch;
|
|
474
|
+
} catch {
|
|
475
|
+
// Try next
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Pull the main branch from remote before worktree creation.
|
|
484
|
+
* Uses fetch + merge --ff-only to safely update the main branch.
|
|
485
|
+
* This ensures the worktree is created from the latest remote state.
|
|
486
|
+
*
|
|
487
|
+
* Only pulls if currently on the main branch or if the main branch exists.
|
|
488
|
+
* Fails gracefully if there are conflicts or the branch has diverged.
|
|
489
|
+
*
|
|
490
|
+
* @param cwd - The directory to run git commands in (defaults to current directory)
|
|
491
|
+
*/
|
|
492
|
+
export function pullMainBranch(cwd?: string): SyncMainBranchResult {
|
|
493
|
+
const mainBranch = detectMainBranch(cwd);
|
|
494
|
+
|
|
495
|
+
if (!mainBranch) {
|
|
496
|
+
return {
|
|
497
|
+
success: false,
|
|
498
|
+
mainBranch: null,
|
|
499
|
+
hadChanges: false,
|
|
500
|
+
error: 'Could not detect main branch (no origin/HEAD or main/master found)',
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Get current branch to check if we need to switch
|
|
505
|
+
const currentBranch = getCurrentBranch();
|
|
506
|
+
|
|
507
|
+
// If not on main, we need to fetch and update the main branch ref
|
|
508
|
+
// without checking it out (to avoid disrupting the user's work)
|
|
509
|
+
if (currentBranch !== mainBranch) {
|
|
510
|
+
// Fetch the main branch from origin
|
|
511
|
+
try {
|
|
512
|
+
execSync(`git fetch origin ${mainBranch}:${mainBranch}`, {
|
|
513
|
+
encoding: 'utf-8',
|
|
514
|
+
stdio: 'pipe',
|
|
515
|
+
...(cwd ? { cwd } : {}),
|
|
516
|
+
});
|
|
517
|
+
logger.debug(`Fetched ${mainBranch} from origin`);
|
|
518
|
+
return {
|
|
519
|
+
success: true,
|
|
520
|
+
mainBranch,
|
|
521
|
+
hadChanges: true, // We fetched updates (may or may not have actual changes)
|
|
522
|
+
};
|
|
523
|
+
} catch (error) {
|
|
524
|
+
// This can fail if the local main has diverged from remote
|
|
525
|
+
// Try a simple fetch without updating the local ref
|
|
526
|
+
try {
|
|
527
|
+
execSync(`git fetch origin ${mainBranch}`, {
|
|
528
|
+
encoding: 'utf-8',
|
|
529
|
+
stdio: 'pipe',
|
|
530
|
+
...(cwd ? { cwd } : {}),
|
|
531
|
+
});
|
|
532
|
+
logger.debug(`Fetched origin/${mainBranch} (local ${mainBranch} diverged, not updated)`);
|
|
533
|
+
return {
|
|
534
|
+
success: true,
|
|
535
|
+
mainBranch,
|
|
536
|
+
hadChanges: false,
|
|
537
|
+
error: `Local ${mainBranch} has diverged from origin, not updated`,
|
|
538
|
+
};
|
|
539
|
+
} catch (fetchError) {
|
|
540
|
+
const msg = fetchError instanceof Error ? fetchError.message : String(fetchError);
|
|
541
|
+
return {
|
|
542
|
+
success: false,
|
|
543
|
+
mainBranch,
|
|
544
|
+
hadChanges: false,
|
|
545
|
+
error: `Failed to fetch ${mainBranch}: ${msg}`,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Currently on main branch - can do a regular pull with ff-only
|
|
552
|
+
// First, check for uncommitted changes that would block the pull
|
|
553
|
+
try {
|
|
554
|
+
const status = execSync('git status --porcelain', {
|
|
555
|
+
encoding: 'utf-8',
|
|
556
|
+
stdio: 'pipe',
|
|
557
|
+
...(cwd ? { cwd } : {}),
|
|
558
|
+
}).trim();
|
|
559
|
+
|
|
560
|
+
if (status) {
|
|
561
|
+
return {
|
|
562
|
+
success: false,
|
|
563
|
+
mainBranch,
|
|
564
|
+
hadChanges: false,
|
|
565
|
+
error: `Cannot pull ${mainBranch}: uncommitted changes in working directory`,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
mainBranch,
|
|
573
|
+
hadChanges: false,
|
|
574
|
+
error: `Failed to check git status: ${msg}`,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Fetch and merge with ff-only
|
|
579
|
+
try {
|
|
580
|
+
execSync(`git fetch origin ${mainBranch}`, {
|
|
581
|
+
encoding: 'utf-8',
|
|
582
|
+
stdio: 'pipe',
|
|
583
|
+
...(cwd ? { cwd } : {}),
|
|
584
|
+
});
|
|
585
|
+
} catch (error) {
|
|
586
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
587
|
+
return {
|
|
588
|
+
success: false,
|
|
589
|
+
mainBranch,
|
|
590
|
+
hadChanges: false,
|
|
591
|
+
error: `Failed to fetch from origin: ${msg}`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
try {
|
|
596
|
+
const output = execSync(`git merge --ff-only origin/${mainBranch}`, {
|
|
597
|
+
encoding: 'utf-8',
|
|
598
|
+
stdio: 'pipe',
|
|
599
|
+
...(cwd ? { cwd } : {}),
|
|
600
|
+
});
|
|
601
|
+
const hadChanges = !output.includes('Already up to date');
|
|
602
|
+
return {
|
|
603
|
+
success: true,
|
|
604
|
+
mainBranch,
|
|
605
|
+
hadChanges,
|
|
606
|
+
};
|
|
607
|
+
} catch (error) {
|
|
608
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
609
|
+
return {
|
|
610
|
+
success: false,
|
|
611
|
+
mainBranch,
|
|
612
|
+
hadChanges: false,
|
|
613
|
+
error: `Cannot fast-forward ${mainBranch}: ${msg.includes('Not possible to fast-forward') ? 'branch has diverged from origin' : msg}`,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Push the main branch to remote before PR creation.
|
|
620
|
+
* Ensures the PR base is up to date.
|
|
621
|
+
*
|
|
622
|
+
* @param cwd - The directory to run git commands in (defaults to current directory)
|
|
623
|
+
*/
|
|
624
|
+
export function pushMainBranch(cwd?: string): SyncMainBranchResult {
|
|
625
|
+
const mainBranch = detectMainBranch(cwd);
|
|
626
|
+
|
|
627
|
+
if (!mainBranch) {
|
|
628
|
+
return {
|
|
629
|
+
success: false,
|
|
630
|
+
mainBranch: null,
|
|
631
|
+
hadChanges: false,
|
|
632
|
+
error: 'Could not detect main branch (no origin/HEAD or main/master found)',
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
try {
|
|
637
|
+
execSync(`git push origin ${mainBranch}`, {
|
|
638
|
+
encoding: 'utf-8',
|
|
639
|
+
stdio: 'pipe',
|
|
640
|
+
...(cwd ? { cwd } : {}),
|
|
641
|
+
});
|
|
642
|
+
return {
|
|
643
|
+
success: true,
|
|
644
|
+
mainBranch,
|
|
645
|
+
hadChanges: true,
|
|
646
|
+
};
|
|
647
|
+
} catch (error) {
|
|
648
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
649
|
+
// Check if it's just "already up to date"
|
|
650
|
+
if (msg.includes('Everything up-to-date')) {
|
|
651
|
+
return {
|
|
652
|
+
success: true,
|
|
653
|
+
mainBranch,
|
|
654
|
+
hadChanges: false,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
success: false,
|
|
659
|
+
mainBranch,
|
|
660
|
+
hadChanges: false,
|
|
661
|
+
error: `Failed to push ${mainBranch}: ${msg}`,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
}
|
package/src/prompts/amend.ts
CHANGED
|
@@ -135,9 +135,11 @@ After interviewing the user about all NEW tasks, create plan files starting from
|
|
|
135
135
|
- ${projectPath}/plans/${encodeTaskId(nextTaskNumber + 1)}-task-name.md
|
|
136
136
|
- etc.
|
|
137
137
|
|
|
138
|
-
Each plan file
|
|
138
|
+
Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter format uses only a closing \`---\` delimiter (no opening delimiter):
|
|
139
139
|
|
|
140
140
|
\`\`\`markdown
|
|
141
|
+
effort: medium
|
|
142
|
+
---
|
|
141
143
|
# Task: [Task Name]
|
|
142
144
|
|
|
143
145
|
## Objective
|
|
@@ -175,6 +177,23 @@ Each plan file should follow this structure:
|
|
|
175
177
|
[Reference to existing task outcomes if relevant]
|
|
176
178
|
\`\`\`
|
|
177
179
|
|
|
180
|
+
### Frontmatter Requirements
|
|
181
|
+
|
|
182
|
+
The \`effort\` field is REQUIRED in every plan file. It indicates task complexity and determines which Claude model will execute the task:
|
|
183
|
+
- \`effort: low\` — Trivial/mechanical changes, simple one-file edits, config changes
|
|
184
|
+
- \`effort: medium\` — Well-scoped feature work, bug fixes with clear plans, multi-file changes following existing patterns
|
|
185
|
+
- \`effort: high\` — Architectural changes, complex logic, tasks requiring deep codebase understanding
|
|
186
|
+
|
|
187
|
+
Optionally, you can add an explicit \`model\` field to override the effort-based model selection:
|
|
188
|
+
\`\`\`markdown
|
|
189
|
+
effort: medium
|
|
190
|
+
model: opus
|
|
191
|
+
---
|
|
192
|
+
# Task: ...
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
This is rarely needed — prefer using the \`effort\` label so the user's config controls the actual model used.
|
|
196
|
+
|
|
178
197
|
### Step 5: Confirm Completion
|
|
179
198
|
|
|
180
199
|
After creating all new plan files:
|
|
@@ -201,19 +220,15 @@ Planning complete! To exit this session and run your tasks:
|
|
|
201
220
|
7. Specify task dependencies using the ## Dependencies section with task IDs only (e.g., "01, 02")
|
|
202
221
|
8. Tasks without dependencies should omit the Dependencies section entirely
|
|
203
222
|
9. Be specific - vague plans lead to poor execution
|
|
223
|
+
10. ALWAYS include the \`effort\` frontmatter field in every plan file — assess each task's complexity
|
|
204
224
|
|
|
205
225
|
## Plan Output Style
|
|
206
226
|
|
|
207
|
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
- Existing project files to modify
|
|
213
|
-
- Previous plan/outcome files for context
|
|
214
|
-
- Project structure and directories
|
|
215
|
-
- Let the executing agent decide implementation specifics
|
|
216
|
-
- Plans guide the work; they don't prescribe exact code`;
|
|
227
|
+
Plans can include whatever level of detail you deem helpful for the executing agent. Use your judgment:
|
|
228
|
+
- Include implementation details when they clarify the approach
|
|
229
|
+
- Code snippets are acceptable when they help illustrate a specific pattern
|
|
230
|
+
- File paths are helpful when referencing existing project files, patterns, or directories
|
|
231
|
+
- Focus on clarity — the goal is for the executing agent to understand what needs to be done`;
|
|
217
232
|
|
|
218
233
|
const userMessage = `I want to add the following new tasks to this project:
|
|
219
234
|
|
|
@@ -37,24 +37,36 @@ Controls which Claude model is used for each scenario. Values can be a short ali
|
|
|
37
37
|
| Key | Default | Description |
|
|
38
38
|
|-----|---------|-------------|
|
|
39
39
|
| `models.plan` | `"opus"` | Model used for planning sessions (`raf plan`) |
|
|
40
|
-
| `models.execute` | `"opus"` |
|
|
40
|
+
| `models.execute` | `"opus"` | Ceiling model for task execution (`raf do`). Per-task models from effort frontmatter are capped to this tier. Also used as the fallback when a plan has no effort frontmatter. |
|
|
41
41
|
| `models.nameGeneration` | `"sonnet"` | Model used for generating project names |
|
|
42
42
|
| `models.failureAnalysis` | `"haiku"` | Model used for analyzing task failures |
|
|
43
43
|
| `models.prGeneration` | `"sonnet"` | Model used for generating PR titles and descriptions |
|
|
44
44
|
| `models.config` | `"sonnet"` | Model used for the interactive config editor (`raf config`) |
|
|
45
45
|
|
|
46
|
-
### `
|
|
46
|
+
### `effortMapping` — Task Effort to Model Mapping
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Maps task complexity labels (in plan frontmatter) to Claude models. When a plan file has `effort: medium`, RAF resolves the execution model using this mapping.
|
|
49
49
|
|
|
50
50
|
| Key | Default | Description |
|
|
51
51
|
|-----|---------|-------------|
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
| `effortMapping.low` | `"haiku"` | Model for low-complexity tasks |
|
|
53
|
+
| `effortMapping.medium` | `"sonnet"` | Model for medium-complexity tasks |
|
|
54
|
+
| `effortMapping.high` | `"opus"` | Model for high-complexity tasks |
|
|
55
|
+
|
|
56
|
+
Values must be a short alias (`"sonnet"`, `"haiku"`, `"opus"`) or a full model ID.
|
|
57
|
+
|
|
58
|
+
**Interaction with `models.execute`**: The `models.execute` value acts as a ceiling. If a task's effort maps to a more expensive model than the ceiling, the ceiling model is used instead. This gives users budget control while allowing tasks to use cheaper models when appropriate.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"models": { "execute": "sonnet" },
|
|
64
|
+
"effortMapping": { "low": "haiku", "medium": "sonnet", "high": "opus" }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
- Task with `effort: low` → haiku (under ceiling)
|
|
68
|
+
- Task with `effort: medium` → sonnet (at ceiling)
|
|
69
|
+
- Task with `effort: high` → sonnet (capped to ceiling, not opus)
|
|
58
70
|
|
|
59
71
|
### `timeout` — Task Timeout
|
|
60
72
|
|
|
@@ -80,6 +92,18 @@ Controls the effort level passed to Claude for each scenario. All values must be
|
|
|
80
92
|
- **Default**: `false`
|
|
81
93
|
- **Description**: When `true`, `raf plan` and `raf do` default to worktree mode (isolated git worktree). Can be overridden per-command with `--worktree` flag.
|
|
82
94
|
|
|
95
|
+
### `syncMainBranch` — Sync Main Branch with Remote
|
|
96
|
+
|
|
97
|
+
- **Type**: boolean
|
|
98
|
+
- **Default**: `true`
|
|
99
|
+
- **Description**: When `true`, RAF automatically syncs the main branch with the remote before worktree operations:
|
|
100
|
+
- **Before worktree creation** (`raf plan --worktree`): Pulls the main branch from remote to ensure the worktree starts from the latest code
|
|
101
|
+
- **Before PR creation** (post-execution "Create PR" action): Pushes the main branch to remote so the PR base is up to date
|
|
102
|
+
|
|
103
|
+
The main branch is auto-detected from `refs/remotes/origin/HEAD`, falling back to `main` or `master` if not set.
|
|
104
|
+
|
|
105
|
+
Failures in sync operations produce warnings but don't block the workflow. For example, if the local main branch has diverged from remote, the sync will warn and continue.
|
|
106
|
+
|
|
83
107
|
### `commitFormat` — Commit Message Templates
|
|
84
108
|
|
|
85
109
|
Controls the format of git commit messages. Templates use `{placeholder}` syntax for variable substitution.
|
|
@@ -147,25 +171,62 @@ Example override:
|
|
|
147
171
|
|
|
148
172
|
Only specify the fields you want to change — unset fields keep their defaults.
|
|
149
173
|
|
|
150
|
-
### `
|
|
174
|
+
### `display` — Token Summary Display Options
|
|
175
|
+
|
|
176
|
+
Controls what information is shown in token usage summaries after tasks and in the grand total.
|
|
177
|
+
|
|
178
|
+
| Key | Default | Description |
|
|
179
|
+
|-----|---------|-------------|
|
|
180
|
+
| `display.showRateLimitEstimate` | `true` | Show estimated 5h rate limit window percentage (e.g., `~42% of 5h window`) |
|
|
181
|
+
| `display.showCacheTokens` | `true` | Show cache read/create token counts in summaries |
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"display": {
|
|
188
|
+
"showRateLimitEstimate": false,
|
|
189
|
+
"showCacheTokens": true
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `rateLimitWindow` — Rate Limit Configuration
|
|
195
|
+
|
|
196
|
+
Controls the rate limit estimation calculation.
|
|
197
|
+
|
|
198
|
+
| Key | Default | Description |
|
|
199
|
+
|-----|---------|-------------|
|
|
200
|
+
| `rateLimitWindow.sonnetTokenCap` | `88000` | The Sonnet-equivalent token cap for the 5-hour window. All token usage is normalized to Sonnet-equivalent tokens using pricing ratios. |
|
|
201
|
+
|
|
202
|
+
The 5h window percentage is calculated as: `(estimatedCost / sonnetCostPerToken) / sonnetTokenCap * 100`
|
|
151
203
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
204
|
+
Where `sonnetCostPerToken` is derived from the configured Sonnet pricing. Heavier models (Opus) consume the window faster than lighter ones (Haiku) in proportion to their API pricing ratios.
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"rateLimitWindow": {
|
|
211
|
+
"sonnetTokenCap": 100000
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
155
215
|
|
|
156
216
|
## Validation Rules
|
|
157
217
|
|
|
158
218
|
The config is validated when loaded. Invalid configs cause an error with a descriptive message. The following rules are enforced:
|
|
159
219
|
|
|
160
220
|
- **Unknown keys are rejected** at every nesting level. Typos like `"model"` instead of `"models"` will be caught.
|
|
161
|
-
- **Model values** must be a short alias (`"sonnet"`, `"haiku"`, `"opus"`) or a full model ID matching the pattern `claude-{family}-{version}` (e.g., `"claude-sonnet-4-5-20250929"`).
|
|
162
|
-
-
|
|
221
|
+
- **Model values** (`models.*`, `effortMapping.*`) must be a short alias (`"sonnet"`, `"haiku"`, `"opus"`) or a full model ID matching the pattern `claude-{family}-{version}` (e.g., `"claude-sonnet-4-5-20250929"`).
|
|
222
|
+
- **`effortMapping` keys** must be `"low"`, `"medium"`, or `"high"`.
|
|
163
223
|
- **`timeout`** must be a positive finite number.
|
|
164
224
|
- **`maxRetries`** must be a non-negative integer.
|
|
165
|
-
- **`autoCommit
|
|
225
|
+
- **`autoCommit`**, **`worktree`**, and **`syncMainBranch`** must be booleans.
|
|
166
226
|
- **`commitFormat` values** must be strings.
|
|
167
|
-
- **`claudeCommand`** must be a non-empty string.
|
|
168
227
|
- **`pricing`** categories must be `"opus"`, `"sonnet"`, or `"haiku"`. Each field must be a non-negative number.
|
|
228
|
+
- **`display` values** (`showRateLimitEstimate`, `showCacheTokens`) must be booleans.
|
|
229
|
+
- **`rateLimitWindow.sonnetTokenCap`** must be a positive number.
|
|
169
230
|
- The config file must be valid JSON containing an object (not an array or primitive).
|
|
170
231
|
|
|
171
232
|
## CLI Precedence
|
|
@@ -199,14 +260,11 @@ Uses Sonnet instead of Opus for task execution. Everything else stays at default
|
|
|
199
260
|
"plan": "sonnet",
|
|
200
261
|
"execute": "sonnet"
|
|
201
262
|
},
|
|
202
|
-
"effort": {
|
|
203
|
-
"plan": "medium"
|
|
204
|
-
},
|
|
205
263
|
"worktree": true
|
|
206
264
|
}
|
|
207
265
|
```
|
|
208
266
|
|
|
209
|
-
Uses Sonnet for
|
|
267
|
+
Uses Sonnet for planning and caps task execution at Sonnet (tasks with `effort: high` will use Sonnet instead of Opus). Defaults to worktree mode.
|
|
210
268
|
|
|
211
269
|
### Full — All Settings Explicit
|
|
212
270
|
|
|
@@ -220,29 +278,33 @@ Uses Sonnet for both planning and execution, reduces planning effort, and defaul
|
|
|
220
278
|
"prGeneration": "sonnet",
|
|
221
279
|
"config": "sonnet"
|
|
222
280
|
},
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"failureAnalysis": "low",
|
|
228
|
-
"prGeneration": "medium",
|
|
229
|
-
"config": "medium"
|
|
281
|
+
"effortMapping": {
|
|
282
|
+
"low": "haiku",
|
|
283
|
+
"medium": "sonnet",
|
|
284
|
+
"high": "opus"
|
|
230
285
|
},
|
|
231
286
|
"timeout": 60,
|
|
232
287
|
"maxRetries": 3,
|
|
233
288
|
"autoCommit": true,
|
|
234
289
|
"worktree": false,
|
|
290
|
+
"syncMainBranch": true,
|
|
235
291
|
"commitFormat": {
|
|
236
292
|
"task": "{prefix}[{projectId}:{taskId}] {description}",
|
|
237
293
|
"plan": "{prefix}[{projectId}] Plan: {projectName}",
|
|
238
294
|
"amend": "{prefix}[{projectId}] Amend: {projectName}",
|
|
239
295
|
"prefix": "RAF"
|
|
240
296
|
},
|
|
241
|
-
"claudeCommand": "claude",
|
|
242
297
|
"pricing": {
|
|
243
298
|
"opus": { "inputPerMTok": 15, "outputPerMTok": 75, "cacheReadPerMTok": 1.5, "cacheCreatePerMTok": 18.75 },
|
|
244
299
|
"sonnet": { "inputPerMTok": 3, "outputPerMTok": 15, "cacheReadPerMTok": 0.3, "cacheCreatePerMTok": 3.75 },
|
|
245
300
|
"haiku": { "inputPerMTok": 1, "outputPerMTok": 5, "cacheReadPerMTok": 0.1, "cacheCreatePerMTok": 1.25 }
|
|
301
|
+
},
|
|
302
|
+
"display": {
|
|
303
|
+
"showRateLimitEstimate": true,
|
|
304
|
+
"showCacheTokens": true
|
|
305
|
+
},
|
|
306
|
+
"rateLimitWindow": {
|
|
307
|
+
"sonnetTokenCap": 88000
|
|
246
308
|
}
|
|
247
309
|
}
|
|
248
310
|
```
|
package/src/prompts/planning.ts
CHANGED
|
@@ -80,9 +80,11 @@ After interviewing the user about all tasks, create plan files in the plans fold
|
|
|
80
80
|
- ${projectPath}/plans/02-task-name.md
|
|
81
81
|
- etc.
|
|
82
82
|
|
|
83
|
-
Each plan file
|
|
83
|
+
Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter format uses only a closing \`---\` delimiter (no opening delimiter):
|
|
84
84
|
|
|
85
85
|
\`\`\`markdown
|
|
86
|
+
effort: medium
|
|
87
|
+
---
|
|
86
88
|
# Task: [Task Name]
|
|
87
89
|
|
|
88
90
|
## Objective
|
|
@@ -117,6 +119,23 @@ Each plan file should follow this structure:
|
|
|
117
119
|
[Any additional context, warnings, or considerations]
|
|
118
120
|
\`\`\`
|
|
119
121
|
|
|
122
|
+
### Frontmatter Requirements
|
|
123
|
+
|
|
124
|
+
The \`effort\` field is REQUIRED in every plan file. It indicates task complexity and determines which Claude model will execute the task:
|
|
125
|
+
- \`effort: low\` — Trivial/mechanical changes, simple one-file edits, config changes
|
|
126
|
+
- \`effort: medium\` — Well-scoped feature work, bug fixes with clear plans, multi-file changes following existing patterns
|
|
127
|
+
- \`effort: high\` — Architectural changes, complex logic, tasks requiring deep codebase understanding
|
|
128
|
+
|
|
129
|
+
Optionally, you can add an explicit \`model\` field to override the effort-based model selection:
|
|
130
|
+
\`\`\`markdown
|
|
131
|
+
effort: medium
|
|
132
|
+
model: opus
|
|
133
|
+
---
|
|
134
|
+
# Task: ...
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
This is rarely needed — prefer using the \`effort\` label so the user's config controls the actual model used.
|
|
138
|
+
|
|
120
139
|
### Step 4: Infer Task Dependencies
|
|
121
140
|
|
|
122
141
|
For each task, analyze which other tasks must complete successfully before it can begin. Add a \`## Dependencies\` section to plan files that have prerequisites.
|
|
@@ -166,19 +185,15 @@ Planning complete! To exit this session and run your tasks:
|
|
|
166
185
|
6. Only add Dependencies section when a task genuinely requires another to complete first
|
|
167
186
|
7. Dependencies must only reference lower-numbered tasks to prevent circular dependencies
|
|
168
187
|
8. Be specific - vague plans lead to poor execution
|
|
188
|
+
9. ALWAYS include the \`effort\` frontmatter field in every plan file — assess each task's complexity
|
|
169
189
|
|
|
170
190
|
## Plan Output Style
|
|
171
191
|
|
|
172
|
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
- Existing project files to modify
|
|
178
|
-
- Previous plan/outcome files for context
|
|
179
|
-
- Project structure and directories
|
|
180
|
-
- Let the executing agent decide implementation specifics
|
|
181
|
-
- Plans guide the work; they don't prescribe exact code`;
|
|
192
|
+
Plans can include whatever level of detail you deem helpful for the executing agent. Use your judgment:
|
|
193
|
+
- Include implementation details when they clarify the approach
|
|
194
|
+
- Code snippets are acceptable when they help illustrate a specific pattern
|
|
195
|
+
- File paths are helpful when referencing existing project files, patterns, or directories
|
|
196
|
+
- Focus on clarity — the goal is for the executing agent to understand what needs to be done`;
|
|
182
197
|
|
|
183
198
|
const userMessage = `Here is my project description:
|
|
184
199
|
|