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.
- package/CLAUDE.md +19 -4
- package/RAF/ahvrih-rate-forge/decisions.md +70 -0
- package/RAF/ahvrih-rate-forge/input.md +44 -0
- package/RAF/ahvrih-rate-forge/outcomes/01-remove-claude-command-config.md +58 -0
- package/RAF/ahvrih-rate-forge/outcomes/02-fix-mixed-attempt-cost.md +46 -0
- package/RAF/ahvrih-rate-forge/outcomes/03-rate-limit-estimation.md +82 -0
- package/RAF/ahvrih-rate-forge/outcomes/04-show-version-in-do-logs.md +45 -0
- package/RAF/ahvrih-rate-forge/outcomes/05-sync-main-before-worktree.md +96 -0
- package/RAF/ahvrih-rate-forge/outcomes/06-sync-readme-with-codebase.md +45 -0
- package/RAF/ahvrih-rate-forge/outcomes/07-no-session-persistence.md +26 -0
- package/RAF/ahvrih-rate-forge/outcomes/08-plan-execution-metadata.md +130 -0
- package/RAF/ahvrih-rate-forge/plans/01-remove-claude-command-config.md +36 -0
- package/RAF/ahvrih-rate-forge/plans/02-fix-mixed-attempt-cost.md +33 -0
- package/RAF/ahvrih-rate-forge/plans/03-rate-limit-estimation.md +82 -0
- package/RAF/ahvrih-rate-forge/plans/04-show-version-in-do-logs.md +32 -0
- package/RAF/ahvrih-rate-forge/plans/05-sync-main-before-worktree.md +40 -0
- package/RAF/ahvrih-rate-forge/plans/06-sync-readme-with-codebase.md +61 -0
- package/RAF/ahvrih-rate-forge/plans/07-no-session-persistence.md +28 -0
- package/RAF/ahvrih-rate-forge/plans/08-plan-execution-metadata.md +123 -0
- package/README.md +27 -7
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +1 -6
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +106 -18
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +77 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +6 -6
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +9 -10
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +3 -3
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/pull-request.js +3 -3
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/state-derivation.d.ts +5 -0
- package/dist/core/state-derivation.d.ts.map +1 -1
- package/dist/core/state-derivation.js +14 -4
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/core/worktree.d.ts +32 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +215 -0
- package/dist/core/worktree.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +26 -11
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +26 -11
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +30 -13
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +14 -10
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +47 -4
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +176 -30
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +43 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +85 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +2 -3
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/session-parser.d.ts +44 -0
- package/dist/utils/session-parser.d.ts.map +1 -0
- package/dist/utils/session-parser.js +122 -0
- package/dist/utils/session-parser.js.map +1 -0
- package/dist/utils/terminal-symbols.d.ts +22 -3
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +52 -18
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +20 -0
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +57 -2
- package/dist/utils/token-tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +0 -7
- package/src/commands/do.ts +141 -20
- package/src/commands/plan.ts +87 -1
- package/src/core/claude-runner.ts +16 -17
- package/src/core/failure-analyzer.ts +3 -3
- package/src/core/pull-request.ts +3 -3
- package/src/core/state-derivation.ts +20 -4
- package/src/core/worktree.ts +230 -0
- package/src/prompts/amend.ts +26 -11
- package/src/prompts/config-docs.md +91 -29
- package/src/prompts/planning.ts +26 -11
- package/src/types/config.ts +46 -21
- package/src/utils/config.ts +200 -33
- package/src/utils/frontmatter.ts +110 -0
- package/src/utils/name-generator.ts +2 -3
- package/src/utils/session-parser.ts +161 -0
- package/src/utils/terminal-symbols.ts +68 -16
- package/src/utils/token-tracker.ts +65 -2
- package/tests/unit/claude-runner-interactive.test.ts +8 -6
- package/tests/unit/claude-runner.test.ts +5 -66
- package/tests/unit/config-command.test.ts +6 -6
- package/tests/unit/config.test.ts +268 -45
- package/tests/unit/frontmatter.test.ts +182 -0
- package/tests/unit/post-execution-picker.test.ts +5 -0
- package/tests/unit/session-parser.test.ts +301 -0
- package/tests/unit/terminal-symbols.test.ts +142 -0
- package/tests/unit/token-tracker.test.ts +304 -1
- package/tests/unit/validation.test.ts +6 -4
- 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
|
-
|
|
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":"AACA,OAAO,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/config.ts
CHANGED
|
@@ -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 {
|
package/src/commands/do.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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 &&
|
|
930
|
-
|
|
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 =
|
|
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
|
|
975
|
-
: await
|
|
1095
|
+
? await taskRunner.runVerbose(prompt, runnerOptions)
|
|
1096
|
+
: await taskRunner.run(prompt, runnerOptions);
|
|
976
1097
|
|
|
977
1098
|
lastOutput = result.output;
|
|
978
1099
|
if (result.usageData) {
|
package/src/commands/plan.ts
CHANGED
|
@@ -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 {
|
|
9
|
+
import { getModel } from '../utils/config.js';
|
|
10
10
|
|
|
11
11
|
function getClaudePath(): string {
|
|
12
|
-
const cmd = getClaudeCommand();
|
|
13
12
|
try {
|
|
14
|
-
return execSync(
|
|
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,
|
|
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
|
|