rafcode 2.2.0 → 2.3.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/RAF/ahtahs-token-reaper/decisions.md +37 -0
- package/RAF/ahtahs-token-reaper/input.md +20 -0
- package/RAF/ahtahs-token-reaper/outcomes/01-extend-token-tracker-data-model.md +42 -0
- package/RAF/ahtahs-token-reaper/outcomes/02-accumulate-usage-in-retry-loop.md +31 -0
- package/RAF/ahtahs-token-reaper/outcomes/03-per-attempt-display-formatting.md +60 -0
- package/RAF/ahtahs-token-reaper/outcomes/04-add-model-name-to-claude-call-logs.md +57 -0
- package/RAF/ahtahs-token-reaper/outcomes/05-handle-invalid-config-in-raf-config.md +46 -0
- package/RAF/ahtahs-token-reaper/outcomes/06-fix-verbose-toggle-timer-display.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/01-extend-token-tracker-data-model.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/02-accumulate-usage-in-retry-loop.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/03-per-attempt-display-formatting.md +43 -0
- package/RAF/ahtahs-token-reaper/plans/04-add-model-name-to-claude-call-logs.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/05-handle-invalid-config-in-raf-config.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/06-fix-verbose-toggle-timer-display.md +40 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +27 -5
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.js +17 -10
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.js +3 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/pull-request.d.ts.map +1 -1
- package/dist/core/pull-request.js +3 -1
- package/dist/core/pull-request.js.map +1 -1
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +21 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +8 -4
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +31 -6
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +11 -1
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +37 -2
- package/dist/utils/token-tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +30 -4
- package/src/commands/do.ts +17 -10
- package/src/commands/plan.ts +3 -2
- package/src/core/pull-request.ts +3 -1
- package/src/utils/config.ts +22 -0
- package/src/utils/terminal-symbols.ts +42 -7
- package/src/utils/token-tracker.ts +44 -2
- package/tests/unit/config-command.test.ts +80 -1
- package/tests/unit/config.test.ts +24 -0
- package/tests/unit/terminal-symbols.test.ts +121 -33
- package/tests/unit/timer-verbose-integration.test.ts +170 -0
- package/tests/unit/token-tracker.test.ts +350 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-symbols.d.ts","sourceRoot":"","sources":["../../src/utils/terminal-symbols.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal-symbols.d.ts","sourceRoot":"","sources":["../../src/utils/terminal-symbols.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,OAAO;;;;;;;CAOV,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAYpF;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAYR;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE,MAAU,GAClB,MAAM,CAwBR;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAM7D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AA+BD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,cAAc,EACrB,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,aAAa,GACzD,MAAM,CAgBR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAoBrF"}
|
|
@@ -114,12 +114,13 @@ export function formatCost(cost) {
|
|
|
114
114
|
return `$${cost.toFixed(2)}`;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* Formats a
|
|
118
|
-
*
|
|
117
|
+
* Formats a single line of token usage (for a single attempt or total).
|
|
118
|
+
* Used internally by formatTaskTokenSummary.
|
|
119
119
|
*/
|
|
120
|
-
|
|
120
|
+
function formatTokenLine(usage, costValue, prefix = '', indent = ' ') {
|
|
121
121
|
const parts = [];
|
|
122
|
-
|
|
122
|
+
const tokenPart = `${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`;
|
|
123
|
+
parts.push(prefix ? `${prefix}: ${tokenPart}` : `Tokens: ${tokenPart}`);
|
|
123
124
|
const cacheTotal = usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
|
|
124
125
|
if (cacheTotal > 0) {
|
|
125
126
|
if (usage.cacheReadInputTokens > 0 && usage.cacheCreationInputTokens > 0) {
|
|
@@ -132,8 +133,32 @@ export function formatTaskTokenSummary(usage, cost) {
|
|
|
132
133
|
parts.push(`Cache: ${formatNumber(usage.cacheCreationInputTokens)} created`);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
|
-
parts.push(`Est. cost: ${formatCost(
|
|
136
|
-
return
|
|
136
|
+
parts.push(`Est. cost: ${formatCost(costValue)}`);
|
|
137
|
+
return `${indent}${parts.join(' | ')}`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Formats a per-task token usage summary.
|
|
141
|
+
* For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42"
|
|
142
|
+
* For multi-attempt tasks: shows per-attempt breakdown plus total.
|
|
143
|
+
*
|
|
144
|
+
* @param entry - The TaskUsageEntry containing accumulated usage, cost, and attempts array
|
|
145
|
+
* @param calculateAttemptCost - Optional function to calculate cost for a single attempt's UsageData
|
|
146
|
+
*/
|
|
147
|
+
export function formatTaskTokenSummary(entry, calculateAttemptCost) {
|
|
148
|
+
// Single-attempt: render exactly as before (no per-attempt breakdown)
|
|
149
|
+
if (entry.attempts.length <= 1) {
|
|
150
|
+
return formatTokenLine(entry.usage, entry.cost.totalCost);
|
|
151
|
+
}
|
|
152
|
+
// Multi-attempt: show per-attempt lines plus total
|
|
153
|
+
const lines = [];
|
|
154
|
+
entry.attempts.forEach((attemptUsage, i) => {
|
|
155
|
+
const attemptCost = calculateAttemptCost
|
|
156
|
+
? calculateAttemptCost(attemptUsage).totalCost
|
|
157
|
+
: 0;
|
|
158
|
+
lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' '));
|
|
159
|
+
});
|
|
160
|
+
lines.push(formatTokenLine(entry.usage, entry.cost.totalCost, 'Total', ' '));
|
|
161
|
+
return lines.join('\n');
|
|
137
162
|
}
|
|
138
163
|
/**
|
|
139
164
|
* Formats the grand total token usage summary block.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-symbols.js","sourceRoot":"","sources":["../../src/utils/terminal-symbols.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"terminal-symbols.js","sourceRoot":"","sources":["../../src/utils/terminal-symbols.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,9 +10,18 @@ export interface CostBreakdown {
|
|
|
10
10
|
/** Per-task usage snapshot stored by the tracker. */
|
|
11
11
|
export interface TaskUsageEntry {
|
|
12
12
|
taskId: string;
|
|
13
|
+
/** Accumulated usage across all attempts. */
|
|
13
14
|
usage: UsageData;
|
|
15
|
+
/** Cost breakdown for accumulated usage. */
|
|
14
16
|
cost: CostBreakdown;
|
|
17
|
+
/** Raw per-attempt usage data (for display breakdowns). */
|
|
18
|
+
attempts: UsageData[];
|
|
15
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Merge multiple UsageData objects into a single accumulated UsageData.
|
|
22
|
+
* Sums all token fields and merges modelUsage maps.
|
|
23
|
+
*/
|
|
24
|
+
export declare function accumulateUsage(attempts: UsageData[]): UsageData;
|
|
16
25
|
/**
|
|
17
26
|
* Accumulates token usage across multiple task executions and calculates costs
|
|
18
27
|
* using configurable per-model pricing.
|
|
@@ -23,8 +32,9 @@ export declare class TokenTracker {
|
|
|
23
32
|
constructor(pricingConfig?: PricingConfig);
|
|
24
33
|
/**
|
|
25
34
|
* Record usage data from a completed task.
|
|
35
|
+
* Accepts an array of UsageData (one per attempt) and accumulates them.
|
|
26
36
|
*/
|
|
27
|
-
addTask(taskId: string,
|
|
37
|
+
addTask(taskId: string, attempts: UsageData[]): TaskUsageEntry;
|
|
28
38
|
/**
|
|
29
39
|
* Get all recorded task entries.
|
|
30
40
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAG9D,6DAA6D;AAC7D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAG9D,6DAA6D;AAC7D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,KAAK,EAAE,SAAS,CAAC;IACjB,4CAA4C;IAC5C,IAAI,EAAE,aAAa,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,CA8BhE;AAED;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,aAAa,CAAC,EAAE,aAAa;IAIzC;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,cAAc;IAQ9D;;OAEG;IACH,UAAU,IAAI,SAAS,cAAc,EAAE;IAIvC;;OAEG;IACH,SAAS,IAAI;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE;IA6CtD;;;OAGG;IACH,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa;CAkC/C"}
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
import { resolveModelPricingCategory, getPricingConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Merge multiple UsageData objects into a single accumulated UsageData.
|
|
4
|
+
* Sums all token fields and merges modelUsage maps.
|
|
5
|
+
*/
|
|
6
|
+
export function accumulateUsage(attempts) {
|
|
7
|
+
const result = {
|
|
8
|
+
inputTokens: 0,
|
|
9
|
+
outputTokens: 0,
|
|
10
|
+
cacheReadInputTokens: 0,
|
|
11
|
+
cacheCreationInputTokens: 0,
|
|
12
|
+
modelUsage: {},
|
|
13
|
+
};
|
|
14
|
+
for (const attempt of attempts) {
|
|
15
|
+
result.inputTokens += attempt.inputTokens;
|
|
16
|
+
result.outputTokens += attempt.outputTokens;
|
|
17
|
+
result.cacheReadInputTokens += attempt.cacheReadInputTokens;
|
|
18
|
+
result.cacheCreationInputTokens += attempt.cacheCreationInputTokens;
|
|
19
|
+
// Merge per-model usage
|
|
20
|
+
for (const [modelId, modelUsage] of Object.entries(attempt.modelUsage)) {
|
|
21
|
+
const existing = result.modelUsage[modelId];
|
|
22
|
+
if (existing) {
|
|
23
|
+
existing.inputTokens += modelUsage.inputTokens;
|
|
24
|
+
existing.outputTokens += modelUsage.outputTokens;
|
|
25
|
+
existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
|
|
26
|
+
existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
result.modelUsage[modelId] = { ...modelUsage };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
2
35
|
/**
|
|
3
36
|
* Accumulates token usage across multiple task executions and calculates costs
|
|
4
37
|
* using configurable per-model pricing.
|
|
@@ -11,10 +44,12 @@ export class TokenTracker {
|
|
|
11
44
|
}
|
|
12
45
|
/**
|
|
13
46
|
* Record usage data from a completed task.
|
|
47
|
+
* Accepts an array of UsageData (one per attempt) and accumulates them.
|
|
14
48
|
*/
|
|
15
|
-
addTask(taskId,
|
|
49
|
+
addTask(taskId, attempts) {
|
|
50
|
+
const usage = accumulateUsage(attempts);
|
|
16
51
|
const cost = this.calculateCost(usage);
|
|
17
|
-
const entry = { taskId, usage, cost };
|
|
52
|
+
const entry = { taskId, usage, cost, attempts };
|
|
18
53
|
this.entries.push(entry);
|
|
19
54
|
return entry;
|
|
20
55
|
}
|
|
@@ -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
|
@@ -10,9 +10,12 @@ import {
|
|
|
10
10
|
getConfigPath,
|
|
11
11
|
getModel,
|
|
12
12
|
getEffort,
|
|
13
|
+
getModelShortName,
|
|
13
14
|
validateConfig,
|
|
14
15
|
ConfigValidationError,
|
|
16
|
+
resetConfigCache,
|
|
15
17
|
} from '../utils/config.js';
|
|
18
|
+
import { DEFAULT_CONFIG } from '../types/config.js';
|
|
16
19
|
|
|
17
20
|
interface ConfigCommandOptions {
|
|
18
21
|
reset?: boolean;
|
|
@@ -153,8 +156,31 @@ async function handleReset(): Promise<void> {
|
|
|
153
156
|
|
|
154
157
|
async function runConfigSession(initialPrompt?: string): Promise<void> {
|
|
155
158
|
const configPath = getConfigPath();
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
|
|
160
|
+
// Try to load config, but fall back to defaults if it's broken
|
|
161
|
+
// This allows raf config to be used to fix a broken config file
|
|
162
|
+
let model: string;
|
|
163
|
+
let effort: string;
|
|
164
|
+
let configError: Error | null = null;
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
model = getModel('config');
|
|
168
|
+
effort = getEffort('config');
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// Config file has errors - fall back to defaults so the session can launch
|
|
171
|
+
configError = error instanceof Error ? error : new Error(String(error));
|
|
172
|
+
model = DEFAULT_CONFIG.models.config;
|
|
173
|
+
effort = DEFAULT_CONFIG.effort.config;
|
|
174
|
+
// Clear the cached config so subsequent calls don't use the broken cache
|
|
175
|
+
resetConfigCache();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Warn user if config has errors, before starting the session
|
|
179
|
+
if (configError) {
|
|
180
|
+
logger.warn(`Config file has errors, using defaults: ${configError.message}`);
|
|
181
|
+
logger.warn('Fix the config in this session or run `raf config --reset` to start fresh.');
|
|
182
|
+
logger.newline();
|
|
183
|
+
}
|
|
158
184
|
|
|
159
185
|
// Set effort level env var for the Claude session
|
|
160
186
|
process.env['CLAUDE_CODE_EFFORT_LEVEL'] = effort;
|
|
@@ -181,8 +207,8 @@ async function runConfigSession(initialPrompt?: string): Promise<void> {
|
|
|
181
207
|
shutdownHandler.init();
|
|
182
208
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
183
209
|
|
|
184
|
-
|
|
185
|
-
logger.info(`
|
|
210
|
+
const configModel = getModelShortName(model);
|
|
211
|
+
logger.info(`Starting config session with ${configModel}...`);
|
|
186
212
|
logger.newline();
|
|
187
213
|
|
|
188
214
|
try {
|
package/src/commands/do.ts
CHANGED
|
@@ -13,7 +13,7 @@ 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 } from '../utils/config.js';
|
|
16
|
+
import { getConfig, getEffort, getWorktreeDefault, getModel, getModelShortName } from '../utils/config.js';
|
|
17
17
|
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
18
18
|
import { createStatusLine } from '../utils/status-line.js';
|
|
19
19
|
import {
|
|
@@ -905,13 +905,19 @@ async function executeSingleProject(
|
|
|
905
905
|
let attempts = 0;
|
|
906
906
|
let lastOutput = '';
|
|
907
907
|
let failureReason = '';
|
|
908
|
-
|
|
908
|
+
// Collect usage data from all attempts (for accurate token tracking across retries)
|
|
909
|
+
const attemptUsageData: import('../types/config.js').UsageData[] = [];
|
|
909
910
|
// Track failure history for each attempt (attempt number -> reason)
|
|
910
911
|
const failureHistory: Array<{ attempt: number; reason: string }> = [];
|
|
911
912
|
|
|
912
913
|
// Set up timer for elapsed time tracking
|
|
913
914
|
const statusLine = createStatusLine();
|
|
914
915
|
const timer = createTaskTimer(verbose ? undefined : (elapsed) => {
|
|
916
|
+
// When verbose is toggled ON at runtime, clear the status line and skip updates
|
|
917
|
+
if (verboseToggle.isVerbose) {
|
|
918
|
+
statusLine.clear();
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
915
921
|
// Show running status with task name and timer (updates in place)
|
|
916
922
|
statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed, taskId));
|
|
917
923
|
});
|
|
@@ -970,7 +976,7 @@ async function executeSingleProject(
|
|
|
970
976
|
|
|
971
977
|
lastOutput = result.output;
|
|
972
978
|
if (result.usageData) {
|
|
973
|
-
|
|
979
|
+
attemptUsageData.push(result.usageData);
|
|
974
980
|
}
|
|
975
981
|
|
|
976
982
|
// Parse result
|
|
@@ -1088,9 +1094,9 @@ Task completed. No detailed report provided.
|
|
|
1088
1094
|
}
|
|
1089
1095
|
|
|
1090
1096
|
// Track and display token usage for this task
|
|
1091
|
-
if (
|
|
1092
|
-
const entry = tokenTracker.addTask(task.id,
|
|
1093
|
-
logger.dim(formatTaskTokenSummary(entry
|
|
1097
|
+
if (attemptUsageData.length > 0) {
|
|
1098
|
+
const entry = tokenTracker.addTask(task.id, attemptUsageData);
|
|
1099
|
+
logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u)));
|
|
1094
1100
|
}
|
|
1095
1101
|
|
|
1096
1102
|
completedInSession.add(task.id);
|
|
@@ -1108,16 +1114,17 @@ Task completed. No detailed report provided.
|
|
|
1108
1114
|
|
|
1109
1115
|
if (verbose) {
|
|
1110
1116
|
logger.error(` Task ${taskLabel} failed: ${failureReason} (${elapsedFormatted})`);
|
|
1111
|
-
|
|
1117
|
+
const analysisModel = getModelShortName(getModel('failureAnalysis'));
|
|
1118
|
+
logger.info(` Analyzing failure with ${analysisModel}...`);
|
|
1112
1119
|
} else {
|
|
1113
1120
|
// Minimal mode: show failed task line
|
|
1114
1121
|
logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id));
|
|
1115
1122
|
}
|
|
1116
1123
|
|
|
1117
1124
|
// Track token usage even for failed tasks (partial data still useful for totals)
|
|
1118
|
-
if (
|
|
1119
|
-
const entry = tokenTracker.addTask(task.id,
|
|
1120
|
-
logger.dim(formatTaskTokenSummary(entry
|
|
1125
|
+
if (attemptUsageData.length > 0) {
|
|
1126
|
+
const entry = tokenTracker.addTask(task.id, attemptUsageData);
|
|
1127
|
+
logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u)));
|
|
1121
1128
|
}
|
|
1122
1129
|
|
|
1123
1130
|
// Analyze failure and generate structured report
|
package/src/commands/plan.ts
CHANGED
|
@@ -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 } from '../utils/config.js';
|
|
18
|
+
import { getWorktreeDefault, getModel, getModelShortName } 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 {
|
|
@@ -155,7 +155,8 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
155
155
|
// Get or generate project name
|
|
156
156
|
let finalProjectName = projectName;
|
|
157
157
|
if (!finalProjectName) {
|
|
158
|
-
|
|
158
|
+
const nameModel = getModelShortName(getModel('nameGeneration'));
|
|
159
|
+
logger.info(`Generating project name suggestions with ${nameModel}...`);
|
|
159
160
|
const suggestedNames = await generateProjectNames(cleanInput);
|
|
160
161
|
logger.newline();
|
|
161
162
|
|
package/src/core/pull-request.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
-
import { getModel, getClaudeCommand } from '../utils/config.js';
|
|
6
|
+
import { getModel, getClaudeCommand, getModelShortName } from '../utils/config.js';
|
|
7
7
|
import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN } from '../utils/paths.js';
|
|
8
8
|
|
|
9
9
|
export interface PrCreateResult {
|
|
@@ -279,6 +279,8 @@ Respond with ONLY the PR body in this exact format (no extra text, no code fence
|
|
|
279
279
|
[1-3 bullet points describing how to verify these changes work correctly]`;
|
|
280
280
|
|
|
281
281
|
try {
|
|
282
|
+
const prModel = getModelShortName(getModel('prGeneration'));
|
|
283
|
+
logger.info(`Generating PR with ${prModel}...`);
|
|
282
284
|
const body = await callClaudeForPrBody(prompt, timeoutMs);
|
|
283
285
|
return body;
|
|
284
286
|
} catch (error) {
|
package/src/utils/config.ts
CHANGED
|
@@ -305,6 +305,28 @@ export function getClaudeCommand(): string {
|
|
|
305
305
|
return getResolvedConfig().claudeCommand;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Extract the short model alias (opus, sonnet, haiku) from a model ID.
|
|
310
|
+
* Works with both full model IDs (e.g., "claude-sonnet-4-5-20250929") and already-short names ("sonnet").
|
|
311
|
+
* Returns the original string if no known alias can be extracted.
|
|
312
|
+
*/
|
|
313
|
+
export function getModelShortName(modelId: string): string {
|
|
314
|
+
// Already a short alias
|
|
315
|
+
if (modelId === 'opus' || modelId === 'sonnet' || modelId === 'haiku') {
|
|
316
|
+
return modelId;
|
|
317
|
+
}
|
|
318
|
+
// Extract family from full model ID: claude-{family}-{version}
|
|
319
|
+
const match = modelId.match(/^claude-([a-z]+)-/);
|
|
320
|
+
if (match) {
|
|
321
|
+
const family = match[1];
|
|
322
|
+
if (family === 'opus' || family === 'sonnet' || family === 'haiku') {
|
|
323
|
+
return family;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Unknown format, return as-is
|
|
327
|
+
return modelId;
|
|
328
|
+
}
|
|
329
|
+
|
|
308
330
|
/**
|
|
309
331
|
* Map a full model ID (e.g., `claude-opus-4-6`) or short alias to a pricing category.
|
|
310
332
|
* Returns null if the model cannot be mapped.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { formatElapsedTime } from './timer.js';
|
|
7
7
|
import type { UsageData } from '../types/config.js';
|
|
8
|
-
import type { CostBreakdown } from './token-tracker.js';
|
|
8
|
+
import type { CostBreakdown, TaskUsageEntry } from './token-tracker.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Visual symbols for terminal output using dots/symbols style.
|
|
@@ -146,12 +146,18 @@ export function formatCost(cost: number): string {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
* Formats a
|
|
150
|
-
*
|
|
149
|
+
* Formats a single line of token usage (for a single attempt or total).
|
|
150
|
+
* Used internally by formatTaskTokenSummary.
|
|
151
151
|
*/
|
|
152
|
-
|
|
152
|
+
function formatTokenLine(
|
|
153
|
+
usage: UsageData,
|
|
154
|
+
costValue: number,
|
|
155
|
+
prefix: string = '',
|
|
156
|
+
indent: string = ' '
|
|
157
|
+
): string {
|
|
153
158
|
const parts: string[] = [];
|
|
154
|
-
|
|
159
|
+
const tokenPart = `${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`;
|
|
160
|
+
parts.push(prefix ? `${prefix}: ${tokenPart}` : `Tokens: ${tokenPart}`);
|
|
155
161
|
|
|
156
162
|
const cacheTotal = usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
|
|
157
163
|
if (cacheTotal > 0) {
|
|
@@ -164,8 +170,37 @@ export function formatTaskTokenSummary(usage: UsageData, cost: CostBreakdown): s
|
|
|
164
170
|
}
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
parts.push(`Est. cost: ${formatCost(
|
|
168
|
-
return
|
|
173
|
+
parts.push(`Est. cost: ${formatCost(costValue)}`);
|
|
174
|
+
return `${indent}${parts.join(' | ')}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Formats a per-task token usage summary.
|
|
179
|
+
* For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42"
|
|
180
|
+
* For multi-attempt tasks: shows per-attempt breakdown plus total.
|
|
181
|
+
*
|
|
182
|
+
* @param entry - The TaskUsageEntry containing accumulated usage, cost, and attempts array
|
|
183
|
+
* @param calculateAttemptCost - Optional function to calculate cost for a single attempt's UsageData
|
|
184
|
+
*/
|
|
185
|
+
export function formatTaskTokenSummary(
|
|
186
|
+
entry: TaskUsageEntry,
|
|
187
|
+
calculateAttemptCost?: (usage: UsageData) => CostBreakdown
|
|
188
|
+
): string {
|
|
189
|
+
// Single-attempt: render exactly as before (no per-attempt breakdown)
|
|
190
|
+
if (entry.attempts.length <= 1) {
|
|
191
|
+
return formatTokenLine(entry.usage, entry.cost.totalCost);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Multi-attempt: show per-attempt lines plus total
|
|
195
|
+
const lines: string[] = [];
|
|
196
|
+
entry.attempts.forEach((attemptUsage, i) => {
|
|
197
|
+
const attemptCost = calculateAttemptCost
|
|
198
|
+
? calculateAttemptCost(attemptUsage).totalCost
|
|
199
|
+
: 0;
|
|
200
|
+
lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' '));
|
|
201
|
+
});
|
|
202
|
+
lines.push(formatTokenLine(entry.usage, entry.cost.totalCost, 'Total', ' '));
|
|
203
|
+
return lines.join('\n');
|
|
169
204
|
}
|
|
170
205
|
|
|
171
206
|
/**
|
|
@@ -13,8 +13,48 @@ export interface CostBreakdown {
|
|
|
13
13
|
/** Per-task usage snapshot stored by the tracker. */
|
|
14
14
|
export interface TaskUsageEntry {
|
|
15
15
|
taskId: string;
|
|
16
|
+
/** Accumulated usage across all attempts. */
|
|
16
17
|
usage: UsageData;
|
|
18
|
+
/** Cost breakdown for accumulated usage. */
|
|
17
19
|
cost: CostBreakdown;
|
|
20
|
+
/** Raw per-attempt usage data (for display breakdowns). */
|
|
21
|
+
attempts: UsageData[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Merge multiple UsageData objects into a single accumulated UsageData.
|
|
26
|
+
* Sums all token fields and merges modelUsage maps.
|
|
27
|
+
*/
|
|
28
|
+
export function accumulateUsage(attempts: UsageData[]): UsageData {
|
|
29
|
+
const result: UsageData = {
|
|
30
|
+
inputTokens: 0,
|
|
31
|
+
outputTokens: 0,
|
|
32
|
+
cacheReadInputTokens: 0,
|
|
33
|
+
cacheCreationInputTokens: 0,
|
|
34
|
+
modelUsage: {},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
for (const attempt of attempts) {
|
|
38
|
+
result.inputTokens += attempt.inputTokens;
|
|
39
|
+
result.outputTokens += attempt.outputTokens;
|
|
40
|
+
result.cacheReadInputTokens += attempt.cacheReadInputTokens;
|
|
41
|
+
result.cacheCreationInputTokens += attempt.cacheCreationInputTokens;
|
|
42
|
+
|
|
43
|
+
// Merge per-model usage
|
|
44
|
+
for (const [modelId, modelUsage] of Object.entries(attempt.modelUsage)) {
|
|
45
|
+
const existing = result.modelUsage[modelId];
|
|
46
|
+
if (existing) {
|
|
47
|
+
existing.inputTokens += modelUsage.inputTokens;
|
|
48
|
+
existing.outputTokens += modelUsage.outputTokens;
|
|
49
|
+
existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
|
|
50
|
+
existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
|
|
51
|
+
} else {
|
|
52
|
+
result.modelUsage[modelId] = { ...modelUsage };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
18
58
|
}
|
|
19
59
|
|
|
20
60
|
/**
|
|
@@ -31,10 +71,12 @@ export class TokenTracker {
|
|
|
31
71
|
|
|
32
72
|
/**
|
|
33
73
|
* Record usage data from a completed task.
|
|
74
|
+
* Accepts an array of UsageData (one per attempt) and accumulates them.
|
|
34
75
|
*/
|
|
35
|
-
addTask(taskId: string,
|
|
76
|
+
addTask(taskId: string, attempts: UsageData[]): TaskUsageEntry {
|
|
77
|
+
const usage = accumulateUsage(attempts);
|
|
36
78
|
const cost = this.calculateCost(usage);
|
|
37
|
-
const entry: TaskUsageEntry = { taskId, usage, cost };
|
|
79
|
+
const entry: TaskUsageEntry = { taskId, usage, cost, attempts };
|
|
38
80
|
this.entries.push(entry);
|
|
39
81
|
return entry;
|
|
40
82
|
}
|
|
@@ -3,17 +3,27 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { createConfigCommand } from '../../src/commands/config.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
validateConfig,
|
|
8
|
+
ConfigValidationError,
|
|
9
|
+
resolveConfig,
|
|
10
|
+
getModel,
|
|
11
|
+
getEffort,
|
|
12
|
+
resetConfigCache,
|
|
13
|
+
} from '../../src/utils/config.js';
|
|
14
|
+
import { DEFAULT_CONFIG } from '../../src/types/config.js';
|
|
7
15
|
|
|
8
16
|
describe('Config Command', () => {
|
|
9
17
|
let tempDir: string;
|
|
10
18
|
|
|
11
19
|
beforeEach(() => {
|
|
12
20
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-config-cmd-test-'));
|
|
21
|
+
resetConfigCache();
|
|
13
22
|
});
|
|
14
23
|
|
|
15
24
|
afterEach(() => {
|
|
16
25
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
26
|
+
resetConfigCache();
|
|
17
27
|
});
|
|
18
28
|
|
|
19
29
|
describe('Command setup', () => {
|
|
@@ -160,4 +170,73 @@ describe('Config Command', () => {
|
|
|
160
170
|
expect(content).toContain('"worktree": true');
|
|
161
171
|
});
|
|
162
172
|
});
|
|
173
|
+
|
|
174
|
+
describe('Error recovery - invalid config fallback', () => {
|
|
175
|
+
// These tests verify the behaviors that runConfigSession relies on for error recovery
|
|
176
|
+
// The config command catches errors from getModel/getEffort and falls back to defaults
|
|
177
|
+
|
|
178
|
+
it('should throw on invalid JSON when resolving config', () => {
|
|
179
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
180
|
+
fs.writeFileSync(configPath, '{ invalid json }}}');
|
|
181
|
+
|
|
182
|
+
expect(() => resolveConfig(configPath)).toThrow(SyntaxError);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should throw on schema validation failure when resolving config', () => {
|
|
186
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
187
|
+
fs.writeFileSync(configPath, JSON.stringify({ unknownKey: true }));
|
|
188
|
+
|
|
189
|
+
expect(() => resolveConfig(configPath)).toThrow(ConfigValidationError);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should have valid default fallback values for config scenario', () => {
|
|
193
|
+
// These are the values that runConfigSession uses when config loading fails
|
|
194
|
+
expect(DEFAULT_CONFIG.models.config).toBe('sonnet');
|
|
195
|
+
expect(DEFAULT_CONFIG.effort.config).toBe('medium');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should be able to read raw file contents even when config is invalid JSON', () => {
|
|
199
|
+
// This verifies that getCurrentConfigState can still read the broken file
|
|
200
|
+
// so Claude can see and help fix it
|
|
201
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
202
|
+
const invalidContent = '{ "broken": true, }'; // trailing comma = invalid
|
|
203
|
+
fs.writeFileSync(configPath, invalidContent);
|
|
204
|
+
|
|
205
|
+
// File is readable even though it's invalid JSON
|
|
206
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
207
|
+
expect(content).toBe(invalidContent);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should be able to read raw file contents even when config fails schema validation', () => {
|
|
211
|
+
const configPath = path.join(tempDir, 'raf.config.json');
|
|
212
|
+
const invalidContent = JSON.stringify({ badKey: 'value' }, null, 2);
|
|
213
|
+
fs.writeFileSync(configPath, invalidContent);
|
|
214
|
+
|
|
215
|
+
// File is readable even though it fails validation
|
|
216
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
217
|
+
expect(JSON.parse(content)).toEqual({ badKey: 'value' });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('resetConfigCache should clear the cached config', () => {
|
|
221
|
+
// This is used by runConfigSession to clear a broken cached config
|
|
222
|
+
// so subsequent operations don't fail
|
|
223
|
+
const configPath = path.join(tempDir, 'valid.json');
|
|
224
|
+
fs.writeFileSync(configPath, JSON.stringify({ timeout: 99 }));
|
|
225
|
+
|
|
226
|
+
// Load the config
|
|
227
|
+
const config1 = resolveConfig(configPath);
|
|
228
|
+
expect(config1.timeout).toBe(99);
|
|
229
|
+
|
|
230
|
+
// Write different content
|
|
231
|
+
fs.writeFileSync(configPath, JSON.stringify({ timeout: 120 }));
|
|
232
|
+
|
|
233
|
+
// Without reset, we'd still get cached value (but resolveConfig doesn't use cache)
|
|
234
|
+
// This test verifies resetConfigCache exists and can be called
|
|
235
|
+
resetConfigCache();
|
|
236
|
+
|
|
237
|
+
// After reset, we should get new value
|
|
238
|
+
const config2 = resolveConfig(configPath);
|
|
239
|
+
expect(config2.timeout).toBe(120);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
163
242
|
});
|