rafcode 2.3.0 → 2.4.1-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.
Files changed (129) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CLAUDE.md +21 -4
  3. package/RAF/ahvrih-rate-forge/decisions.md +70 -0
  4. package/RAF/ahvrih-rate-forge/input.md +44 -0
  5. package/RAF/ahvrih-rate-forge/outcomes/01-remove-claude-command-config.md +58 -0
  6. package/RAF/ahvrih-rate-forge/outcomes/02-fix-mixed-attempt-cost.md +46 -0
  7. package/RAF/ahvrih-rate-forge/outcomes/03-rate-limit-estimation.md +82 -0
  8. package/RAF/ahvrih-rate-forge/outcomes/04-show-version-in-do-logs.md +45 -0
  9. package/RAF/ahvrih-rate-forge/outcomes/05-sync-main-before-worktree.md +96 -0
  10. package/RAF/ahvrih-rate-forge/outcomes/06-sync-readme-with-codebase.md +45 -0
  11. package/RAF/ahvrih-rate-forge/outcomes/07-no-session-persistence.md +26 -0
  12. package/RAF/ahvrih-rate-forge/outcomes/08-plan-execution-metadata.md +130 -0
  13. package/RAF/ahvrih-rate-forge/plans/01-remove-claude-command-config.md +36 -0
  14. package/RAF/ahvrih-rate-forge/plans/02-fix-mixed-attempt-cost.md +33 -0
  15. package/RAF/ahvrih-rate-forge/plans/03-rate-limit-estimation.md +82 -0
  16. package/RAF/ahvrih-rate-forge/plans/04-show-version-in-do-logs.md +32 -0
  17. package/RAF/ahvrih-rate-forge/plans/05-sync-main-before-worktree.md +40 -0
  18. package/RAF/ahvrih-rate-forge/plans/06-sync-readme-with-codebase.md +61 -0
  19. package/RAF/ahvrih-rate-forge/plans/07-no-session-persistence.md +28 -0
  20. package/RAF/ahvrih-rate-forge/plans/08-plan-execution-metadata.md +123 -0
  21. package/RAF/ahwidh-quick-fix-gremlin/decisions.md +37 -0
  22. package/RAF/ahwidh-quick-fix-gremlin/input.md +35 -0
  23. package/RAF/ahwidh-quick-fix-gremlin/outcomes/01-fix-name-generation-prompt.md +33 -0
  24. package/RAF/ahwidh-quick-fix-gremlin/outcomes/02-fix-amend-commit-scope.md +43 -0
  25. package/RAF/ahwidh-quick-fix-gremlin/outcomes/03-fix-diverged-main-branch-sync.md +32 -0
  26. package/RAF/ahwidh-quick-fix-gremlin/outcomes/04-wire-rate-limit-to-do-command.md +61 -0
  27. package/RAF/ahwidh-quick-fix-gremlin/outcomes/05-add-config-get-set-flags.md +125 -0
  28. package/RAF/ahwidh-quick-fix-gremlin/outcomes/06-sync-worktree-branch-before-execution.md +96 -0
  29. package/RAF/ahwidh-quick-fix-gremlin/outcomes/07-update-frontmatter-format.md +107 -0
  30. package/RAF/ahwidh-quick-fix-gremlin/outcomes/08-remove-plan-token-report.md +76 -0
  31. package/RAF/ahwidh-quick-fix-gremlin/plans/01-fix-name-generation-prompt.md +52 -0
  32. package/RAF/ahwidh-quick-fix-gremlin/plans/02-fix-amend-commit-scope.md +48 -0
  33. package/RAF/ahwidh-quick-fix-gremlin/plans/03-fix-diverged-main-branch-sync.md +49 -0
  34. package/RAF/ahwidh-quick-fix-gremlin/plans/04-wire-rate-limit-to-do-command.md +78 -0
  35. package/RAF/ahwidh-quick-fix-gremlin/plans/05-add-config-get-set-flags.md +101 -0
  36. package/RAF/ahwidh-quick-fix-gremlin/plans/06-sync-worktree-branch-before-execution.md +92 -0
  37. package/RAF/ahwidh-quick-fix-gremlin/plans/07-update-frontmatter-format.md +105 -0
  38. package/RAF/ahwidh-quick-fix-gremlin/plans/08-remove-plan-token-report.md +50 -0
  39. package/README.md +27 -7
  40. package/dist/commands/config.d.ts.map +1 -1
  41. package/dist/commands/config.js +209 -6
  42. package/dist/commands/config.js.map +1 -1
  43. package/dist/commands/do.d.ts.map +1 -1
  44. package/dist/commands/do.js +140 -21
  45. package/dist/commands/do.js.map +1 -1
  46. package/dist/commands/plan.d.ts.map +1 -1
  47. package/dist/commands/plan.js +27 -5
  48. package/dist/commands/plan.js.map +1 -1
  49. package/dist/core/claude-runner.d.ts +0 -6
  50. package/dist/core/claude-runner.d.ts.map +1 -1
  51. package/dist/core/claude-runner.js +4 -9
  52. package/dist/core/claude-runner.js.map +1 -1
  53. package/dist/core/failure-analyzer.d.ts.map +1 -1
  54. package/dist/core/failure-analyzer.js +3 -3
  55. package/dist/core/failure-analyzer.js.map +1 -1
  56. package/dist/core/pull-request.js +3 -3
  57. package/dist/core/pull-request.js.map +1 -1
  58. package/dist/core/state-derivation.d.ts +5 -0
  59. package/dist/core/state-derivation.d.ts.map +1 -1
  60. package/dist/core/state-derivation.js +14 -4
  61. package/dist/core/state-derivation.js.map +1 -1
  62. package/dist/core/worktree.d.ts +44 -0
  63. package/dist/core/worktree.d.ts.map +1 -1
  64. package/dist/core/worktree.js +247 -0
  65. package/dist/core/worktree.js.map +1 -1
  66. package/dist/prompts/amend.d.ts.map +1 -1
  67. package/dist/prompts/amend.js +28 -11
  68. package/dist/prompts/amend.js.map +1 -1
  69. package/dist/prompts/planning.d.ts.map +1 -1
  70. package/dist/prompts/planning.js +28 -11
  71. package/dist/prompts/planning.js.map +1 -1
  72. package/dist/types/config.d.ts +30 -13
  73. package/dist/types/config.d.ts.map +1 -1
  74. package/dist/types/config.js +14 -10
  75. package/dist/types/config.js.map +1 -1
  76. package/dist/utils/config.d.ts +47 -4
  77. package/dist/utils/config.d.ts.map +1 -1
  78. package/dist/utils/config.js +176 -30
  79. package/dist/utils/config.js.map +1 -1
  80. package/dist/utils/frontmatter.d.ts +53 -0
  81. package/dist/utils/frontmatter.d.ts.map +1 -0
  82. package/dist/utils/frontmatter.js +115 -0
  83. package/dist/utils/frontmatter.js.map +1 -0
  84. package/dist/utils/name-generator.d.ts.map +1 -1
  85. package/dist/utils/name-generator.js +9 -19
  86. package/dist/utils/name-generator.js.map +1 -1
  87. package/dist/utils/session-parser.d.ts +44 -0
  88. package/dist/utils/session-parser.d.ts.map +1 -0
  89. package/dist/utils/session-parser.js +122 -0
  90. package/dist/utils/session-parser.js.map +1 -0
  91. package/dist/utils/terminal-symbols.d.ts +22 -3
  92. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  93. package/dist/utils/terminal-symbols.js +52 -18
  94. package/dist/utils/terminal-symbols.js.map +1 -1
  95. package/dist/utils/token-tracker.d.ts +20 -0
  96. package/dist/utils/token-tracker.d.ts.map +1 -1
  97. package/dist/utils/token-tracker.js +57 -2
  98. package/dist/utils/token-tracker.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/commands/config.ts +242 -7
  101. package/src/commands/do.ts +177 -23
  102. package/src/commands/plan.ts +27 -4
  103. package/src/core/claude-runner.ts +4 -16
  104. package/src/core/failure-analyzer.ts +3 -3
  105. package/src/core/pull-request.ts +3 -3
  106. package/src/core/state-derivation.ts +20 -4
  107. package/src/core/worktree.ts +266 -0
  108. package/src/prompts/amend.ts +28 -11
  109. package/src/prompts/config-docs.md +91 -29
  110. package/src/prompts/planning.ts +28 -11
  111. package/src/types/config.ts +46 -21
  112. package/src/utils/config.ts +200 -33
  113. package/src/utils/frontmatter.ts +140 -0
  114. package/src/utils/name-generator.ts +9 -19
  115. package/src/utils/terminal-symbols.ts +68 -16
  116. package/src/utils/token-tracker.ts +65 -2
  117. package/tests/unit/claude-runner-interactive.test.ts +8 -6
  118. package/tests/unit/claude-runner.test.ts +5 -66
  119. package/tests/unit/commit-planning-artifacts-worktree.test.ts +6 -14
  120. package/tests/unit/commit-planning-artifacts.test.ts +4 -12
  121. package/tests/unit/config-command.test.ts +176 -6
  122. package/tests/unit/config.test.ts +268 -45
  123. package/tests/unit/frontmatter.test.ts +276 -0
  124. package/tests/unit/name-generator.test.ts +1 -1
  125. package/tests/unit/post-execution-picker.test.ts +6 -0
  126. package/tests/unit/terminal-symbols.test.ts +142 -0
  127. package/tests/unit/token-tracker.test.ts +304 -1
  128. package/tests/unit/validation.test.ts +6 -4
  129. package/tests/unit/worktree.test.ts +309 -0
@@ -432,3 +432,269 @@ 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
+ export interface RebaseResult {
446
+ success: boolean;
447
+ error?: string;
448
+ }
449
+
450
+ /**
451
+ * Detect the main branch name from the remote.
452
+ * Uses refs/remotes/origin/HEAD, falling back to main/master.
453
+ * Reuses the same logic as detectBaseBranch from pull-request.ts.
454
+ */
455
+ export function detectMainBranch(cwd?: string): string | null {
456
+ // Try to find the default branch from the remote
457
+ try {
458
+ const output = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
459
+ encoding: 'utf-8',
460
+ stdio: 'pipe',
461
+ ...(cwd ? { cwd } : {}),
462
+ }).trim();
463
+ // Output is like "refs/remotes/origin/main"
464
+ const parts = output.split('/');
465
+ return parts[parts.length - 1] ?? null;
466
+ } catch {
467
+ // Fallback: check for common branch names
468
+ }
469
+
470
+ // Try common default branches
471
+ for (const branch of ['main', 'master']) {
472
+ try {
473
+ execSync(`git rev-parse --verify "refs/heads/${branch}"`, {
474
+ encoding: 'utf-8',
475
+ stdio: 'pipe',
476
+ ...(cwd ? { cwd } : {}),
477
+ });
478
+ return branch;
479
+ } catch {
480
+ // Try next
481
+ }
482
+ }
483
+
484
+ return null;
485
+ }
486
+
487
+ /**
488
+ * Pull the main branch from remote before worktree creation.
489
+ * Uses fetch + merge --ff-only to safely update the main branch.
490
+ * This ensures the worktree is created from the latest remote state.
491
+ *
492
+ * Only pulls if currently on the main branch or if the main branch exists.
493
+ * Fails gracefully if there are conflicts or the branch has diverged.
494
+ *
495
+ * @param cwd - The directory to run git commands in (defaults to current directory)
496
+ */
497
+ export function pullMainBranch(cwd?: string): SyncMainBranchResult {
498
+ const mainBranch = detectMainBranch(cwd);
499
+
500
+ if (!mainBranch) {
501
+ return {
502
+ success: false,
503
+ mainBranch: null,
504
+ hadChanges: false,
505
+ error: 'Could not detect main branch (no origin/HEAD or main/master found)',
506
+ };
507
+ }
508
+
509
+ // Get current branch to check if we need to switch
510
+ const currentBranch = getCurrentBranch();
511
+
512
+ // If not on main, we need to fetch and update the main branch ref
513
+ // without checking it out (to avoid disrupting the user's work)
514
+ if (currentBranch !== mainBranch) {
515
+ // Fetch the main branch from origin
516
+ try {
517
+ execSync(`git fetch origin ${mainBranch}:${mainBranch}`, {
518
+ encoding: 'utf-8',
519
+ stdio: 'pipe',
520
+ ...(cwd ? { cwd } : {}),
521
+ });
522
+ logger.debug(`Fetched ${mainBranch} from origin`);
523
+ return {
524
+ success: true,
525
+ mainBranch,
526
+ hadChanges: true, // We fetched updates (may or may not have actual changes)
527
+ };
528
+ } catch (error) {
529
+ // This can fail if the local main has diverged from remote
530
+ // Try a simple fetch without updating the local ref
531
+ try {
532
+ execSync(`git fetch origin ${mainBranch}`, {
533
+ encoding: 'utf-8',
534
+ stdio: 'pipe',
535
+ ...(cwd ? { cwd } : {}),
536
+ });
537
+ logger.debug(`Fetched origin/${mainBranch} (local ${mainBranch} diverged, not updated)`);
538
+ return {
539
+ success: false,
540
+ mainBranch,
541
+ hadChanges: false,
542
+ error: `Local ${mainBranch} has diverged from origin, not updated`,
543
+ };
544
+ } catch (fetchError) {
545
+ const msg = fetchError instanceof Error ? fetchError.message : String(fetchError);
546
+ return {
547
+ success: false,
548
+ mainBranch,
549
+ hadChanges: false,
550
+ error: `Failed to fetch ${mainBranch}: ${msg}`,
551
+ };
552
+ }
553
+ }
554
+ }
555
+
556
+ // Currently on main branch - can do a regular pull with ff-only
557
+ // First, check for uncommitted changes that would block the pull
558
+ try {
559
+ const status = execSync('git status --porcelain', {
560
+ encoding: 'utf-8',
561
+ stdio: 'pipe',
562
+ ...(cwd ? { cwd } : {}),
563
+ }).trim();
564
+
565
+ if (status) {
566
+ return {
567
+ success: false,
568
+ mainBranch,
569
+ hadChanges: false,
570
+ error: `Cannot pull ${mainBranch}: uncommitted changes in working directory`,
571
+ };
572
+ }
573
+ } catch (error) {
574
+ const msg = error instanceof Error ? error.message : String(error);
575
+ return {
576
+ success: false,
577
+ mainBranch,
578
+ hadChanges: false,
579
+ error: `Failed to check git status: ${msg}`,
580
+ };
581
+ }
582
+
583
+ // Fetch and merge with ff-only
584
+ try {
585
+ execSync(`git fetch origin ${mainBranch}`, {
586
+ encoding: 'utf-8',
587
+ stdio: 'pipe',
588
+ ...(cwd ? { cwd } : {}),
589
+ });
590
+ } catch (error) {
591
+ const msg = error instanceof Error ? error.message : String(error);
592
+ return {
593
+ success: false,
594
+ mainBranch,
595
+ hadChanges: false,
596
+ error: `Failed to fetch from origin: ${msg}`,
597
+ };
598
+ }
599
+
600
+ try {
601
+ const output = execSync(`git merge --ff-only origin/${mainBranch}`, {
602
+ encoding: 'utf-8',
603
+ stdio: 'pipe',
604
+ ...(cwd ? { cwd } : {}),
605
+ });
606
+ const hadChanges = !output.includes('Already up to date');
607
+ return {
608
+ success: true,
609
+ mainBranch,
610
+ hadChanges,
611
+ };
612
+ } catch (error) {
613
+ const msg = error instanceof Error ? error.message : String(error);
614
+ return {
615
+ success: false,
616
+ mainBranch,
617
+ hadChanges: false,
618
+ error: `Cannot fast-forward ${mainBranch}: ${msg.includes('Not possible to fast-forward') ? 'branch has diverged from origin' : msg}`,
619
+ };
620
+ }
621
+ }
622
+
623
+ /**
624
+ * Push the main branch to remote before PR creation.
625
+ * Ensures the PR base is up to date.
626
+ *
627
+ * @param cwd - The directory to run git commands in (defaults to current directory)
628
+ */
629
+ export function pushMainBranch(cwd?: string): SyncMainBranchResult {
630
+ const mainBranch = detectMainBranch(cwd);
631
+
632
+ if (!mainBranch) {
633
+ return {
634
+ success: false,
635
+ mainBranch: null,
636
+ hadChanges: false,
637
+ error: 'Could not detect main branch (no origin/HEAD or main/master found)',
638
+ };
639
+ }
640
+
641
+ try {
642
+ execSync(`git push origin ${mainBranch}`, {
643
+ encoding: 'utf-8',
644
+ stdio: 'pipe',
645
+ ...(cwd ? { cwd } : {}),
646
+ });
647
+ return {
648
+ success: true,
649
+ mainBranch,
650
+ hadChanges: true,
651
+ };
652
+ } catch (error) {
653
+ const msg = error instanceof Error ? error.message : String(error);
654
+ // Check if it's just "already up to date"
655
+ if (msg.includes('Everything up-to-date')) {
656
+ return {
657
+ success: true,
658
+ mainBranch,
659
+ hadChanges: false,
660
+ };
661
+ }
662
+ return {
663
+ success: false,
664
+ mainBranch,
665
+ hadChanges: false,
666
+ error: `Failed to push ${mainBranch}: ${msg}`,
667
+ };
668
+ }
669
+ }
670
+
671
+ /**
672
+ * Rebase the current branch onto the main branch.
673
+ * If the rebase fails with conflicts, aborts the rebase and returns failure.
674
+ *
675
+ * @param mainBranch - The main branch name to rebase onto (e.g., 'main' or 'master')
676
+ * @param cwd - The directory to run git commands in (defaults to current directory)
677
+ */
678
+ export function rebaseOntoMain(mainBranch: string, cwd: string): RebaseResult {
679
+ try {
680
+ execSync(`git rebase ${mainBranch}`, {
681
+ encoding: 'utf-8',
682
+ stdio: 'pipe',
683
+ cwd,
684
+ });
685
+ return { success: true };
686
+ } catch (error) {
687
+ // Abort the failed rebase to restore clean state
688
+ try {
689
+ execSync('git rebase --abort', {
690
+ encoding: 'utf-8',
691
+ stdio: 'pipe',
692
+ cwd,
693
+ });
694
+ } catch {
695
+ // Ignore abort errors
696
+ }
697
+ const msg = error instanceof Error ? error.message : String(error);
698
+ return { success: false, error: msg };
699
+ }
700
+ }
@@ -135,9 +135,12 @@ 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 should follow this structure:
138
+ Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter uses standard YAML format with opening and closing \`---\` delimiters:
139
139
 
140
140
  \`\`\`markdown
141
+ ---
142
+ effort: medium
143
+ ---
141
144
  # Task: [Task Name]
142
145
 
143
146
  ## Objective
@@ -175,6 +178,24 @@ Each plan file should follow this structure:
175
178
  [Reference to existing task outcomes if relevant]
176
179
  \`\`\`
177
180
 
181
+ ### Frontmatter Requirements
182
+
183
+ The \`effort\` field is REQUIRED in every plan file. It indicates task complexity and determines which Claude model will execute the task:
184
+ - \`effort: low\` — Trivial/mechanical changes, simple one-file edits, config changes
185
+ - \`effort: medium\` — Well-scoped feature work, bug fixes with clear plans, multi-file changes following existing patterns
186
+ - \`effort: high\` — Architectural changes, complex logic, tasks requiring deep codebase understanding
187
+
188
+ Optionally, you can add an explicit \`model\` field to override the effort-based model selection:
189
+ \`\`\`markdown
190
+ ---
191
+ effort: medium
192
+ model: opus
193
+ ---
194
+ # Task: ...
195
+ \`\`\`
196
+
197
+ This is rarely needed — prefer using the \`effort\` label so the user's config controls the actual model used.
198
+
178
199
  ### Step 5: Confirm Completion
179
200
 
180
201
  After creating all new plan files:
@@ -201,19 +222,15 @@ Planning complete! To exit this session and run your tasks:
201
222
  7. Specify task dependencies using the ## Dependencies section with task IDs only (e.g., "01, 02")
202
223
  8. Tasks without dependencies should omit the Dependencies section entirely
203
224
  9. Be specific - vague plans lead to poor execution
225
+ 10. ALWAYS include the \`effort\` frontmatter field in every plan file — assess each task's complexity
204
226
 
205
227
  ## Plan Output Style
206
228
 
207
- **CRITICAL**: Plans should be HIGH-LEVEL and CONCEPTUAL:
208
- - Describe WHAT needs to be done, not HOW to code it
209
- - Focus on architecture, data flow, and component interactions
210
- - NO code snippets or implementation details in plans
211
- - File paths ARE acceptable when referencing:
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`;
229
+ Plans can include whatever level of detail you deem helpful for the executing agent. Use your judgment:
230
+ - Include implementation details when they clarify the approach
231
+ - Code snippets are acceptable when they help illustrate a specific pattern
232
+ - File paths are helpful when referencing existing project files, patterns, or directories
233
+ - Focus on clarity the goal is for the executing agent to understand what needs to be done`;
217
234
 
218
235
  const userMessage = `I want to add the following new tasks to this project:
219
236
 
@@ -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"` | Model used for task execution (`raf do`) |
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
- ### `effort` — Claude Effort Level
46
+ ### `effortMapping` — Task Effort to Model Mapping
47
47
 
48
- Controls the effort level passed to Claude for each scenario. All values must be one of: `"low"`, `"medium"`, `"high"`.
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
- | `effort.plan` | `"high"` | Effort level for planning sessions |
53
- | `effort.execute` | `"medium"` | Effort level for task execution |
54
- | `effort.nameGeneration` | `"low"` | Effort level for project name generation |
55
- | `effort.failureAnalysis` | `"low"` | Effort level for failure analysis |
56
- | `effort.prGeneration` | `"medium"` | Effort level for PR generation |
57
- | `effort.config` | `"medium"` | Effort level for config editing sessions |
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
- ### `claudeCommand` — Claude CLI Path
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
- - **Type**: string (non-empty)
153
- - **Default**: `"claude"`
154
- - **Description**: The command or path used to invoke the Claude CLI. Change this if `claude` is not on your PATH or you want to use a wrapper script.
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
- - **Effort values** must be exactly `"low"`, `"medium"`, or `"high"` (case-sensitive).
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`** and **`worktree`** must be booleans.
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 both planning and execution, reduces planning effort, and defaults to worktree mode.
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
- "effort": {
224
- "plan": "high",
225
- "execute": "medium",
226
- "nameGeneration": "low",
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
  ```
@@ -80,9 +80,12 @@ 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 should follow this structure:
83
+ Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter uses standard YAML format with opening and closing \`---\` delimiters:
84
84
 
85
85
  \`\`\`markdown
86
+ ---
87
+ effort: medium
88
+ ---
86
89
  # Task: [Task Name]
87
90
 
88
91
  ## Objective
@@ -117,6 +120,24 @@ Each plan file should follow this structure:
117
120
  [Any additional context, warnings, or considerations]
118
121
  \`\`\`
119
122
 
123
+ ### Frontmatter Requirements
124
+
125
+ The \`effort\` field is REQUIRED in every plan file. It indicates task complexity and determines which Claude model will execute the task:
126
+ - \`effort: low\` — Trivial/mechanical changes, simple one-file edits, config changes
127
+ - \`effort: medium\` — Well-scoped feature work, bug fixes with clear plans, multi-file changes following existing patterns
128
+ - \`effort: high\` — Architectural changes, complex logic, tasks requiring deep codebase understanding
129
+
130
+ Optionally, you can add an explicit \`model\` field to override the effort-based model selection:
131
+ \`\`\`markdown
132
+ ---
133
+ effort: medium
134
+ model: opus
135
+ ---
136
+ # Task: ...
137
+ \`\`\`
138
+
139
+ This is rarely needed — prefer using the \`effort\` label so the user's config controls the actual model used.
140
+
120
141
  ### Step 4: Infer Task Dependencies
121
142
 
122
143
  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 +187,15 @@ Planning complete! To exit this session and run your tasks:
166
187
  6. Only add Dependencies section when a task genuinely requires another to complete first
167
188
  7. Dependencies must only reference lower-numbered tasks to prevent circular dependencies
168
189
  8. Be specific - vague plans lead to poor execution
190
+ 9. ALWAYS include the \`effort\` frontmatter field in every plan file — assess each task's complexity
169
191
 
170
192
  ## Plan Output Style
171
193
 
172
- **CRITICAL**: Plans should be HIGH-LEVEL and CONCEPTUAL:
173
- - Describe WHAT needs to be done, not HOW to code it
174
- - Focus on architecture, data flow, and component interactions
175
- - NO code snippets or implementation details in plans
176
- - File paths ARE acceptable when referencing:
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`;
194
+ Plans can include whatever level of detail you deem helpful for the executing agent. Use your judgment:
195
+ - Include implementation details when they clarify the approach
196
+ - Code snippets are acceptable when they help illustrate a specific pattern
197
+ - File paths are helpful when referencing existing project files, patterns, or directories
198
+ - Focus on clarity the goal is for the executing agent to understand what needs to be done`;
182
199
 
183
200
  const userMessage = `Here is my project description:
184
201