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.
Files changed (49) hide show
  1. package/RAF/ahtahs-token-reaper/decisions.md +37 -0
  2. package/RAF/ahtahs-token-reaper/input.md +20 -0
  3. package/RAF/ahtahs-token-reaper/outcomes/01-extend-token-tracker-data-model.md +42 -0
  4. package/RAF/ahtahs-token-reaper/outcomes/02-accumulate-usage-in-retry-loop.md +31 -0
  5. package/RAF/ahtahs-token-reaper/outcomes/03-per-attempt-display-formatting.md +60 -0
  6. package/RAF/ahtahs-token-reaper/outcomes/04-add-model-name-to-claude-call-logs.md +57 -0
  7. package/RAF/ahtahs-token-reaper/outcomes/05-handle-invalid-config-in-raf-config.md +46 -0
  8. package/RAF/ahtahs-token-reaper/outcomes/06-fix-verbose-toggle-timer-display.md +38 -0
  9. package/RAF/ahtahs-token-reaper/plans/01-extend-token-tracker-data-model.md +36 -0
  10. package/RAF/ahtahs-token-reaper/plans/02-accumulate-usage-in-retry-loop.md +36 -0
  11. package/RAF/ahtahs-token-reaper/plans/03-per-attempt-display-formatting.md +43 -0
  12. package/RAF/ahtahs-token-reaper/plans/04-add-model-name-to-claude-call-logs.md +38 -0
  13. package/RAF/ahtahs-token-reaper/plans/05-handle-invalid-config-in-raf-config.md +36 -0
  14. package/RAF/ahtahs-token-reaper/plans/06-fix-verbose-toggle-timer-display.md +40 -0
  15. package/dist/commands/config.d.ts.map +1 -1
  16. package/dist/commands/config.js +27 -5
  17. package/dist/commands/config.js.map +1 -1
  18. package/dist/commands/do.js +17 -10
  19. package/dist/commands/do.js.map +1 -1
  20. package/dist/commands/plan.js +3 -2
  21. package/dist/commands/plan.js.map +1 -1
  22. package/dist/core/pull-request.d.ts.map +1 -1
  23. package/dist/core/pull-request.js +3 -1
  24. package/dist/core/pull-request.js.map +1 -1
  25. package/dist/utils/config.d.ts +6 -0
  26. package/dist/utils/config.d.ts.map +1 -1
  27. package/dist/utils/config.js +21 -0
  28. package/dist/utils/config.js.map +1 -1
  29. package/dist/utils/terminal-symbols.d.ts +8 -4
  30. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  31. package/dist/utils/terminal-symbols.js +31 -6
  32. package/dist/utils/terminal-symbols.js.map +1 -1
  33. package/dist/utils/token-tracker.d.ts +11 -1
  34. package/dist/utils/token-tracker.d.ts.map +1 -1
  35. package/dist/utils/token-tracker.js +37 -2
  36. package/dist/utils/token-tracker.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/commands/config.ts +30 -4
  39. package/src/commands/do.ts +17 -10
  40. package/src/commands/plan.ts +3 -2
  41. package/src/core/pull-request.ts +3 -1
  42. package/src/utils/config.ts +22 -0
  43. package/src/utils/terminal-symbols.ts +42 -7
  44. package/src/utils/token-tracker.ts +44 -2
  45. package/tests/unit/config-command.test.ts +80 -1
  46. package/tests/unit/config.test.ts +24 -0
  47. package/tests/unit/terminal-symbols.test.ts +121 -33
  48. package/tests/unit/timer-verbose-integration.test.ts +170 -0
  49. 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;AAExD;;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;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAiBpF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAoBrF"}
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 per-task token usage summary line.
118
- * Example: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42"
117
+ * Formats a single line of token usage (for a single attempt or total).
118
+ * Used internally by formatTaskTokenSummary.
119
119
  */
120
- export function formatTaskTokenSummary(usage, cost) {
120
+ function formatTokenLine(usage, costValue, prefix = '', indent = ' ') {
121
121
  const parts = [];
122
- parts.push(`Tokens: ${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`);
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(cost.totalCost)}`);
136
- return ` ${parts.join(' | ')}`;
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, usage: UsageData): TaskUsageEntry;
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;CACrB;AAED;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,aAAa,CAAC,EAAE,aAAa;IAIzC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc;IAOzD;;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
+ {"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, usage) {
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;AAkB5E;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,OAAO,GAAqB,EAAE,CAAC;IAC/B,aAAa,CAAgB;IAErC,YAAY,aAA6B;QACvC,IAAI,CAAC,aAAa,GAAG,aAAa,IAAI,gBAAgB,EAAE,CAAC;IAC3D,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,MAAc,EAAE,KAAgB;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,KAAK,GAAmB,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACtD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,SAAS;QACP,MAAM,UAAU,GAAc;YAC5B,WAAW,EAAE,CAAC;YACd,YAAY,EAAE,CAAC;YACf,oBAAoB,EAAE,CAAC;YACvB,wBAAwB,EAAE,CAAC;YAC3B,UAAU,EAAE,EAAE;SACf,CAAC;QACF,MAAM,SAAS,GAAkB;YAC/B,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC;YACb,aAAa,EAAE,CAAC;YAChB,eAAe,EAAE,CAAC;YAClB,SAAS,EAAE,CAAC;SACb,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,UAAU,CAAC,WAAW,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC;YAClD,UAAU,CAAC,YAAY,IAAI,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC;YACpD,UAAU,CAAC,oBAAoB,IAAI,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAAC;YACpE,UAAU,CAAC,wBAAwB,IAAI,KAAK,CAAC,KAAK,CAAC,wBAAwB,CAAC;YAE5E,wBAAwB;YACxB,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3E,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBAChD,IAAI,QAAQ,EAAE,CAAC;oBACb,QAAQ,CAAC,WAAW,IAAI,UAAU,CAAC,WAAW,CAAC;oBAC/C,QAAQ,CAAC,YAAY,IAAI,UAAU,CAAC,YAAY,CAAC;oBACjD,QAAQ,CAAC,oBAAoB,IAAI,UAAU,CAAC,oBAAoB,CAAC;oBACjE,QAAQ,CAAC,wBAAwB,IAAI,UAAU,CAAC,wBAAwB,CAAC;gBAC3E,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,UAAU,EAAE,CAAC;gBACrD,CAAC;YACH,CAAC;YAED,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;YAC5C,SAAS,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;YAC9C,SAAS,CAAC,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC;YACpD,SAAS,CAAC,eAAe,IAAI,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;YACxD,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;QAC9C,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAChD,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,KAAgB;QAC5B,MAAM,MAAM,GAAkB;YAC5B,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC;YACb,aAAa,EAAE,CAAC;YAChB,eAAe,EAAE,CAAC;YAClB,SAAS,EAAE,CAAC;SACb,CAAC;QAEF,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAEtD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,+CAA+C;YAC/C,KAAK,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,IAAI,YAAY,EAAE,CAAC;gBACjD,MAAM,QAAQ,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;gBACtD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;gBAEzD,MAAM,CAAC,SAAS,IAAI,CAAC,UAAU,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;gBAChF,MAAM,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;gBACnF,MAAM,CAAC,aAAa,IAAI,CAAC,UAAU,CAAC,oBAAoB,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;gBACjG,MAAM,CAAC,eAAe,IAAI,CAAC,UAAU,CAAC,wBAAwB,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;YAC3G,CAAC;QACH,CAAC;aAAM,CAAC;YACN,qDAAqD;YACrD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;YAC1C,MAAM,CAAC,SAAS,GAAG,CAAC,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;YAC1E,MAAM,CAAC,UAAU,GAAG,CAAC,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;YAC7E,MAAM,CAAC,aAAa,GAAG,CAAC,KAAK,CAAC,oBAAoB,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAC3F,MAAM,CAAC,eAAe,GAAG,CAAC,KAAK,CAAC,wBAAwB,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;QACrG,CAAC;QAED,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,eAAe,CAAC;QACxG,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
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.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Automated Task Planning & Execution with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- const model = getModel('config');
157
- const effort = getEffort('config');
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
- logger.info('Starting config session with Claude...');
185
- logger.info(`Using model: ${model}`);
210
+ const configModel = getModelShortName(model);
211
+ logger.info(`Starting config session with ${configModel}...`);
186
212
  logger.newline();
187
213
 
188
214
  try {
@@ -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
- let lastUsageData: import('../types/config.js').UsageData | undefined;
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
- lastUsageData = result.usageData;
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 (lastUsageData) {
1092
- const entry = tokenTracker.addTask(task.id, lastUsageData);
1093
- logger.dim(formatTaskTokenSummary(entry.usage, entry.cost));
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
- logger.info(' Analyzing failure...');
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 (lastUsageData) {
1119
- const entry = tokenTracker.addTask(task.id, lastUsageData);
1120
- logger.dim(formatTaskTokenSummary(entry.usage, entry.cost));
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
@@ -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
- logger.info('Generating project name suggestions...');
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
 
@@ -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) {
@@ -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 per-task token usage summary line.
150
- * Example: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42"
149
+ * Formats a single line of token usage (for a single attempt or total).
150
+ * Used internally by formatTaskTokenSummary.
151
151
  */
152
- export function formatTaskTokenSummary(usage: UsageData, cost: CostBreakdown): string {
152
+ function formatTokenLine(
153
+ usage: UsageData,
154
+ costValue: number,
155
+ prefix: string = '',
156
+ indent: string = ' '
157
+ ): string {
153
158
  const parts: string[] = [];
154
- parts.push(`Tokens: ${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`);
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(cost.totalCost)}`);
168
- return ` ${parts.join(' | ')}`;
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, usage: UsageData): TaskUsageEntry {
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 { validateConfig, ConfigValidationError } from '../../src/utils/config.js';
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
  });