rafcode 2.3.0 → 2.4.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +3 -1
- package/CLAUDE.md +21 -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/RAF/ahwidh-quick-fix-gremlin/decisions.md +37 -0
- package/RAF/ahwidh-quick-fix-gremlin/input.md +35 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/01-fix-name-generation-prompt.md +33 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/02-fix-amend-commit-scope.md +43 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/03-fix-diverged-main-branch-sync.md +32 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/04-wire-rate-limit-to-do-command.md +61 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/05-add-config-get-set-flags.md +125 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/06-sync-worktree-branch-before-execution.md +96 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/07-update-frontmatter-format.md +107 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/08-remove-plan-token-report.md +76 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/01-fix-name-generation-prompt.md +52 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/02-fix-amend-commit-scope.md +48 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/03-fix-diverged-main-branch-sync.md +49 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/04-wire-rate-limit-to-do-command.md +78 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/05-add-config-get-set-flags.md +101 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/06-sync-worktree-branch-before-execution.md +92 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/07-update-frontmatter-format.md +105 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/08-remove-plan-token-report.md +50 -0
- package/README.md +27 -7
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +209 -6
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +140 -21
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +27 -5
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +0 -6
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +4 -9
- 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 +44 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +247 -0
- package/dist/core/worktree.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +28 -11
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +28 -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 +53 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +115 -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 +9 -19
- 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 +242 -7
- package/src/commands/do.ts +177 -23
- package/src/commands/plan.ts +27 -4
- package/src/core/claude-runner.ts +4 -16
- 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 +266 -0
- package/src/prompts/amend.ts +28 -11
- package/src/prompts/config-docs.md +91 -29
- package/src/prompts/planning.ts +28 -11
- package/src/types/config.ts +46 -21
- package/src/utils/config.ts +200 -33
- package/src/utils/frontmatter.ts +140 -0
- package/src/utils/name-generator.ts +9 -19
- 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/commit-planning-artifacts-worktree.test.ts +6 -14
- package/tests/unit/commit-planning-artifacts.test.ts +4 -12
- package/tests/unit/config-command.test.ts +176 -6
- package/tests/unit/config.test.ts +268 -45
- package/tests/unit/frontmatter.test.ts +276 -0
- package/tests/unit/name-generator.test.ts +1 -1
- package/tests/unit/post-execution-picker.test.ts +6 -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 +309 -0
|
@@ -7,6 +7,16 @@ import { formatElapsedTime } from './timer.js';
|
|
|
7
7
|
import type { UsageData } from '../types/config.js';
|
|
8
8
|
import type { CostBreakdown, TaskUsageEntry } from './token-tracker.js';
|
|
9
9
|
|
|
10
|
+
/** Options for token summary formatting. */
|
|
11
|
+
export interface TokenSummaryOptions {
|
|
12
|
+
/** Whether to show cache token counts. Default: true */
|
|
13
|
+
showCacheTokens?: boolean;
|
|
14
|
+
/** Whether to show rate limit percentage. Default: true */
|
|
15
|
+
showRateLimitEstimate?: boolean;
|
|
16
|
+
/** Rate limit percentage to display (requires showRateLimitEstimate: true) */
|
|
17
|
+
rateLimitPercentage?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* Visual symbols for terminal output using dots/symbols style.
|
|
12
22
|
*/
|
|
@@ -145,6 +155,17 @@ export function formatCost(cost: number): string {
|
|
|
145
155
|
return `$${cost.toFixed(2)}`;
|
|
146
156
|
}
|
|
147
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Formats a rate limit percentage for display.
|
|
160
|
+
* Uses tilde (~) prefix to indicate estimate.
|
|
161
|
+
*/
|
|
162
|
+
export function formatRateLimitPercentage(percentage: number): string {
|
|
163
|
+
if (percentage === 0) return '~0% of 5h window';
|
|
164
|
+
if (percentage < 0.1) return `~${percentage.toFixed(2)}% of 5h window`;
|
|
165
|
+
if (percentage < 1) return `~${percentage.toFixed(1)}% of 5h window`;
|
|
166
|
+
return `~${Math.round(percentage)}% of 5h window`;
|
|
167
|
+
}
|
|
168
|
+
|
|
148
169
|
/**
|
|
149
170
|
* Formats a single line of token usage (for a single attempt or total).
|
|
150
171
|
* Used internally by formatTaskTokenSummary.
|
|
@@ -153,67 +174,94 @@ function formatTokenLine(
|
|
|
153
174
|
usage: UsageData,
|
|
154
175
|
costValue: number,
|
|
155
176
|
prefix: string = '',
|
|
156
|
-
indent: string = ' '
|
|
177
|
+
indent: string = ' ',
|
|
178
|
+
options: TokenSummaryOptions = {}
|
|
157
179
|
): string {
|
|
180
|
+
const { showCacheTokens = true, showRateLimitEstimate = true, rateLimitPercentage } = options;
|
|
158
181
|
const parts: string[] = [];
|
|
159
182
|
const tokenPart = `${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`;
|
|
160
183
|
parts.push(prefix ? `${prefix}: ${tokenPart}` : `Tokens: ${tokenPart}`);
|
|
161
184
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
if (showCacheTokens) {
|
|
186
|
+
const cacheTotal = usage.cacheReadInputTokens + usage.cacheCreationInputTokens;
|
|
187
|
+
if (cacheTotal > 0) {
|
|
188
|
+
if (usage.cacheReadInputTokens > 0 && usage.cacheCreationInputTokens > 0) {
|
|
189
|
+
parts.push(`Cache: ${formatNumber(usage.cacheReadInputTokens)} read / ${formatNumber(usage.cacheCreationInputTokens)} created`);
|
|
190
|
+
} else if (usage.cacheReadInputTokens > 0) {
|
|
191
|
+
parts.push(`Cache: ${formatNumber(usage.cacheReadInputTokens)} read`);
|
|
192
|
+
} else {
|
|
193
|
+
parts.push(`Cache: ${formatNumber(usage.cacheCreationInputTokens)} created`);
|
|
194
|
+
}
|
|
170
195
|
}
|
|
171
196
|
}
|
|
172
197
|
|
|
173
198
|
parts.push(`Est. cost: ${formatCost(costValue)}`);
|
|
199
|
+
|
|
200
|
+
if (showRateLimitEstimate && rateLimitPercentage !== undefined) {
|
|
201
|
+
parts.push(formatRateLimitPercentage(rateLimitPercentage));
|
|
202
|
+
}
|
|
203
|
+
|
|
174
204
|
return `${indent}${parts.join(' | ')}`;
|
|
175
205
|
}
|
|
176
206
|
|
|
177
207
|
/**
|
|
178
208
|
* 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"
|
|
209
|
+
* For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42 | ~2% of 5h window"
|
|
180
210
|
* For multi-attempt tasks: shows per-attempt breakdown plus total.
|
|
181
211
|
*
|
|
182
212
|
* @param entry - The TaskUsageEntry containing accumulated usage, cost, and attempts array
|
|
183
213
|
* @param calculateAttemptCost - Optional function to calculate cost for a single attempt's UsageData
|
|
214
|
+
* @param options - Display options for showing cache tokens and rate limit percentage
|
|
184
215
|
*/
|
|
185
216
|
export function formatTaskTokenSummary(
|
|
186
217
|
entry: TaskUsageEntry,
|
|
187
|
-
calculateAttemptCost?: (usage: UsageData) => CostBreakdown
|
|
218
|
+
calculateAttemptCost?: (usage: UsageData) => CostBreakdown,
|
|
219
|
+
options: TokenSummaryOptions = {}
|
|
188
220
|
): string {
|
|
189
221
|
// Single-attempt: render exactly as before (no per-attempt breakdown)
|
|
190
222
|
if (entry.attempts.length <= 1) {
|
|
191
|
-
return formatTokenLine(entry.usage, entry.cost.totalCost);
|
|
223
|
+
return formatTokenLine(entry.usage, entry.cost.totalCost, '', ' ', options);
|
|
192
224
|
}
|
|
193
225
|
|
|
194
226
|
// Multi-attempt: show per-attempt lines plus total
|
|
227
|
+
// Per-attempt lines don't show rate limit (only show on total)
|
|
228
|
+
const perAttemptOptions: TokenSummaryOptions = {
|
|
229
|
+
...options,
|
|
230
|
+
showRateLimitEstimate: false,
|
|
231
|
+
rateLimitPercentage: undefined,
|
|
232
|
+
};
|
|
233
|
+
|
|
195
234
|
const lines: string[] = [];
|
|
196
235
|
entry.attempts.forEach((attemptUsage, i) => {
|
|
197
236
|
const attemptCost = calculateAttemptCost
|
|
198
237
|
? calculateAttemptCost(attemptUsage).totalCost
|
|
199
238
|
: 0;
|
|
200
|
-
lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' '));
|
|
239
|
+
lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' ', perAttemptOptions));
|
|
201
240
|
});
|
|
202
|
-
lines.push(formatTokenLine(entry.usage, entry.cost.totalCost, 'Total', ' '));
|
|
241
|
+
lines.push(formatTokenLine(entry.usage, entry.cost.totalCost, 'Total', ' ', options));
|
|
203
242
|
return lines.join('\n');
|
|
204
243
|
}
|
|
205
244
|
|
|
206
245
|
/**
|
|
207
246
|
* Formats the grand total token usage summary block.
|
|
208
247
|
* Displayed after all tasks complete.
|
|
248
|
+
*
|
|
249
|
+
* @param usage - Total usage data
|
|
250
|
+
* @param cost - Total cost breakdown
|
|
251
|
+
* @param options - Display options for cache tokens and rate limit
|
|
209
252
|
*/
|
|
210
|
-
export function formatTokenTotalSummary(
|
|
253
|
+
export function formatTokenTotalSummary(
|
|
254
|
+
usage: UsageData,
|
|
255
|
+
cost: CostBreakdown,
|
|
256
|
+
options: TokenSummaryOptions = {}
|
|
257
|
+
): string {
|
|
258
|
+
const { showCacheTokens = true, showRateLimitEstimate = true, rateLimitPercentage } = options;
|
|
211
259
|
const lines: string[] = [];
|
|
212
260
|
const divider = '── Token Usage Summary ──────────────────';
|
|
213
261
|
lines.push(divider);
|
|
214
262
|
lines.push(`Total tokens: ${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`);
|
|
215
263
|
|
|
216
|
-
if (usage.cacheReadInputTokens > 0 || usage.cacheCreationInputTokens > 0) {
|
|
264
|
+
if (showCacheTokens && (usage.cacheReadInputTokens > 0 || usage.cacheCreationInputTokens > 0)) {
|
|
217
265
|
const cacheParts: string[] = [];
|
|
218
266
|
if (usage.cacheReadInputTokens > 0) {
|
|
219
267
|
cacheParts.push(`${formatNumber(usage.cacheReadInputTokens)} read`);
|
|
@@ -225,6 +273,10 @@ export function formatTokenTotalSummary(usage: UsageData, cost: CostBreakdown):
|
|
|
225
273
|
}
|
|
226
274
|
|
|
227
275
|
lines.push(`Estimated cost: ${formatCost(cost.totalCost)}`);
|
|
276
|
+
|
|
277
|
+
if (showRateLimitEstimate && rateLimitPercentage !== undefined) {
|
|
278
|
+
lines.push(formatRateLimitPercentage(rateLimitPercentage));
|
|
279
|
+
}
|
|
228
280
|
lines.push('─────────────────────────────────────────');
|
|
229
281
|
return lines.join('\n');
|
|
230
282
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UsageData, PricingConfig } from '../types/config.js';
|
|
2
|
-
import { resolveModelPricingCategory, getPricingConfig } from './config.js';
|
|
2
|
+
import { resolveModelPricingCategory, getPricingConfig, getRateLimitWindowConfig } from './config.js';
|
|
3
3
|
|
|
4
4
|
/** Cost breakdown for a single task or accumulated total. */
|
|
5
5
|
export interface CostBreakdown {
|
|
@@ -21,6 +21,29 @@ export interface TaskUsageEntry {
|
|
|
21
21
|
attempts: UsageData[];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Sum multiple CostBreakdown objects into a single total.
|
|
26
|
+
*/
|
|
27
|
+
export function sumCostBreakdowns(costs: CostBreakdown[]): CostBreakdown {
|
|
28
|
+
const result: CostBreakdown = {
|
|
29
|
+
inputCost: 0,
|
|
30
|
+
outputCost: 0,
|
|
31
|
+
cacheReadCost: 0,
|
|
32
|
+
cacheCreateCost: 0,
|
|
33
|
+
totalCost: 0,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (const cost of costs) {
|
|
37
|
+
result.inputCost += cost.inputCost;
|
|
38
|
+
result.outputCost += cost.outputCost;
|
|
39
|
+
result.cacheReadCost += cost.cacheReadCost;
|
|
40
|
+
result.cacheCreateCost += cost.cacheCreateCost;
|
|
41
|
+
result.totalCost += cost.totalCost;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
/**
|
|
25
48
|
* Merge multiple UsageData objects into a single accumulated UsageData.
|
|
26
49
|
* Sums all token fields and merges modelUsage maps.
|
|
@@ -72,10 +95,15 @@ export class TokenTracker {
|
|
|
72
95
|
/**
|
|
73
96
|
* Record usage data from a completed task.
|
|
74
97
|
* Accepts an array of UsageData (one per attempt) and accumulates them.
|
|
98
|
+
* Cost is calculated per-attempt to avoid underreporting when some attempts
|
|
99
|
+
* have modelUsage and others only have aggregate fields.
|
|
75
100
|
*/
|
|
76
101
|
addTask(taskId: string, attempts: UsageData[]): TaskUsageEntry {
|
|
77
102
|
const usage = accumulateUsage(attempts);
|
|
78
|
-
|
|
103
|
+
// Calculate cost per-attempt, then sum. This ensures attempts with only
|
|
104
|
+
// aggregate fields use sonnet fallback pricing independently.
|
|
105
|
+
const perAttemptCosts = attempts.map((attempt) => this.calculateCost(attempt));
|
|
106
|
+
const cost = sumCostBreakdowns(perAttemptCosts);
|
|
79
107
|
const entry: TaskUsageEntry = { taskId, usage, cost, attempts };
|
|
80
108
|
this.entries.push(entry);
|
|
81
109
|
return entry;
|
|
@@ -174,4 +202,39 @@ export class TokenTracker {
|
|
|
174
202
|
result.totalCost = result.inputCost + result.outputCost + result.cacheReadCost + result.cacheCreateCost;
|
|
175
203
|
return result;
|
|
176
204
|
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Calculate the 5h rate limit window percentage for a given cost.
|
|
208
|
+
* Converts cost to Sonnet-equivalent tokens using the configured Sonnet pricing,
|
|
209
|
+
* then divides by the configured cap.
|
|
210
|
+
*
|
|
211
|
+
* @param totalCost - The total cost in dollars
|
|
212
|
+
* @param sonnetTokenCap - Optional override for the Sonnet-equivalent token cap (defaults to config value)
|
|
213
|
+
* @returns The percentage of the 5h window consumed (0-100+)
|
|
214
|
+
*/
|
|
215
|
+
calculateRateLimitPercentage(totalCost: number, sonnetTokenCap?: number): number {
|
|
216
|
+
if (totalCost === 0) return 0;
|
|
217
|
+
|
|
218
|
+
// Get the configured cap or use the provided override
|
|
219
|
+
const cap = sonnetTokenCap ?? getRateLimitWindowConfig().sonnetTokenCap;
|
|
220
|
+
|
|
221
|
+
// Calculate the average Sonnet cost per token
|
|
222
|
+
// Using the average of input and output pricing (simplified approach)
|
|
223
|
+
const sonnetPricing = this.pricingConfig.sonnet;
|
|
224
|
+
const avgSonnetCostPerToken = (sonnetPricing.inputPerMTok + sonnetPricing.outputPerMTok) / 2 / 1_000_000;
|
|
225
|
+
|
|
226
|
+
// Convert cost to Sonnet-equivalent tokens
|
|
227
|
+
const sonnetEquivalentTokens = totalCost / avgSonnetCostPerToken;
|
|
228
|
+
|
|
229
|
+
// Calculate percentage
|
|
230
|
+
return (sonnetEquivalentTokens / cap) * 100;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the cumulative 5h window percentage across all recorded tasks.
|
|
235
|
+
*/
|
|
236
|
+
getCumulativeRateLimitPercentage(sonnetTokenCap?: number): number {
|
|
237
|
+
const totals = this.getTotals();
|
|
238
|
+
return this.calculateRateLimitPercentage(totals.cost.totalCost, sonnetTokenCap);
|
|
239
|
+
}
|
|
177
240
|
}
|
|
@@ -144,7 +144,9 @@ describe('ClaudeRunner - runInteractive', () => {
|
|
|
144
144
|
|
|
145
145
|
const spawnArgs = mockPtySpawn.mock.calls[0][1] as string[];
|
|
146
146
|
expect(spawnArgs).toContain('--model');
|
|
147
|
-
|
|
147
|
+
// Default model comes from config, could be short alias or full model ID
|
|
148
|
+
const modelArgIndex = spawnArgs.indexOf('--model');
|
|
149
|
+
expect(spawnArgs[modelArgIndex + 1]).toMatch(/^(opus|sonnet|haiku|claude-(opus|sonnet|haiku)-.+)$/);
|
|
148
150
|
|
|
149
151
|
mockProc._exitCallback({ exitCode: 0 });
|
|
150
152
|
await runPromise;
|
|
@@ -244,8 +246,8 @@ describe('ClaudeRunner - runInteractive', () => {
|
|
|
244
246
|
});
|
|
245
247
|
});
|
|
246
248
|
|
|
247
|
-
describe('
|
|
248
|
-
it('should
|
|
249
|
+
describe('environment passing', () => {
|
|
250
|
+
it('should pass process.env to pty spawn in runInteractive', async () => {
|
|
249
251
|
const mockProc = createMockPtyProcess();
|
|
250
252
|
const mockStdin = createMockStdin();
|
|
251
253
|
const mockStdout = createMockStdout();
|
|
@@ -256,12 +258,12 @@ describe('ClaudeRunner - runInteractive', () => {
|
|
|
256
258
|
mockPtySpawn.mockReturnValue(mockProc);
|
|
257
259
|
|
|
258
260
|
const runner = new ClaudeRunner();
|
|
259
|
-
// Even if effortLevel were somehow passed, interactive mode should use process.env as-is
|
|
260
261
|
const runPromise = runner.runInteractive('system', 'user');
|
|
261
262
|
|
|
262
263
|
const spawnOptions = mockPtySpawn.mock.calls[0][2];
|
|
263
|
-
// Interactive mode passes process.env directly
|
|
264
|
-
|
|
264
|
+
// Interactive mode passes process.env directly
|
|
265
|
+
// Note: effortLevel option was removed from ClaudeRunner in favor of per-task model resolution
|
|
266
|
+
expect(spawnOptions.env).toBeDefined();
|
|
265
267
|
|
|
266
268
|
mockProc._exitCallback({ exitCode: 0 });
|
|
267
269
|
await runPromise;
|
|
@@ -473,7 +473,9 @@ describe('ClaudeRunner', () => {
|
|
|
473
473
|
});
|
|
474
474
|
});
|
|
475
475
|
|
|
476
|
-
|
|
476
|
+
// Note: effortLevel option was removed from ClaudeRunner in favor of per-task model resolution
|
|
477
|
+
// via plan frontmatter. See effortMapping config and frontmatter.ts for the new approach.
|
|
478
|
+
describe('environment handling', () => {
|
|
477
479
|
function createMockProcess() {
|
|
478
480
|
const stdout = new EventEmitter();
|
|
479
481
|
const stderr = new EventEmitter();
|
|
@@ -484,35 +486,7 @@ describe('ClaudeRunner', () => {
|
|
|
484
486
|
return proc;
|
|
485
487
|
}
|
|
486
488
|
|
|
487
|
-
it('should
|
|
488
|
-
const mockProc = createMockProcess();
|
|
489
|
-
mockSpawn.mockReturnValue(mockProc);
|
|
490
|
-
|
|
491
|
-
const runner = new ClaudeRunner();
|
|
492
|
-
const runPromise = runner.run('test prompt', { timeout: 60, effortLevel: 'medium' });
|
|
493
|
-
|
|
494
|
-
mockProc.emit('close', 0);
|
|
495
|
-
await runPromise;
|
|
496
|
-
|
|
497
|
-
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
498
|
-
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe('medium');
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('should set CLAUDE_CODE_EFFORT_LEVEL env var in runVerbose() when effortLevel is provided', async () => {
|
|
502
|
-
const mockProc = createMockProcess();
|
|
503
|
-
mockSpawn.mockReturnValue(mockProc);
|
|
504
|
-
|
|
505
|
-
const runner = new ClaudeRunner();
|
|
506
|
-
const runPromise = runner.runVerbose('test prompt', { timeout: 60, effortLevel: 'medium' });
|
|
507
|
-
|
|
508
|
-
mockProc.emit('close', 0);
|
|
509
|
-
await runPromise;
|
|
510
|
-
|
|
511
|
-
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
512
|
-
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe('medium');
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it('should NOT set CLAUDE_CODE_EFFORT_LEVEL when effortLevel is not provided in run()', async () => {
|
|
489
|
+
it('should pass process.env to child process in run()', async () => {
|
|
516
490
|
const mockProc = createMockProcess();
|
|
517
491
|
mockSpawn.mockReturnValue(mockProc);
|
|
518
492
|
|
|
@@ -523,11 +497,10 @@ describe('ClaudeRunner', () => {
|
|
|
523
497
|
await runPromise;
|
|
524
498
|
|
|
525
499
|
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
526
|
-
// env should be process.env directly (no CLAUDE_CODE_EFFORT_LEVEL override)
|
|
527
500
|
expect(spawnOptions.env).toBe(process.env);
|
|
528
501
|
});
|
|
529
502
|
|
|
530
|
-
it('should
|
|
503
|
+
it('should pass process.env to child process in runVerbose()', async () => {
|
|
531
504
|
const mockProc = createMockProcess();
|
|
532
505
|
mockSpawn.mockReturnValue(mockProc);
|
|
533
506
|
|
|
@@ -538,42 +511,8 @@ describe('ClaudeRunner', () => {
|
|
|
538
511
|
await runPromise;
|
|
539
512
|
|
|
540
513
|
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
541
|
-
// env should be process.env directly (no CLAUDE_CODE_EFFORT_LEVEL override)
|
|
542
514
|
expect(spawnOptions.env).toBe(process.env);
|
|
543
515
|
});
|
|
544
|
-
|
|
545
|
-
it('should support different effort levels', async () => {
|
|
546
|
-
for (const level of ['low', 'medium', 'high'] as const) {
|
|
547
|
-
const mockProc = createMockProcess();
|
|
548
|
-
mockSpawn.mockReturnValue(mockProc);
|
|
549
|
-
|
|
550
|
-
const runner = new ClaudeRunner();
|
|
551
|
-
const runPromise = runner.run('test prompt', { timeout: 60, effortLevel: level });
|
|
552
|
-
|
|
553
|
-
mockProc.emit('close', 0);
|
|
554
|
-
await runPromise;
|
|
555
|
-
|
|
556
|
-
const spawnOptions = mockSpawn.mock.calls[mockSpawn.mock.calls.length - 1][2];
|
|
557
|
-
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe(level);
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it('should preserve other env vars when effortLevel is set', async () => {
|
|
562
|
-
const mockProc = createMockProcess();
|
|
563
|
-
mockSpawn.mockReturnValue(mockProc);
|
|
564
|
-
|
|
565
|
-
const runner = new ClaudeRunner();
|
|
566
|
-
const runPromise = runner.run('test prompt', { timeout: 60, effortLevel: 'medium' });
|
|
567
|
-
|
|
568
|
-
mockProc.emit('close', 0);
|
|
569
|
-
await runPromise;
|
|
570
|
-
|
|
571
|
-
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
572
|
-
// Should have PATH from process.env
|
|
573
|
-
expect(spawnOptions.env.PATH).toBe(process.env.PATH);
|
|
574
|
-
// And the injected effort level
|
|
575
|
-
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe('medium');
|
|
576
|
-
});
|
|
577
516
|
});
|
|
578
517
|
|
|
579
518
|
describe('system prompt append flag', () => {
|
|
@@ -162,13 +162,9 @@ describe('commitPlanningArtifacts - worktree integration', () => {
|
|
|
162
162
|
'# Task: New Task'
|
|
163
163
|
);
|
|
164
164
|
|
|
165
|
-
// Call commitPlanningArtifacts
|
|
166
|
-
const additionalFiles = [
|
|
167
|
-
path.join(wtProjectPath, 'plans', '02-new-task.md'),
|
|
168
|
-
];
|
|
165
|
+
// Call commitPlanningArtifacts (plan files not included in amend commit)
|
|
169
166
|
await commitPlanningArtifacts(wtProjectPath, {
|
|
170
167
|
cwd: worktreePath,
|
|
171
|
-
additionalFiles,
|
|
172
168
|
isAmend: true,
|
|
173
169
|
});
|
|
174
170
|
|
|
@@ -176,11 +172,11 @@ describe('commitPlanningArtifacts - worktree integration', () => {
|
|
|
176
172
|
const lastMsg = getLastCommitMessage(worktreePath);
|
|
177
173
|
expect(lastMsg).toMatch(/RAF\[aatest\] Amend: my-project/);
|
|
178
174
|
|
|
179
|
-
// Verify
|
|
175
|
+
// Verify only input.md and decisions.md are in the commit (not plan files)
|
|
180
176
|
const committedFiles = getLastCommitFiles(worktreePath);
|
|
181
177
|
expect(committedFiles).toContain(`RAF/${projectFolder}/input.md`);
|
|
182
178
|
expect(committedFiles).toContain(`RAF/${projectFolder}/decisions.md`);
|
|
183
|
-
expect(committedFiles).toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
179
|
+
expect(committedFiles).not.toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
184
180
|
});
|
|
185
181
|
|
|
186
182
|
it('should commit after worktree recreation from branch', async () => {
|
|
@@ -234,13 +230,9 @@ describe('commitPlanningArtifacts - worktree integration', () => {
|
|
|
234
230
|
'# Task: New Task'
|
|
235
231
|
);
|
|
236
232
|
|
|
237
|
-
// Call commitPlanningArtifacts
|
|
238
|
-
const additionalFiles = [
|
|
239
|
-
path.join(recreatedProjectPath, 'plans', '02-new-task.md'),
|
|
240
|
-
];
|
|
233
|
+
// Call commitPlanningArtifacts (plan files not included in amend commit)
|
|
241
234
|
await commitPlanningArtifacts(recreatedProjectPath, {
|
|
242
235
|
cwd: recreatedWtPath,
|
|
243
|
-
additionalFiles,
|
|
244
236
|
isAmend: true,
|
|
245
237
|
});
|
|
246
238
|
|
|
@@ -248,11 +240,11 @@ describe('commitPlanningArtifacts - worktree integration', () => {
|
|
|
248
240
|
const lastMsg = getLastCommitMessage(recreatedWtPath);
|
|
249
241
|
expect(lastMsg).toMatch(/RAF\[aatest\] Amend: my-project/);
|
|
250
242
|
|
|
251
|
-
// Verify
|
|
243
|
+
// Verify only input.md and decisions.md are in the commit (not plan files)
|
|
252
244
|
const committedFiles = getLastCommitFiles(recreatedWtPath);
|
|
253
245
|
expect(committedFiles).toContain(`RAF/${projectFolder}/input.md`);
|
|
254
246
|
expect(committedFiles).toContain(`RAF/${projectFolder}/decisions.md`);
|
|
255
|
-
expect(committedFiles).toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
247
|
+
expect(committedFiles).not.toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
256
248
|
});
|
|
257
249
|
|
|
258
250
|
it('should work when only some files have changed', async () => {
|
|
@@ -252,7 +252,7 @@ describe('commitPlanningArtifacts', () => {
|
|
|
252
252
|
);
|
|
253
253
|
});
|
|
254
254
|
|
|
255
|
-
it('should stage
|
|
255
|
+
it('should not stage plan files in amend mode', async () => {
|
|
256
256
|
mockExecSync.mockImplementation((cmd: unknown) => {
|
|
257
257
|
const cmdStr = cmd as string;
|
|
258
258
|
if (cmdStr.includes('rev-parse')) {
|
|
@@ -262,32 +262,24 @@ describe('commitPlanningArtifacts', () => {
|
|
|
262
262
|
return '';
|
|
263
263
|
}
|
|
264
264
|
if (cmdStr.includes('git diff --cached')) {
|
|
265
|
-
return 'RAF/aaaaar-decision-vault/input.md\
|
|
265
|
+
return 'RAF/aaaaar-decision-vault/input.md\n';
|
|
266
266
|
}
|
|
267
267
|
return '';
|
|
268
268
|
});
|
|
269
269
|
|
|
270
|
-
const additionalFiles = [
|
|
271
|
-
'/Users/test/RAF/aaaaar-decision-vault/plans/04-new-task.md',
|
|
272
|
-
'/Users/test/RAF/aaaaar-decision-vault/plans/05-another-task.md',
|
|
273
|
-
];
|
|
274
|
-
|
|
275
270
|
await commitPlanningArtifacts('/Users/test/RAF/aaaaar-decision-vault', {
|
|
276
|
-
additionalFiles,
|
|
277
271
|
isAmend: true,
|
|
278
272
|
});
|
|
279
273
|
|
|
280
|
-
// Verify git add called for
|
|
274
|
+
// Verify git add called for only 2 files (input, decisions)
|
|
281
275
|
const addCalls = mockExecSync.mock.calls.filter(
|
|
282
276
|
(call) => (call[0] as string).includes('git add')
|
|
283
277
|
);
|
|
284
|
-
expect(addCalls.length).toBe(
|
|
278
|
+
expect(addCalls.length).toBe(2);
|
|
285
279
|
|
|
286
280
|
const addCmds = addCalls.map((c) => c[0] as string);
|
|
287
281
|
expect(addCmds.some((cmd) => cmd.includes('input.md'))).toBe(true);
|
|
288
282
|
expect(addCmds.some((cmd) => cmd.includes('decisions.md'))).toBe(true);
|
|
289
|
-
expect(addCmds.some((cmd) => cmd.includes('04-new-task.md'))).toBe(true);
|
|
290
|
-
expect(addCmds.some((cmd) => cmd.includes('05-another-task.md'))).toBe(true);
|
|
291
283
|
});
|
|
292
284
|
|
|
293
285
|
it('should pass cwd to isGitRepo for worktree support', async () => {
|