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
@@ -13,7 +13,9 @@ import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFro
13
13
  import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects } from '../ui/project-picker.js';
14
14
  import type { PendingProjectInfo } from '../ui/project-picker.js';
15
15
  import { logger } from '../utils/logger.js';
16
- import { getConfig, getEffort, getWorktreeDefault, getModel, getModelShortName } from '../utils/config.js';
16
+ import { getConfig, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowRateLimitEstimate, getShowCacheTokens } from '../utils/config.js';
17
+ import type { PlanFrontmatter } from '../utils/frontmatter.js';
18
+ import { getVersion } from '../utils/version.js';
17
19
  import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
18
20
  import { createStatusLine } from '../utils/status-line.js';
19
21
  import {
@@ -49,6 +51,10 @@ import {
49
51
  mergeWorktreeBranch,
50
52
  removeWorktree,
51
53
  resolveWorktreeProjectByIdentifier,
54
+ pushMainBranch,
55
+ pullMainBranch,
56
+ detectMainBranch,
57
+ rebaseOntoMain,
52
58
  } from '../core/worktree.js';
53
59
  import { createPullRequest, prPreflight } from '../core/pull-request.js';
54
60
  import type { DoCommandOptions } from '../types/config.js';
@@ -61,6 +67,74 @@ import type { DoCommandOptions } from '../types/config.js';
61
67
  */
62
68
  export type PostExecutionAction = 'merge' | 'pr' | 'leave';
63
69
 
70
+ /**
71
+ * Result of resolving a task's model from frontmatter.
72
+ */
73
+ interface TaskModelResolution {
74
+ /** The resolved model (after ceiling is applied). */
75
+ model: string;
76
+ /** Whether a warning should be logged about missing frontmatter. */
77
+ missingFrontmatter: boolean;
78
+ /** Frontmatter parsing warnings to log. */
79
+ warnings: string[];
80
+ }
81
+
82
+ /**
83
+ * Resolve the execution model for a task from its frontmatter metadata.
84
+ *
85
+ * Resolution order:
86
+ * 1. Explicit `model` in frontmatter (subject to ceiling)
87
+ * 2. `effort` in frontmatter resolved via effortMapping (subject to ceiling)
88
+ * 3. Fallback to models.execute (the ceiling, with a warning)
89
+ *
90
+ * @param frontmatter - Parsed frontmatter from the plan file
91
+ * @param frontmatterWarnings - Warnings from frontmatter parsing
92
+ * @param ceilingModel - The ceiling model (usually models.execute from config)
93
+ * @param isRetry - Whether this is a retry attempt (escalates to ceiling)
94
+ */
95
+ function resolveTaskModel(
96
+ frontmatter: PlanFrontmatter | undefined,
97
+ frontmatterWarnings: string[] | undefined,
98
+ ceilingModel: string,
99
+ isRetry: boolean,
100
+ ): TaskModelResolution {
101
+ const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
102
+
103
+ // Retry escalation: always use the ceiling model on retry
104
+ if (isRetry) {
105
+ return { model: ceilingModel, missingFrontmatter: false, warnings };
106
+ }
107
+
108
+ // No frontmatter - fallback to ceiling with warning
109
+ if (!frontmatter) {
110
+ return {
111
+ model: ceilingModel,
112
+ missingFrontmatter: true,
113
+ warnings,
114
+ };
115
+ }
116
+
117
+ // Explicit model in frontmatter - apply ceiling
118
+ if (frontmatter.model) {
119
+ const model = applyModelCeiling(frontmatter.model, ceilingModel);
120
+ return { model, missingFrontmatter: false, warnings };
121
+ }
122
+
123
+ // Effort-based resolution - apply ceiling
124
+ if (frontmatter.effort) {
125
+ const mappedModel = resolveEffortToModel(frontmatter.effort);
126
+ const model = applyModelCeiling(mappedModel, ceilingModel);
127
+ return { model, missingFrontmatter: false, warnings };
128
+ }
129
+
130
+ // Frontmatter present but no effort or model - fallback to ceiling with warning
131
+ return {
132
+ model: ceilingModel,
133
+ missingFrontmatter: true,
134
+ warnings,
135
+ };
136
+ }
137
+
64
138
  /**
65
139
  * Format failure history for console output.
66
140
  * Shows attempts that failed before eventual success or final failure.
@@ -152,6 +226,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
152
226
  // Variables for worktree context (set when --worktree is used)
153
227
  let worktreeRoot: string | undefined;
154
228
  let originalBranch: string | undefined;
229
+ let mainBranchName: string | null = null;
155
230
 
156
231
  if (worktreeMode) {
157
232
  // Validate git repo
@@ -166,6 +241,19 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
166
241
  // Record original branch before any worktree operations
167
242
  originalBranch = getCurrentBranch() ?? undefined;
168
243
 
244
+ // Sync main branch before worktree operations (if enabled)
245
+ if (getSyncMainBranch()) {
246
+ const syncResult = pullMainBranch();
247
+ mainBranchName = syncResult.mainBranch;
248
+ if (syncResult.success) {
249
+ if (syncResult.hadChanges) {
250
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
251
+ }
252
+ } else {
253
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
254
+ }
255
+ }
256
+
169
257
  if (!projectIdentifier) {
170
258
  // Auto-discovery flow
171
259
  const selected = await discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath);
@@ -378,6 +466,20 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
378
466
  }
379
467
  throw error;
380
468
  }
469
+
470
+ // Rebase worktree branch onto main before execution (if sync is enabled)
471
+ if (getSyncMainBranch()) {
472
+ const mainBranch = mainBranchName ?? detectMainBranch();
473
+ if (mainBranch) {
474
+ const rebaseResult = rebaseOntoMain(mainBranch, worktreeRoot);
475
+ if (rebaseResult.success) {
476
+ logger.info(`Rebased onto ${mainBranch}`);
477
+ } else {
478
+ logger.warn(`Could not rebase onto ${mainBranch}: ${rebaseResult.error}`);
479
+ logger.warn('Continuing with current branch state.');
480
+ }
481
+ }
482
+ }
381
483
  }
382
484
 
383
485
  // Execute project
@@ -394,7 +496,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
394
496
  force,
395
497
  maxRetries,
396
498
  autoCommit,
397
- showModel: true,
398
499
  model,
399
500
  worktreeCwd: worktreeRoot,
400
501
  }
@@ -500,6 +601,19 @@ async function executePostAction(
500
601
 
501
602
  case 'pr': {
502
603
  logger.newline();
604
+
605
+ // Push main branch to remote before PR creation (if enabled)
606
+ if (getSyncMainBranch()) {
607
+ const syncResult = pushMainBranch();
608
+ if (syncResult.success) {
609
+ if (syncResult.hadChanges) {
610
+ logger.info(`Pushed ${syncResult.mainBranch} to remote`);
611
+ }
612
+ } else {
613
+ logger.warn(`Could not push main branch: ${syncResult.error}`);
614
+ }
615
+ }
616
+
503
617
  logger.info(`Creating PR for branch "${worktreeBranch}"...`);
504
618
 
505
619
  const prResult = await createPullRequest(worktreeBranch, projectPath, { cwd: worktreeRoot });
@@ -658,7 +772,6 @@ interface SingleProjectOptions {
658
772
  force: boolean;
659
773
  maxRetries: number;
660
774
  autoCommit: boolean;
661
- showModel: boolean;
662
775
  model: string;
663
776
  /** Worktree root directory. When set, Claude runs with cwd in the worktree. */
664
777
  worktreeCwd?: string;
@@ -669,7 +782,7 @@ async function executeSingleProject(
669
782
  projectName: string,
670
783
  options: SingleProjectOptions
671
784
  ): Promise<ProjectExecutionResult> {
672
- const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model, worktreeCwd } = options;
785
+ const { timeout, verbose, debug, force, maxRetries, autoCommit, model, worktreeCwd } = options;
673
786
 
674
787
  if (!validatePlansExist(projectPath)) {
675
788
  return {
@@ -709,11 +822,12 @@ async function executeSingleProject(
709
822
  : state.tasks.filter((t) => t.status !== 'completed').map((t) => t.id)
710
823
  );
711
824
 
712
- // Set up shutdown handler
713
- const claudeRunner = new ClaudeRunner({ model });
825
+ // Set up shutdown handler - we'll register runners dynamically per-task
714
826
  const projectManager = new ProjectManager();
715
827
  shutdownHandler.init();
716
- shutdownHandler.registerClaudeRunner(claudeRunner);
828
+
829
+ // The ceiling model for all tasks (can be overridden per-task, subject to this ceiling)
830
+ const ceilingModel = model;
717
831
 
718
832
  // Initialize token tracker for usage reporting
719
833
  const tokenTracker = new TokenTracker();
@@ -725,15 +839,13 @@ async function executeSingleProject(
725
839
  // Start project timer
726
840
  const projectStartTime = Date.now();
727
841
 
842
+ // Resolve and display version + ceiling model info (before any tasks run)
843
+ const fullCeilingModelId = resolveFullModelId(ceilingModel);
844
+ logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId}`);
845
+
728
846
  if (verbose) {
729
847
  logger.info(`Executing project: ${projectName}`);
730
848
  logger.info(`Tasks: ${state.tasks.length}, Task timeout: ${timeout} minutes`);
731
-
732
- // Log Claude model name
733
- if (showModel && model) {
734
- logger.info(`Using model: ${model}`);
735
- }
736
-
737
849
  logger.newline();
738
850
  } else {
739
851
  // Minimal mode: show project header
@@ -923,16 +1035,45 @@ async function executeSingleProject(
923
1035
  });
924
1036
  timer.start();
925
1037
 
1038
+ // Log frontmatter warnings once before the retry loop
1039
+ if (task.frontmatterWarnings && task.frontmatterWarnings.length > 0) {
1040
+ for (const warning of task.frontmatterWarnings) {
1041
+ logger.warn(` Frontmatter warning: ${warning}`);
1042
+ }
1043
+ }
1044
+
926
1045
  while (!success && attempts < maxRetries) {
927
1046
  attempts++;
1047
+ const isRetry = attempts > 1;
1048
+
1049
+ // Resolve the model for this attempt (escalates to ceiling on retry)
1050
+ const modelResolution = resolveTaskModel(
1051
+ task.frontmatter,
1052
+ undefined, // warnings already logged above
1053
+ ceilingModel,
1054
+ isRetry,
1055
+ );
1056
+
1057
+ // Log missing frontmatter warning on first attempt only
1058
+ if (!isRetry && modelResolution.missingFrontmatter) {
1059
+ logger.warn(` No effort frontmatter found — using ceiling model`);
1060
+ }
1061
+
1062
+ // Create a runner for this attempt's model
1063
+ const taskRunner = new ClaudeRunner({ model: modelResolution.model });
1064
+ shutdownHandler.registerClaudeRunner(taskRunner);
928
1065
 
929
- if (verbose && attempts > 1) {
930
- logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel}...`);
1066
+ if (verbose && isRetry) {
1067
+ const retryModel = resolveFullModelId(modelResolution.model);
1068
+ logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel} (model: ${retryModel})...`);
1069
+ } else if (verbose && !isRetry) {
1070
+ const taskModel = resolveFullModelId(modelResolution.model);
1071
+ logger.info(` Model: ${taskModel}`);
931
1072
  }
932
1073
 
933
1074
  // Build execution prompt (inside loop to include retry context on retries)
934
1075
  // Check if previous outcome file exists for retry context
935
- const previousOutcomeFileForRetry = attempts > 1 && fs.existsSync(outcomeFilePath)
1076
+ const previousOutcomeFileForRetry = isRetry && fs.existsSync(outcomeFilePath)
936
1077
  ? outcomeFilePath
937
1078
  : undefined;
938
1079
 
@@ -961,18 +1102,16 @@ async function executeSingleProject(
961
1102
  } : undefined;
962
1103
 
963
1104
  // Run Claude (use worktree root as cwd if in worktree mode)
964
- const executeEffort = getEffort('execute');
965
1105
  const runnerOptions = {
966
1106
  timeout,
967
1107
  outcomeFilePath,
968
1108
  commitContext,
969
1109
  cwd: worktreeCwd,
970
- effortLevel: executeEffort,
971
1110
  verboseCheck: () => verboseToggle.isVerbose,
972
1111
  };
973
1112
  const result = verbose
974
- ? await claudeRunner.runVerbose(prompt, runnerOptions)
975
- : await claudeRunner.run(prompt, runnerOptions);
1113
+ ? await taskRunner.runVerbose(prompt, runnerOptions)
1114
+ : await taskRunner.run(prompt, runnerOptions);
976
1115
 
977
1116
  lastOutput = result.output;
978
1117
  if (result.usageData) {
@@ -1096,7 +1235,12 @@ Task completed. No detailed report provided.
1096
1235
  // Track and display token usage for this task
1097
1236
  if (attemptUsageData.length > 0) {
1098
1237
  const entry = tokenTracker.addTask(task.id, attemptUsageData);
1099
- logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u)));
1238
+ const taskRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
1239
+ logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
1240
+ showCacheTokens: getShowCacheTokens(),
1241
+ showRateLimitEstimate: getShowRateLimitEstimate(),
1242
+ rateLimitPercentage: taskRateLimitPct,
1243
+ }));
1100
1244
  }
1101
1245
 
1102
1246
  completedInSession.add(task.id);
@@ -1124,7 +1268,12 @@ Task completed. No detailed report provided.
1124
1268
  // Track token usage even for failed tasks (partial data still useful for totals)
1125
1269
  if (attemptUsageData.length > 0) {
1126
1270
  const entry = tokenTracker.addTask(task.id, attemptUsageData);
1127
- logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u)));
1271
+ const taskRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
1272
+ logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
1273
+ showCacheTokens: getShowCacheTokens(),
1274
+ showRateLimitEstimate: getShowRateLimitEstimate(),
1275
+ rateLimitPercentage: taskRateLimitPct,
1276
+ }));
1128
1277
  }
1129
1278
 
1130
1279
  // Analyze failure and generate structured report
@@ -1237,7 +1386,12 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1237
1386
  if (trackerEntries.length > 0) {
1238
1387
  logger.newline();
1239
1388
  const totals = tokenTracker.getTotals();
1240
- logger.dim(formatTokenTotalSummary(totals.usage, totals.cost));
1389
+ const totalRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
1390
+ logger.dim(formatTokenTotalSummary(totals.usage, totals.cost, {
1391
+ showCacheTokens: getShowCacheTokens(),
1392
+ showRateLimitEstimate: getShowRateLimitEstimate(),
1393
+ rateLimitPercentage: totalRateLimitPct,
1394
+ }));
1241
1395
  }
1242
1396
 
1243
1397
  // Show retry history for tasks that had failures (even if eventually successful)
@@ -15,7 +15,7 @@ import {
15
15
  resolveModelOption,
16
16
  } from '../utils/validation.js';
17
17
  import { logger } from '../utils/logger.js';
18
- import { getWorktreeDefault, getModel, getModelShortName } from '../utils/config.js';
18
+ import { getWorktreeDefault, getModel, getModelShortName, getSyncMainBranch } from '../utils/config.js';
19
19
  import { generateProjectNames } from '../utils/name-generator.js';
20
20
  import { pickProjectName } from '../ui/name-picker.js';
21
21
  import {
@@ -48,6 +48,7 @@ import {
48
48
  validateWorktree,
49
49
  removeWorktree,
50
50
  computeWorktreeBaseDir,
51
+ pullMainBranch,
51
52
  } from '../core/worktree.js';
52
53
 
53
54
  interface PlanCommandOptions {
@@ -185,6 +186,18 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
185
186
  const repoRoot = getRepoRoot()!;
186
187
  const rafDir = getRafDir();
187
188
 
189
+ // Sync main branch before creating worktree (if enabled)
190
+ if (getSyncMainBranch()) {
191
+ const syncResult = pullMainBranch();
192
+ if (syncResult.success) {
193
+ if (syncResult.hadChanges) {
194
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
195
+ }
196
+ } else {
197
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
198
+ }
199
+ }
200
+
188
201
  // Compute project number from main repo's RAF directory
189
202
  const projectNumber = getNextProjectNumber(rafDir);
190
203
  const sanitizedName = sanitizeProjectName(finalProjectName);
@@ -412,6 +425,18 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
412
425
  logger.info(`Recreated worktree from branch: ${folderName}`);
413
426
  } else {
414
427
  // No branch — create fresh worktree and copy project files
428
+ // Sync main branch before creating worktree (if enabled)
429
+ if (getSyncMainBranch()) {
430
+ const syncResult = pullMainBranch();
431
+ if (syncResult.success) {
432
+ if (syncResult.hadChanges) {
433
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
434
+ }
435
+ } else {
436
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
437
+ }
438
+ }
439
+
415
440
  const result = createWorktree(repoBasename, folderName);
416
441
  if (!result.success) {
417
442
  logger.error(`Failed to create worktree: ${result.error}`);
@@ -602,11 +627,9 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
602
627
  logger.info(` - plans/${planFile}`);
603
628
  }
604
629
 
605
- // Commit planning artifacts (input.md, decisions.md, and new plan files)
606
- const newPlanPaths = newPlanFiles.map(f => path.join(plansDir, f));
630
+ // Commit planning artifacts (input.md, decisions.md only plan files committed during execution)
607
631
  await commitPlanningArtifacts(projectPath, {
608
632
  cwd: worktreePath ?? undefined,
609
- additionalFiles: newPlanPaths,
610
633
  isAmend: true,
611
634
  });
612
635
 
@@ -6,12 +6,11 @@ import { logger } from '../utils/logger.js';
6
6
  import { renderStreamEvent } from '../parsers/stream-renderer.js';
7
7
  import type { UsageData } from '../types/config.js';
8
8
  import { getHeadCommitHash, getHeadCommitMessage, isFileCommittedInHead } from './git.js';
9
- import { getClaudeCommand, getModel } from '../utils/config.js';
9
+ import { getModel } from '../utils/config.js';
10
10
 
11
11
  function getClaudePath(): string {
12
- const cmd = getClaudeCommand();
13
12
  try {
14
- return execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();
13
+ return execSync('which claude', { encoding: 'utf-8' }).trim();
15
14
  } catch {
16
15
  throw new Error('Claude CLI not found. Please ensure it is installed and in your PATH.');
17
16
  }
@@ -53,12 +52,6 @@ export interface ClaudeRunnerOptions {
53
52
  /** Path to the outcome file that should be committed. */
54
53
  outcomeFilePath: string;
55
54
  };
56
- /**
57
- * Claude Code reasoning effort level.
58
- * Sets CLAUDE_CODE_EFFORT_LEVEL env var for the spawned process.
59
- * Only applied in non-interactive modes (run, runVerbose).
60
- */
61
- effortLevel?: 'low' | 'medium' | 'high';
62
55
  /**
63
56
  * Dynamic verbose display callback. When provided, called for each stream event
64
57
  * to determine whether to write display output to stdout. Overrides the static
@@ -415,7 +408,7 @@ export class ClaudeRunner {
415
408
  options: ClaudeRunnerOptions,
416
409
  verbose: boolean,
417
410
  ): Promise<RunResult> {
418
- const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel, verboseCheck } = options;
411
+ const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, verboseCheck } = options;
419
412
  // Ensure timeout is a positive number, fallback to 60 minutes
420
413
  const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
421
414
  const timeoutMs = validatedTimeout * 60 * 1000;
@@ -437,11 +430,6 @@ export class ClaudeRunner {
437
430
  logger.debug(`Prompt length: ${prompt.length}, timeout: ${timeoutMs}ms, cwd: ${cwd}`);
438
431
  logger.debug(`Claude path: ${claudePath}`);
439
432
 
440
- // Build env, optionally injecting effort level
441
- const env = effortLevel
442
- ? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
443
- : process.env;
444
-
445
433
  logger.debug('Spawning process...');
446
434
  // Use --output-format stream-json --verbose to get real-time streaming events
447
435
  // including tool calls, file operations, and token usage in the result event.
@@ -460,7 +448,7 @@ export class ClaudeRunner {
460
448
  'Execute the task as described in the system prompt.',
461
449
  ], {
462
450
  cwd,
463
- env,
451
+ env: process.env,
464
452
  stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
465
453
  });
466
454
 
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { execSync } from 'node:child_process';
3
- import { getModel, getClaudeCommand } from '../utils/config.js';
3
+ import { getModel } from '../utils/config.js';
4
4
 
5
5
  /**
6
6
  * Failure types that can be detected programmatically without using the API.
@@ -213,9 +213,8 @@ function extractRelevantOutput(output: string, maxLines: number): string {
213
213
  * Get the path to Claude CLI.
214
214
  */
215
215
  function getClaudePath(): string {
216
- const cmd = getClaudeCommand();
217
216
  try {
218
- return execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();
217
+ return execSync('which claude', { encoding: 'utf-8' }).trim();
219
218
  } catch {
220
219
  throw new Error('Claude CLI not found. Please ensure it is installed and in your PATH.');
221
220
  }
@@ -312,6 +311,7 @@ Respond with ONLY a markdown report in this exact format:
312
311
  const failureModel = getModel('failureAnalysis');
313
312
  const proc = spawn(claudePath, [
314
313
  '--model', failureModel,
314
+ '--no-session-persistence',
315
315
  '--dangerously-skip-permissions',
316
316
  '-p',
317
317
  prompt,
@@ -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, getClaudeCommand, getModelShortName } from '../utils/config.js';
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 {
@@ -354,10 +354,9 @@ export function filterClaudeOutput(output: string): string {
354
354
  * Call Claude to generate a PR body.
355
355
  */
356
356
  async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<string> {
357
- const cmd = getClaudeCommand();
358
357
  let claudePath: string;
359
358
  try {
360
- claudePath = execSync(`which ${cmd}`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
359
+ claudePath = execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' }).trim();
361
360
  } catch {
362
361
  throw new Error('Claude CLI not found');
363
362
  }
@@ -369,6 +368,7 @@ async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<s
369
368
  const prModel = getModel('prGeneration');
370
369
  const proc = spawn(claudePath, [
371
370
  '--model', prModel,
371
+ '--no-session-persistence',
372
372
  '--dangerously-skip-permissions',
373
373
  '-p',
374
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
- tasks.push({
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