rafcode 2.3.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.
Files changed (109) hide show
  1. package/CLAUDE.md +19 -4
  2. package/RAF/ahvrih-rate-forge/decisions.md +70 -0
  3. package/RAF/ahvrih-rate-forge/input.md +44 -0
  4. package/RAF/ahvrih-rate-forge/outcomes/01-remove-claude-command-config.md +58 -0
  5. package/RAF/ahvrih-rate-forge/outcomes/02-fix-mixed-attempt-cost.md +46 -0
  6. package/RAF/ahvrih-rate-forge/outcomes/03-rate-limit-estimation.md +82 -0
  7. package/RAF/ahvrih-rate-forge/outcomes/04-show-version-in-do-logs.md +45 -0
  8. package/RAF/ahvrih-rate-forge/outcomes/05-sync-main-before-worktree.md +96 -0
  9. package/RAF/ahvrih-rate-forge/outcomes/06-sync-readme-with-codebase.md +45 -0
  10. package/RAF/ahvrih-rate-forge/outcomes/07-no-session-persistence.md +26 -0
  11. package/RAF/ahvrih-rate-forge/outcomes/08-plan-execution-metadata.md +130 -0
  12. package/RAF/ahvrih-rate-forge/plans/01-remove-claude-command-config.md +36 -0
  13. package/RAF/ahvrih-rate-forge/plans/02-fix-mixed-attempt-cost.md +33 -0
  14. package/RAF/ahvrih-rate-forge/plans/03-rate-limit-estimation.md +82 -0
  15. package/RAF/ahvrih-rate-forge/plans/04-show-version-in-do-logs.md +32 -0
  16. package/RAF/ahvrih-rate-forge/plans/05-sync-main-before-worktree.md +40 -0
  17. package/RAF/ahvrih-rate-forge/plans/06-sync-readme-with-codebase.md +61 -0
  18. package/RAF/ahvrih-rate-forge/plans/07-no-session-persistence.md +28 -0
  19. package/RAF/ahvrih-rate-forge/plans/08-plan-execution-metadata.md +123 -0
  20. package/README.md +27 -7
  21. package/dist/commands/config.d.ts.map +1 -1
  22. package/dist/commands/config.js +1 -6
  23. package/dist/commands/config.js.map +1 -1
  24. package/dist/commands/do.d.ts.map +1 -1
  25. package/dist/commands/do.js +106 -18
  26. package/dist/commands/do.js.map +1 -1
  27. package/dist/commands/plan.d.ts.map +1 -1
  28. package/dist/commands/plan.js +77 -2
  29. package/dist/commands/plan.js.map +1 -1
  30. package/dist/core/claude-runner.d.ts +6 -6
  31. package/dist/core/claude-runner.d.ts.map +1 -1
  32. package/dist/core/claude-runner.js +9 -10
  33. package/dist/core/claude-runner.js.map +1 -1
  34. package/dist/core/failure-analyzer.d.ts.map +1 -1
  35. package/dist/core/failure-analyzer.js +3 -3
  36. package/dist/core/failure-analyzer.js.map +1 -1
  37. package/dist/core/pull-request.js +3 -3
  38. package/dist/core/pull-request.js.map +1 -1
  39. package/dist/core/state-derivation.d.ts +5 -0
  40. package/dist/core/state-derivation.d.ts.map +1 -1
  41. package/dist/core/state-derivation.js +14 -4
  42. package/dist/core/state-derivation.js.map +1 -1
  43. package/dist/core/worktree.d.ts +32 -0
  44. package/dist/core/worktree.d.ts.map +1 -1
  45. package/dist/core/worktree.js +215 -0
  46. package/dist/core/worktree.js.map +1 -1
  47. package/dist/prompts/amend.d.ts.map +1 -1
  48. package/dist/prompts/amend.js +26 -11
  49. package/dist/prompts/amend.js.map +1 -1
  50. package/dist/prompts/planning.d.ts.map +1 -1
  51. package/dist/prompts/planning.js +26 -11
  52. package/dist/prompts/planning.js.map +1 -1
  53. package/dist/types/config.d.ts +30 -13
  54. package/dist/types/config.d.ts.map +1 -1
  55. package/dist/types/config.js +14 -10
  56. package/dist/types/config.js.map +1 -1
  57. package/dist/utils/config.d.ts +47 -4
  58. package/dist/utils/config.d.ts.map +1 -1
  59. package/dist/utils/config.js +176 -30
  60. package/dist/utils/config.js.map +1 -1
  61. package/dist/utils/frontmatter.d.ts +43 -0
  62. package/dist/utils/frontmatter.d.ts.map +1 -0
  63. package/dist/utils/frontmatter.js +85 -0
  64. package/dist/utils/frontmatter.js.map +1 -0
  65. package/dist/utils/name-generator.d.ts.map +1 -1
  66. package/dist/utils/name-generator.js +2 -3
  67. package/dist/utils/name-generator.js.map +1 -1
  68. package/dist/utils/session-parser.d.ts +44 -0
  69. package/dist/utils/session-parser.d.ts.map +1 -0
  70. package/dist/utils/session-parser.js +122 -0
  71. package/dist/utils/session-parser.js.map +1 -0
  72. package/dist/utils/terminal-symbols.d.ts +22 -3
  73. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  74. package/dist/utils/terminal-symbols.js +52 -18
  75. package/dist/utils/terminal-symbols.js.map +1 -1
  76. package/dist/utils/token-tracker.d.ts +20 -0
  77. package/dist/utils/token-tracker.d.ts.map +1 -1
  78. package/dist/utils/token-tracker.js +57 -2
  79. package/dist/utils/token-tracker.js.map +1 -1
  80. package/package.json +1 -1
  81. package/src/commands/config.ts +0 -7
  82. package/src/commands/do.ts +141 -20
  83. package/src/commands/plan.ts +87 -1
  84. package/src/core/claude-runner.ts +16 -17
  85. package/src/core/failure-analyzer.ts +3 -3
  86. package/src/core/pull-request.ts +3 -3
  87. package/src/core/state-derivation.ts +20 -4
  88. package/src/core/worktree.ts +230 -0
  89. package/src/prompts/amend.ts +26 -11
  90. package/src/prompts/config-docs.md +91 -29
  91. package/src/prompts/planning.ts +26 -11
  92. package/src/types/config.ts +46 -21
  93. package/src/utils/config.ts +200 -33
  94. package/src/utils/frontmatter.ts +110 -0
  95. package/src/utils/name-generator.ts +2 -3
  96. package/src/utils/session-parser.ts +161 -0
  97. package/src/utils/terminal-symbols.ts +68 -16
  98. package/src/utils/token-tracker.ts +65 -2
  99. package/tests/unit/claude-runner-interactive.test.ts +8 -6
  100. package/tests/unit/claude-runner.test.ts +5 -66
  101. package/tests/unit/config-command.test.ts +6 -6
  102. package/tests/unit/config.test.ts +268 -45
  103. package/tests/unit/frontmatter.test.ts +182 -0
  104. package/tests/unit/post-execution-picker.test.ts +5 -0
  105. package/tests/unit/session-parser.test.ts +301 -0
  106. package/tests/unit/terminal-symbols.test.ts +142 -0
  107. package/tests/unit/token-tracker.test.ts +304 -1
  108. package/tests/unit/validation.test.ts +6 -4
  109. package/tests/unit/worktree.test.ts +242 -0
@@ -1,4 +1,24 @@
1
- import { resolveModelPricingCategory, getPricingConfig } from './config.js';
1
+ import { resolveModelPricingCategory, getPricingConfig, getRateLimitWindowConfig } from './config.js';
2
+ /**
3
+ * Sum multiple CostBreakdown objects into a single total.
4
+ */
5
+ export function sumCostBreakdowns(costs) {
6
+ const result = {
7
+ inputCost: 0,
8
+ outputCost: 0,
9
+ cacheReadCost: 0,
10
+ cacheCreateCost: 0,
11
+ totalCost: 0,
12
+ };
13
+ for (const cost of costs) {
14
+ result.inputCost += cost.inputCost;
15
+ result.outputCost += cost.outputCost;
16
+ result.cacheReadCost += cost.cacheReadCost;
17
+ result.cacheCreateCost += cost.cacheCreateCost;
18
+ result.totalCost += cost.totalCost;
19
+ }
20
+ return result;
21
+ }
2
22
  /**
3
23
  * Merge multiple UsageData objects into a single accumulated UsageData.
4
24
  * Sums all token fields and merges modelUsage maps.
@@ -45,10 +65,15 @@ export class TokenTracker {
45
65
  /**
46
66
  * Record usage data from a completed task.
47
67
  * Accepts an array of UsageData (one per attempt) and accumulates them.
68
+ * Cost is calculated per-attempt to avoid underreporting when some attempts
69
+ * have modelUsage and others only have aggregate fields.
48
70
  */
49
71
  addTask(taskId, attempts) {
50
72
  const usage = accumulateUsage(attempts);
51
- const cost = this.calculateCost(usage);
73
+ // Calculate cost per-attempt, then sum. This ensures attempts with only
74
+ // aggregate fields use sonnet fallback pricing independently.
75
+ const perAttemptCosts = attempts.map((attempt) => this.calculateCost(attempt));
76
+ const cost = sumCostBreakdowns(perAttemptCosts);
52
77
  const entry = { taskId, usage, cost, attempts };
53
78
  this.entries.push(entry);
54
79
  return entry;
@@ -138,5 +163,35 @@ export class TokenTracker {
138
163
  result.totalCost = result.inputCost + result.outputCost + result.cacheReadCost + result.cacheCreateCost;
139
164
  return result;
140
165
  }
166
+ /**
167
+ * Calculate the 5h rate limit window percentage for a given cost.
168
+ * Converts cost to Sonnet-equivalent tokens using the configured Sonnet pricing,
169
+ * then divides by the configured cap.
170
+ *
171
+ * @param totalCost - The total cost in dollars
172
+ * @param sonnetTokenCap - Optional override for the Sonnet-equivalent token cap (defaults to config value)
173
+ * @returns The percentage of the 5h window consumed (0-100+)
174
+ */
175
+ calculateRateLimitPercentage(totalCost, sonnetTokenCap) {
176
+ if (totalCost === 0)
177
+ return 0;
178
+ // Get the configured cap or use the provided override
179
+ const cap = sonnetTokenCap ?? getRateLimitWindowConfig().sonnetTokenCap;
180
+ // Calculate the average Sonnet cost per token
181
+ // Using the average of input and output pricing (simplified approach)
182
+ const sonnetPricing = this.pricingConfig.sonnet;
183
+ const avgSonnetCostPerToken = (sonnetPricing.inputPerMTok + sonnetPricing.outputPerMTok) / 2 / 1_000_000;
184
+ // Convert cost to Sonnet-equivalent tokens
185
+ const sonnetEquivalentTokens = totalCost / avgSonnetCostPerToken;
186
+ // Calculate percentage
187
+ return (sonnetEquivalentTokens / cap) * 100;
188
+ }
189
+ /**
190
+ * Get the cumulative 5h window percentage across all recorded tasks.
191
+ */
192
+ getCumulativeRateLimitPercentage(sonnetTokenCap) {
193
+ const totals = this.getTotals();
194
+ return this.calculateRateLimitPercentage(totals.cost.totalCost, sonnetTokenCap);
195
+ }
141
196
  }
142
197
  //# sourceMappingURL=token-tracker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafcode",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Automated Task Planning & Execution with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,6 @@ import { logger } from '../utils/logger.js';
9
9
  import {
10
10
  getConfigPath,
11
11
  getModel,
12
- getEffort,
13
12
  getModelShortName,
14
13
  validateConfig,
15
14
  ConfigValidationError,
@@ -160,17 +159,14 @@ async function runConfigSession(initialPrompt?: string): Promise<void> {
160
159
  // Try to load config, but fall back to defaults if it's broken
161
160
  // This allows raf config to be used to fix a broken config file
162
161
  let model: string;
163
- let effort: string;
164
162
  let configError: Error | null = null;
165
163
 
166
164
  try {
167
165
  model = getModel('config');
168
- effort = getEffort('config');
169
166
  } catch (error) {
170
167
  // Config file has errors - fall back to defaults so the session can launch
171
168
  configError = error instanceof Error ? error : new Error(String(error));
172
169
  model = DEFAULT_CONFIG.models.config;
173
- effort = DEFAULT_CONFIG.effort.config;
174
170
  // Clear the cached config so subsequent calls don't use the broken cache
175
171
  resetConfigCache();
176
172
  }
@@ -182,9 +178,6 @@ async function runConfigSession(initialPrompt?: string): Promise<void> {
182
178
  logger.newline();
183
179
  }
184
180
 
185
- // Set effort level env var for the Claude session
186
- process.env['CLAUDE_CODE_EFFORT_LEVEL'] = effort;
187
-
188
181
  // Load config docs
189
182
  let configDocs: string;
190
183
  try {
@@ -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 } 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,8 @@ import {
49
51
  mergeWorktreeBranch,
50
52
  removeWorktree,
51
53
  resolveWorktreeProjectByIdentifier,
54
+ pushMainBranch,
55
+ pullMainBranch,
52
56
  } from '../core/worktree.js';
53
57
  import { createPullRequest, prPreflight } from '../core/pull-request.js';
54
58
  import type { DoCommandOptions } from '../types/config.js';
@@ -61,6 +65,74 @@ import type { DoCommandOptions } from '../types/config.js';
61
65
  */
62
66
  export type PostExecutionAction = 'merge' | 'pr' | 'leave';
63
67
 
68
+ /**
69
+ * Result of resolving a task's model from frontmatter.
70
+ */
71
+ interface TaskModelResolution {
72
+ /** The resolved model (after ceiling is applied). */
73
+ model: string;
74
+ /** Whether a warning should be logged about missing frontmatter. */
75
+ missingFrontmatter: boolean;
76
+ /** Frontmatter parsing warnings to log. */
77
+ warnings: string[];
78
+ }
79
+
80
+ /**
81
+ * Resolve the execution model for a task from its frontmatter metadata.
82
+ *
83
+ * Resolution order:
84
+ * 1. Explicit `model` in frontmatter (subject to ceiling)
85
+ * 2. `effort` in frontmatter resolved via effortMapping (subject to ceiling)
86
+ * 3. Fallback to models.execute (the ceiling, with a warning)
87
+ *
88
+ * @param frontmatter - Parsed frontmatter from the plan file
89
+ * @param frontmatterWarnings - Warnings from frontmatter parsing
90
+ * @param ceilingModel - The ceiling model (usually models.execute from config)
91
+ * @param isRetry - Whether this is a retry attempt (escalates to ceiling)
92
+ */
93
+ function resolveTaskModel(
94
+ frontmatter: PlanFrontmatter | undefined,
95
+ frontmatterWarnings: string[] | undefined,
96
+ ceilingModel: string,
97
+ isRetry: boolean,
98
+ ): TaskModelResolution {
99
+ const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
100
+
101
+ // Retry escalation: always use the ceiling model on retry
102
+ if (isRetry) {
103
+ return { model: ceilingModel, missingFrontmatter: false, warnings };
104
+ }
105
+
106
+ // No frontmatter - fallback to ceiling with warning
107
+ if (!frontmatter) {
108
+ return {
109
+ model: ceilingModel,
110
+ missingFrontmatter: true,
111
+ warnings,
112
+ };
113
+ }
114
+
115
+ // Explicit model in frontmatter - apply ceiling
116
+ if (frontmatter.model) {
117
+ const model = applyModelCeiling(frontmatter.model, ceilingModel);
118
+ return { model, missingFrontmatter: false, warnings };
119
+ }
120
+
121
+ // Effort-based resolution - apply ceiling
122
+ if (frontmatter.effort) {
123
+ const mappedModel = resolveEffortToModel(frontmatter.effort);
124
+ const model = applyModelCeiling(mappedModel, ceilingModel);
125
+ return { model, missingFrontmatter: false, warnings };
126
+ }
127
+
128
+ // Frontmatter present but no effort or model - fallback to ceiling with warning
129
+ return {
130
+ model: ceilingModel,
131
+ missingFrontmatter: true,
132
+ warnings,
133
+ };
134
+ }
135
+
64
136
  /**
65
137
  * Format failure history for console output.
66
138
  * Shows attempts that failed before eventual success or final failure.
@@ -166,6 +238,18 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
166
238
  // Record original branch before any worktree operations
167
239
  originalBranch = getCurrentBranch() ?? undefined;
168
240
 
241
+ // Sync main branch before worktree operations (if enabled)
242
+ if (getSyncMainBranch()) {
243
+ const syncResult = pullMainBranch();
244
+ if (syncResult.success) {
245
+ if (syncResult.hadChanges) {
246
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
247
+ }
248
+ } else {
249
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
250
+ }
251
+ }
252
+
169
253
  if (!projectIdentifier) {
170
254
  // Auto-discovery flow
171
255
  const selected = await discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath);
@@ -394,7 +478,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
394
478
  force,
395
479
  maxRetries,
396
480
  autoCommit,
397
- showModel: true,
398
481
  model,
399
482
  worktreeCwd: worktreeRoot,
400
483
  }
@@ -500,6 +583,19 @@ async function executePostAction(
500
583
 
501
584
  case 'pr': {
502
585
  logger.newline();
586
+
587
+ // Push main branch to remote before PR creation (if enabled)
588
+ if (getSyncMainBranch()) {
589
+ const syncResult = pushMainBranch();
590
+ if (syncResult.success) {
591
+ if (syncResult.hadChanges) {
592
+ logger.info(`Pushed ${syncResult.mainBranch} to remote`);
593
+ }
594
+ } else {
595
+ logger.warn(`Could not push main branch: ${syncResult.error}`);
596
+ }
597
+ }
598
+
503
599
  logger.info(`Creating PR for branch "${worktreeBranch}"...`);
504
600
 
505
601
  const prResult = await createPullRequest(worktreeBranch, projectPath, { cwd: worktreeRoot });
@@ -658,7 +754,6 @@ interface SingleProjectOptions {
658
754
  force: boolean;
659
755
  maxRetries: number;
660
756
  autoCommit: boolean;
661
- showModel: boolean;
662
757
  model: string;
663
758
  /** Worktree root directory. When set, Claude runs with cwd in the worktree. */
664
759
  worktreeCwd?: string;
@@ -669,7 +764,7 @@ async function executeSingleProject(
669
764
  projectName: string,
670
765
  options: SingleProjectOptions
671
766
  ): Promise<ProjectExecutionResult> {
672
- const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model, worktreeCwd } = options;
767
+ const { timeout, verbose, debug, force, maxRetries, autoCommit, model, worktreeCwd } = options;
673
768
 
674
769
  if (!validatePlansExist(projectPath)) {
675
770
  return {
@@ -709,11 +804,12 @@ async function executeSingleProject(
709
804
  : state.tasks.filter((t) => t.status !== 'completed').map((t) => t.id)
710
805
  );
711
806
 
712
- // Set up shutdown handler
713
- const claudeRunner = new ClaudeRunner({ model });
807
+ // Set up shutdown handler - we'll register runners dynamically per-task
714
808
  const projectManager = new ProjectManager();
715
809
  shutdownHandler.init();
716
- shutdownHandler.registerClaudeRunner(claudeRunner);
810
+
811
+ // The ceiling model for all tasks (can be overridden per-task, subject to this ceiling)
812
+ const ceilingModel = model;
717
813
 
718
814
  // Initialize token tracker for usage reporting
719
815
  const tokenTracker = new TokenTracker();
@@ -725,15 +821,13 @@ async function executeSingleProject(
725
821
  // Start project timer
726
822
  const projectStartTime = Date.now();
727
823
 
824
+ // Resolve and display version + ceiling model info (before any tasks run)
825
+ const fullCeilingModelId = resolveFullModelId(ceilingModel);
826
+ logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId}`);
827
+
728
828
  if (verbose) {
729
829
  logger.info(`Executing project: ${projectName}`);
730
830
  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
831
  logger.newline();
738
832
  } else {
739
833
  // Minimal mode: show project header
@@ -923,16 +1017,45 @@ async function executeSingleProject(
923
1017
  });
924
1018
  timer.start();
925
1019
 
1020
+ // Log frontmatter warnings once before the retry loop
1021
+ if (task.frontmatterWarnings && task.frontmatterWarnings.length > 0) {
1022
+ for (const warning of task.frontmatterWarnings) {
1023
+ logger.warn(` Frontmatter warning: ${warning}`);
1024
+ }
1025
+ }
1026
+
926
1027
  while (!success && attempts < maxRetries) {
927
1028
  attempts++;
1029
+ const isRetry = attempts > 1;
1030
+
1031
+ // Resolve the model for this attempt (escalates to ceiling on retry)
1032
+ const modelResolution = resolveTaskModel(
1033
+ task.frontmatter,
1034
+ undefined, // warnings already logged above
1035
+ ceilingModel,
1036
+ isRetry,
1037
+ );
1038
+
1039
+ // Log missing frontmatter warning on first attempt only
1040
+ if (!isRetry && modelResolution.missingFrontmatter) {
1041
+ logger.warn(` No effort frontmatter found — using ceiling model`);
1042
+ }
1043
+
1044
+ // Create a runner for this attempt's model
1045
+ const taskRunner = new ClaudeRunner({ model: modelResolution.model });
1046
+ shutdownHandler.registerClaudeRunner(taskRunner);
928
1047
 
929
- if (verbose && attempts > 1) {
930
- logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel}...`);
1048
+ if (verbose && isRetry) {
1049
+ const retryModel = resolveFullModelId(modelResolution.model);
1050
+ logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel} (model: ${retryModel})...`);
1051
+ } else if (verbose && !isRetry) {
1052
+ const taskModel = resolveFullModelId(modelResolution.model);
1053
+ logger.info(` Model: ${taskModel}`);
931
1054
  }
932
1055
 
933
1056
  // Build execution prompt (inside loop to include retry context on retries)
934
1057
  // Check if previous outcome file exists for retry context
935
- const previousOutcomeFileForRetry = attempts > 1 && fs.existsSync(outcomeFilePath)
1058
+ const previousOutcomeFileForRetry = isRetry && fs.existsSync(outcomeFilePath)
936
1059
  ? outcomeFilePath
937
1060
  : undefined;
938
1061
 
@@ -961,18 +1084,16 @@ async function executeSingleProject(
961
1084
  } : undefined;
962
1085
 
963
1086
  // Run Claude (use worktree root as cwd if in worktree mode)
964
- const executeEffort = getEffort('execute');
965
1087
  const runnerOptions = {
966
1088
  timeout,
967
1089
  outcomeFilePath,
968
1090
  commitContext,
969
1091
  cwd: worktreeCwd,
970
- effortLevel: executeEffort,
971
1092
  verboseCheck: () => verboseToggle.isVerbose,
972
1093
  };
973
1094
  const result = verbose
974
- ? await claudeRunner.runVerbose(prompt, runnerOptions)
975
- : await claudeRunner.run(prompt, runnerOptions);
1095
+ ? await taskRunner.runVerbose(prompt, runnerOptions)
1096
+ : await taskRunner.run(prompt, runnerOptions);
976
1097
 
977
1098
  lastOutput = result.output;
978
1099
  if (result.usageData) {
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import * as crypto from 'node:crypto';
3
4
  import { Command } from 'commander';
4
5
  import { ProjectManager } from '../core/project-manager.js';
5
6
  import { ClaudeRunner } from '../core/claude-runner.js';
@@ -15,7 +16,10 @@ import {
15
16
  resolveModelOption,
16
17
  } from '../utils/validation.js';
17
18
  import { logger } from '../utils/logger.js';
18
- import { getWorktreeDefault, getModel, getModelShortName } from '../utils/config.js';
19
+ import { getWorktreeDefault, getModel, getModelShortName, getDisplayConfig, getPricingConfig, getSyncMainBranch } from '../utils/config.js';
20
+ import { TokenTracker } from '../utils/token-tracker.js';
21
+ import { parseSessionById } from '../utils/session-parser.js';
22
+ import { formatTokenTotalSummary, TokenSummaryOptions } from '../utils/terminal-symbols.js';
19
23
  import { generateProjectNames } from '../utils/name-generator.js';
20
24
  import { pickProjectName } from '../ui/name-picker.js';
21
25
  import {
@@ -48,6 +52,7 @@ import {
48
52
  validateWorktree,
49
53
  removeWorktree,
50
54
  computeWorktreeBaseDir,
55
+ pullMainBranch,
51
56
  } from '../core/worktree.js';
52
57
 
53
58
  interface PlanCommandOptions {
@@ -185,6 +190,18 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
185
190
  const repoRoot = getRepoRoot()!;
186
191
  const rafDir = getRafDir();
187
192
 
193
+ // Sync main branch before creating worktree (if enabled)
194
+ if (getSyncMainBranch()) {
195
+ const syncResult = pullMainBranch();
196
+ if (syncResult.success) {
197
+ if (syncResult.hadChanges) {
198
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
199
+ }
200
+ } else {
201
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
202
+ }
203
+ }
204
+
188
205
  // Compute project number from main repo's RAF directory
189
206
  const projectNumber = getNextProjectNumber(rafDir);
190
207
  const sanitizedName = sanitizeProjectName(finalProjectName);
@@ -272,17 +289,25 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
272
289
  worktreeMode,
273
290
  });
274
291
 
292
+ // Generate session ID for token tracking
293
+ const sessionId = crypto.randomUUID();
294
+ const sessionCwd = worktreePath ?? process.cwd();
295
+
275
296
  try {
276
297
  const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
277
298
  dangerouslySkipPermissions: autoMode,
278
299
  // Run Claude session in the worktree root if in worktree mode
279
300
  cwd: worktreePath ?? undefined,
301
+ sessionId,
280
302
  });
281
303
 
282
304
  if (exitCode !== 0) {
283
305
  logger.warn(`Claude exited with code ${exitCode}`);
284
306
  }
285
307
 
308
+ // Parse session file and display token usage summary
309
+ displayPlanSessionTokenSummary(sessionId, sessionCwd);
310
+
286
311
  // Check for created plan files
287
312
  const plansDir = getPlansDir(projectPath);
288
313
  const planFiles = fs.existsSync(plansDir)
@@ -412,6 +437,18 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
412
437
  logger.info(`Recreated worktree from branch: ${folderName}`);
413
438
  } else {
414
439
  // No branch — create fresh worktree and copy project files
440
+ // Sync main branch before creating worktree (if enabled)
441
+ if (getSyncMainBranch()) {
442
+ const syncResult = pullMainBranch();
443
+ if (syncResult.success) {
444
+ if (syncResult.hadChanges) {
445
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
446
+ }
447
+ } else {
448
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
449
+ }
450
+ }
451
+
415
452
  const result = createWorktree(repoBasename, folderName);
416
453
  if (!result.success) {
417
454
  logger.error(`Failed to create worktree: ${result.error}`);
@@ -567,17 +604,25 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
567
604
  worktreeMode,
568
605
  });
569
606
 
607
+ // Generate session ID for token tracking
608
+ const sessionId = crypto.randomUUID();
609
+ const sessionCwd = worktreePath ?? process.cwd();
610
+
570
611
  try {
571
612
  const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
572
613
  dangerouslySkipPermissions: autoMode,
573
614
  // Run Claude session in the worktree root if in worktree mode
574
615
  cwd: worktreePath ?? undefined,
616
+ sessionId,
575
617
  });
576
618
 
577
619
  if (exitCode !== 0) {
578
620
  logger.warn(`Claude exited with code ${exitCode}`);
579
621
  }
580
622
 
623
+ // Parse session file and display token usage summary
624
+ displayPlanSessionTokenSummary(sessionId, sessionCwd);
625
+
581
626
  // Check for new plan files
582
627
  const allPlanFiles = fs.existsSync(plansDir)
583
628
  ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
@@ -653,3 +698,44 @@ ${taskList}
653
698
  # Describe what you want to add below:
654
699
  `;
655
700
  }
701
+
702
+ /**
703
+ * Display token usage summary for a plan/amend session.
704
+ * Parses the Claude session file and displays formatted usage data.
705
+ */
706
+ function displayPlanSessionTokenSummary(sessionId: string, cwd: string): void {
707
+ const result = parseSessionById(sessionId, cwd);
708
+
709
+ if (!result.success) {
710
+ // Session file not found or couldn't be parsed - just log debug and continue
711
+ logger.debug(`Could not parse session file: ${result.error}`);
712
+ return;
713
+ }
714
+
715
+ // Check if there's any usage data
716
+ const totalTokens = result.usage.inputTokens + result.usage.outputTokens;
717
+ if (totalTokens === 0) {
718
+ logger.debug('No token usage data found in session file');
719
+ return;
720
+ }
721
+
722
+ // Create tracker and add the session as a single "task"
723
+ const pricingConfig = getPricingConfig();
724
+ const tracker = new TokenTracker(pricingConfig);
725
+ const entry = tracker.addTask('plan', [result.usage]);
726
+
727
+ // Get display options
728
+ const displayConfig = getDisplayConfig();
729
+ const options: TokenSummaryOptions = {
730
+ showCacheTokens: displayConfig.showCacheTokens,
731
+ showRateLimitEstimate: displayConfig.showRateLimitEstimate,
732
+ rateLimitPercentage: displayConfig.showRateLimitEstimate
733
+ ? tracker.calculateRateLimitPercentage(entry.cost.totalCost)
734
+ : undefined,
735
+ };
736
+
737
+ // Display the summary
738
+ logger.newline();
739
+ const summary = formatTokenTotalSummary(result.usage, entry.cost, options);
740
+ console.log(summary);
741
+ }
@@ -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
  }
@@ -32,6 +31,12 @@ export interface ClaudeRunnerOptions {
32
31
  * Claude will still ask planning interview questions.
33
32
  */
34
33
  dangerouslySkipPermissions?: boolean;
34
+ /**
35
+ * Session ID for Claude CLI. When provided, passed as --session-id to enable
36
+ * locating the session file after the session ends for token tracking.
37
+ * Only used in interactive mode (runInteractive).
38
+ */
39
+ sessionId?: string;
35
40
  /**
36
41
  * Path to the outcome file. When provided, enables completion detection:
37
42
  * - Monitors stdout for completion markers (<promise>COMPLETE/FAILED</promise>)
@@ -53,12 +58,6 @@ export interface ClaudeRunnerOptions {
53
58
  /** Path to the outcome file that should be committed. */
54
59
  outcomeFilePath: string;
55
60
  };
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
61
  /**
63
62
  * Dynamic verbose display callback. When provided, called for each stream event
64
63
  * to determine whether to write display output to stdout. Overrides the static
@@ -287,7 +286,7 @@ export class ClaudeRunner {
287
286
  userMessage: string,
288
287
  options: ClaudeRunnerOptions = {}
289
288
  ): Promise<number> {
290
- const { cwd = process.cwd(), dangerouslySkipPermissions = false } = options;
289
+ const { cwd = process.cwd(), dangerouslySkipPermissions = false, sessionId } = options;
291
290
 
292
291
  return new Promise((resolve) => {
293
292
  const args = ['--model', this.model];
@@ -297,6 +296,11 @@ export class ClaudeRunner {
297
296
  args.push('--dangerously-skip-permissions');
298
297
  }
299
298
 
299
+ // Add --session-id if provided (for token tracking)
300
+ if (sessionId) {
301
+ args.push('--session-id', sessionId);
302
+ }
303
+
300
304
  // System instructions via --append-system-prompt
301
305
  args.push('--append-system-prompt', systemPrompt);
302
306
 
@@ -415,7 +419,7 @@ export class ClaudeRunner {
415
419
  options: ClaudeRunnerOptions,
416
420
  verbose: boolean,
417
421
  ): Promise<RunResult> {
418
- const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel, verboseCheck } = options;
422
+ const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, verboseCheck } = options;
419
423
  // Ensure timeout is a positive number, fallback to 60 minutes
420
424
  const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
421
425
  const timeoutMs = validatedTimeout * 60 * 1000;
@@ -437,11 +441,6 @@ export class ClaudeRunner {
437
441
  logger.debug(`Prompt length: ${prompt.length}, timeout: ${timeoutMs}ms, cwd: ${cwd}`);
438
442
  logger.debug(`Claude path: ${claudePath}`);
439
443
 
440
- // Build env, optionally injecting effort level
441
- const env = effortLevel
442
- ? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
443
- : process.env;
444
-
445
444
  logger.debug('Spawning process...');
446
445
  // Use --output-format stream-json --verbose to get real-time streaming events
447
446
  // including tool calls, file operations, and token usage in the result event.
@@ -460,7 +459,7 @@ export class ClaudeRunner {
460
459
  'Execute the task as described in the system prompt.',
461
460
  ], {
462
461
  cwd,
463
- env,
462
+ env: process.env,
464
463
  stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
465
464
  });
466
465