rafcode 2.4.0 → 2.5.0-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 (108) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CLAUDE.md +7 -5
  3. package/RAF/ahwidh-quick-fix-gremlin/decisions.md +37 -0
  4. package/RAF/ahwidh-quick-fix-gremlin/input.md +35 -0
  5. package/RAF/ahwidh-quick-fix-gremlin/outcomes/01-fix-name-generation-prompt.md +33 -0
  6. package/RAF/ahwidh-quick-fix-gremlin/outcomes/02-fix-amend-commit-scope.md +43 -0
  7. package/RAF/ahwidh-quick-fix-gremlin/outcomes/03-fix-diverged-main-branch-sync.md +32 -0
  8. package/RAF/ahwidh-quick-fix-gremlin/outcomes/04-wire-rate-limit-to-do-command.md +61 -0
  9. package/RAF/ahwidh-quick-fix-gremlin/outcomes/05-add-config-get-set-flags.md +125 -0
  10. package/RAF/ahwidh-quick-fix-gremlin/outcomes/06-sync-worktree-branch-before-execution.md +96 -0
  11. package/RAF/ahwidh-quick-fix-gremlin/outcomes/07-update-frontmatter-format.md +107 -0
  12. package/RAF/ahwidh-quick-fix-gremlin/outcomes/08-remove-plan-token-report.md +76 -0
  13. package/RAF/ahwidh-quick-fix-gremlin/plans/01-fix-name-generation-prompt.md +52 -0
  14. package/RAF/ahwidh-quick-fix-gremlin/plans/02-fix-amend-commit-scope.md +48 -0
  15. package/RAF/ahwidh-quick-fix-gremlin/plans/03-fix-diverged-main-branch-sync.md +49 -0
  16. package/RAF/ahwidh-quick-fix-gremlin/plans/04-wire-rate-limit-to-do-command.md +78 -0
  17. package/RAF/ahwidh-quick-fix-gremlin/plans/05-add-config-get-set-flags.md +101 -0
  18. package/RAF/ahwidh-quick-fix-gremlin/plans/06-sync-worktree-branch-before-execution.md +92 -0
  19. package/RAF/ahwidh-quick-fix-gremlin/plans/07-update-frontmatter-format.md +105 -0
  20. package/RAF/ahwidh-quick-fix-gremlin/plans/08-remove-plan-token-report.md +50 -0
  21. package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
  22. package/RAF/ahwqwq-model-whisperer/input.md +5 -0
  23. package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
  24. package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
  25. package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
  26. package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
  27. package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
  28. package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
  29. package/dist/commands/config.d.ts.map +1 -1
  30. package/dist/commands/config.js +209 -1
  31. package/dist/commands/config.js.map +1 -1
  32. package/dist/commands/do.d.ts.map +1 -1
  33. package/dist/commands/do.js +37 -8
  34. package/dist/commands/do.js.map +1 -1
  35. package/dist/commands/plan.d.ts.map +1 -1
  36. package/dist/commands/plan.js +92 -54
  37. package/dist/commands/plan.js.map +1 -1
  38. package/dist/core/claude-runner.d.ts +8 -6
  39. package/dist/core/claude-runner.d.ts.map +1 -1
  40. package/dist/core/claude-runner.js +73 -5
  41. package/dist/core/claude-runner.js.map +1 -1
  42. package/dist/core/worktree.d.ts +12 -0
  43. package/dist/core/worktree.d.ts.map +1 -1
  44. package/dist/core/worktree.js +33 -1
  45. package/dist/core/worktree.js.map +1 -1
  46. package/dist/parsers/stream-renderer.d.ts +2 -0
  47. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  48. package/dist/parsers/stream-renderer.js +2 -0
  49. package/dist/parsers/stream-renderer.js.map +1 -1
  50. package/dist/prompts/amend.d.ts.map +1 -1
  51. package/dist/prompts/amend.js +3 -1
  52. package/dist/prompts/amend.js.map +1 -1
  53. package/dist/prompts/planning.d.ts.map +1 -1
  54. package/dist/prompts/planning.js +3 -1
  55. package/dist/prompts/planning.js.map +1 -1
  56. package/dist/types/config.d.ts +4 -24
  57. package/dist/types/config.d.ts.map +1 -1
  58. package/dist/types/config.js +0 -24
  59. package/dist/types/config.js.map +1 -1
  60. package/dist/utils/config.d.ts +1 -26
  61. package/dist/utils/config.d.ts.map +1 -1
  62. package/dist/utils/config.js +2 -98
  63. package/dist/utils/config.js.map +1 -1
  64. package/dist/utils/frontmatter.d.ts +13 -3
  65. package/dist/utils/frontmatter.d.ts.map +1 -1
  66. package/dist/utils/frontmatter.js +40 -10
  67. package/dist/utils/frontmatter.js.map +1 -1
  68. package/dist/utils/name-generator.d.ts.map +1 -1
  69. package/dist/utils/name-generator.js +7 -16
  70. package/dist/utils/name-generator.js.map +1 -1
  71. package/dist/utils/terminal-symbols.d.ts +7 -16
  72. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  73. package/dist/utils/terminal-symbols.js +16 -42
  74. package/dist/utils/terminal-symbols.js.map +1 -1
  75. package/dist/utils/token-tracker.d.ts +4 -30
  76. package/dist/utils/token-tracker.d.ts.map +1 -1
  77. package/dist/utils/token-tracker.js +17 -98
  78. package/dist/utils/token-tracker.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/commands/config.ts +242 -0
  81. package/src/commands/do.ts +39 -7
  82. package/src/commands/plan.ts +101 -58
  83. package/src/core/claude-runner.ts +82 -12
  84. package/src/core/worktree.ts +37 -1
  85. package/src/parsers/stream-renderer.ts +4 -0
  86. package/src/prompts/amend.ts +3 -1
  87. package/src/prompts/config-docs.md +1 -72
  88. package/src/prompts/planning.ts +3 -1
  89. package/src/types/config.ts +4 -52
  90. package/src/utils/config.ts +2 -112
  91. package/src/utils/frontmatter.ts +41 -11
  92. package/src/utils/name-generator.ts +7 -16
  93. package/src/utils/terminal-symbols.ts +16 -46
  94. package/src/utils/token-tracker.ts +19 -113
  95. package/tests/unit/claude-runner.test.ts +1 -0
  96. package/tests/unit/commit-planning-artifacts-worktree.test.ts +6 -14
  97. package/tests/unit/commit-planning-artifacts.test.ts +4 -12
  98. package/tests/unit/config-command.test.ts +161 -0
  99. package/tests/unit/config.test.ts +6 -148
  100. package/tests/unit/frontmatter.test.ts +95 -1
  101. package/tests/unit/name-generator.test.ts +1 -1
  102. package/tests/unit/post-execution-picker.test.ts +1 -0
  103. package/tests/unit/stream-renderer.test.ts +82 -0
  104. package/tests/unit/terminal-symbols.test.ts +86 -124
  105. package/tests/unit/token-tracker.test.ts +159 -679
  106. package/tests/unit/worktree.test.ts +68 -1
  107. package/src/utils/session-parser.ts +0 -161
  108. package/tests/unit/session-parser.test.ts +0 -301
@@ -1,23 +1,12 @@
1
- import { resolveModelPricingCategory, getPricingConfig, getRateLimitWindowConfig } from './config.js';
2
1
  /**
3
2
  * Sum multiple CostBreakdown objects into a single total.
4
3
  */
5
4
  export function sumCostBreakdowns(costs) {
6
- const result = {
7
- inputCost: 0,
8
- outputCost: 0,
9
- cacheReadCost: 0,
10
- cacheCreateCost: 0,
11
- totalCost: 0,
12
- };
5
+ let totalCost = 0;
13
6
  for (const cost of costs) {
14
- result.inputCost += cost.inputCost;
15
- result.outputCost += cost.outputCost;
16
- result.cacheReadCost += cost.cacheReadCost;
17
- result.cacheCreateCost += cost.cacheCreateCost;
18
- result.totalCost += cost.totalCost;
7
+ totalCost += cost.totalCost;
19
8
  }
20
- return result;
9
+ return { totalCost };
21
10
  }
22
11
  /**
23
12
  * Merge multiple UsageData objects into a single accumulated UsageData.
@@ -30,6 +19,7 @@ export function accumulateUsage(attempts) {
30
19
  cacheReadInputTokens: 0,
31
20
  cacheCreationInputTokens: 0,
32
21
  modelUsage: {},
22
+ totalCostUsd: 0,
33
23
  };
34
24
  for (const attempt of attempts) {
35
25
  result.inputTokens += attempt.inputTokens;
@@ -44,36 +34,35 @@ export function accumulateUsage(attempts) {
44
34
  existing.outputTokens += modelUsage.outputTokens;
45
35
  existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
46
36
  existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
37
+ existing.costUsd += modelUsage.costUsd;
47
38
  }
48
39
  else {
49
40
  result.modelUsage[modelId] = { ...modelUsage };
50
41
  }
51
42
  }
43
+ // Sum totalCostUsd across attempts
44
+ result.totalCostUsd += attempt.totalCostUsd;
52
45
  }
53
46
  return result;
54
47
  }
55
48
  /**
56
- * Accumulates token usage across multiple task executions and calculates costs
57
- * using configurable per-model pricing.
49
+ * Accumulates token usage across multiple task executions using Claude-provided cost data.
58
50
  */
59
51
  export class TokenTracker {
60
52
  entries = [];
61
- pricingConfig;
62
- constructor(pricingConfig) {
63
- this.pricingConfig = pricingConfig ?? getPricingConfig();
53
+ constructor() {
54
+ // No pricing config needed - costs come from Claude
64
55
  }
65
56
  /**
66
57
  * Record usage data from a completed task.
67
58
  * Accepts an array of UsageData (one per attempt) and accumulates them.
68
- * Cost is calculated per-attempt to avoid underreporting when some attempts
69
- * have modelUsage and others only have aggregate fields.
59
+ * Costs are summed from Claude-provided totalCostUsd values.
70
60
  */
71
61
  addTask(taskId, attempts) {
72
62
  const usage = accumulateUsage(attempts);
73
- // Calculate cost per-attempt, then sum. This ensures attempts with only
74
- // aggregate fields use sonnet fallback pricing independently.
75
- const perAttemptCosts = attempts.map((attempt) => this.calculateCost(attempt));
76
- const cost = sumCostBreakdowns(perAttemptCosts);
63
+ // Sum costs from Claude-provided totalCostUsd
64
+ const totalCost = attempts.reduce((sum, attempt) => sum + attempt.totalCostUsd, 0);
65
+ const cost = { totalCost };
77
66
  const entry = { taskId, usage, cost, attempts };
78
67
  this.entries.push(entry);
79
68
  return entry;
@@ -94,12 +83,9 @@ export class TokenTracker {
94
83
  cacheReadInputTokens: 0,
95
84
  cacheCreationInputTokens: 0,
96
85
  modelUsage: {},
86
+ totalCostUsd: 0,
97
87
  };
98
88
  const totalCost = {
99
- inputCost: 0,
100
- outputCost: 0,
101
- cacheReadCost: 0,
102
- cacheCreateCost: 0,
103
89
  totalCost: 0,
104
90
  };
105
91
  for (const entry of this.entries) {
@@ -107,6 +93,7 @@ export class TokenTracker {
107
93
  totalUsage.outputTokens += entry.usage.outputTokens;
108
94
  totalUsage.cacheReadInputTokens += entry.usage.cacheReadInputTokens;
109
95
  totalUsage.cacheCreationInputTokens += entry.usage.cacheCreationInputTokens;
96
+ totalUsage.totalCostUsd += entry.usage.totalCostUsd;
110
97
  // Merge per-model usage
111
98
  for (const [modelId, modelUsage] of Object.entries(entry.usage.modelUsage)) {
112
99
  const existing = totalUsage.modelUsage[modelId];
@@ -115,83 +102,15 @@ export class TokenTracker {
115
102
  existing.outputTokens += modelUsage.outputTokens;
116
103
  existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
117
104
  existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
105
+ existing.costUsd += modelUsage.costUsd;
118
106
  }
119
107
  else {
120
108
  totalUsage.modelUsage[modelId] = { ...modelUsage };
121
109
  }
122
110
  }
123
- totalCost.inputCost += entry.cost.inputCost;
124
- totalCost.outputCost += entry.cost.outputCost;
125
- totalCost.cacheReadCost += entry.cost.cacheReadCost;
126
- totalCost.cacheCreateCost += entry.cost.cacheCreateCost;
127
111
  totalCost.totalCost += entry.cost.totalCost;
128
112
  }
129
113
  return { usage: totalUsage, cost: totalCost };
130
114
  }
131
- /**
132
- * Calculate cost for a given UsageData using per-model pricing.
133
- * Uses per-model breakdown when available, falls back to aggregate with sonnet pricing.
134
- */
135
- calculateCost(usage) {
136
- const result = {
137
- inputCost: 0,
138
- outputCost: 0,
139
- cacheReadCost: 0,
140
- cacheCreateCost: 0,
141
- totalCost: 0,
142
- };
143
- const modelEntries = Object.entries(usage.modelUsage);
144
- if (modelEntries.length > 0) {
145
- // Use per-model breakdown for accurate pricing
146
- for (const [modelId, modelUsage] of modelEntries) {
147
- const category = resolveModelPricingCategory(modelId);
148
- const pricing = this.pricingConfig[category ?? 'sonnet'];
149
- result.inputCost += (modelUsage.inputTokens / 1_000_000) * pricing.inputPerMTok;
150
- result.outputCost += (modelUsage.outputTokens / 1_000_000) * pricing.outputPerMTok;
151
- result.cacheReadCost += (modelUsage.cacheReadInputTokens / 1_000_000) * pricing.cacheReadPerMTok;
152
- result.cacheCreateCost += (modelUsage.cacheCreationInputTokens / 1_000_000) * pricing.cacheCreatePerMTok;
153
- }
154
- }
155
- else {
156
- // Fallback: use aggregate totals with sonnet pricing
157
- const pricing = this.pricingConfig.sonnet;
158
- result.inputCost = (usage.inputTokens / 1_000_000) * pricing.inputPerMTok;
159
- result.outputCost = (usage.outputTokens / 1_000_000) * pricing.outputPerMTok;
160
- result.cacheReadCost = (usage.cacheReadInputTokens / 1_000_000) * pricing.cacheReadPerMTok;
161
- result.cacheCreateCost = (usage.cacheCreationInputTokens / 1_000_000) * pricing.cacheCreatePerMTok;
162
- }
163
- result.totalCost = result.inputCost + result.outputCost + result.cacheReadCost + result.cacheCreateCost;
164
- return result;
165
- }
166
- /**
167
- * Calculate the 5h rate limit window percentage for a given cost.
168
- * Converts cost to Sonnet-equivalent tokens using the configured Sonnet pricing,
169
- * then divides by the configured cap.
170
- *
171
- * @param totalCost - The total cost in dollars
172
- * @param sonnetTokenCap - Optional override for the Sonnet-equivalent token cap (defaults to config value)
173
- * @returns The percentage of the 5h window consumed (0-100+)
174
- */
175
- calculateRateLimitPercentage(totalCost, sonnetTokenCap) {
176
- if (totalCost === 0)
177
- return 0;
178
- // Get the configured cap or use the provided override
179
- const cap = sonnetTokenCap ?? getRateLimitWindowConfig().sonnetTokenCap;
180
- // Calculate the average Sonnet cost per token
181
- // Using the average of input and output pricing (simplified approach)
182
- const sonnetPricing = this.pricingConfig.sonnet;
183
- const avgSonnetCostPerToken = (sonnetPricing.inputPerMTok + sonnetPricing.outputPerMTok) / 2 / 1_000_000;
184
- // Convert cost to Sonnet-equivalent tokens
185
- const sonnetEquivalentTokens = totalCost / avgSonnetCostPerToken;
186
- // Calculate percentage
187
- return (sonnetEquivalentTokens / cap) * 100;
188
- }
189
- /**
190
- * Get the cumulative 5h window percentage across all recorded tasks.
191
- */
192
- getCumulativeRateLimitPercentage(sonnetTokenCap) {
193
- const totals = this.getTotals();
194
- return this.calculateRateLimitPercentage(totals.cost.totalCost, sonnetTokenCap);
195
- }
196
115
  }
197
116
  //# sourceMappingURL=token-tracker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/utils/token-tracker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafcode",
3
- "version": "2.4.0",
3
+ "version": "2.5.0-0",
4
4
  "description": "Automated Task Planning & Execution with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,11 +13,16 @@ import {
13
13
  validateConfig,
14
14
  ConfigValidationError,
15
15
  resetConfigCache,
16
+ resolveConfig,
17
+ saveConfig,
16
18
  } from '../utils/config.js';
17
19
  import { DEFAULT_CONFIG } from '../types/config.js';
20
+ import type { UserConfig } from '../types/config.js';
18
21
 
19
22
  interface ConfigCommandOptions {
20
23
  reset?: boolean;
24
+ get?: true | string; // true when --get with no key, string when --get <key>
25
+ set?: string[]; // [key, value]
21
26
  }
22
27
 
23
28
  /**
@@ -114,17 +119,254 @@ async function confirm(message: string): Promise<boolean> {
114
119
  });
115
120
  }
116
121
 
122
+ // ---- Helper functions for nested config access ----
123
+
124
+ /**
125
+ * Get a nested value from an object using dot notation.
126
+ * Returns undefined if the path doesn't exist.
127
+ */
128
+ function getNestedValue(obj: unknown, dotPath: string): unknown {
129
+ const keys = dotPath.split('.');
130
+ let current: unknown = obj;
131
+
132
+ for (const key of keys) {
133
+ if (current === null || typeof current !== 'object') {
134
+ return undefined;
135
+ }
136
+ current = (current as Record<string, unknown>)[key];
137
+ }
138
+
139
+ return current;
140
+ }
141
+
142
+ /**
143
+ * Set a nested value in an object using dot notation.
144
+ * Creates intermediate objects as needed.
145
+ */
146
+ function setNestedValue(obj: Record<string, unknown>, dotPath: string, value: unknown): void {
147
+ const keys = dotPath.split('.');
148
+ let current: Record<string, unknown> = obj;
149
+
150
+ for (let i = 0; i < keys.length - 1; i++) {
151
+ const key = keys[i]!;
152
+ if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
153
+ current[key] = {};
154
+ }
155
+ current = current[key] as Record<string, unknown>;
156
+ }
157
+
158
+ const lastKey = keys[keys.length - 1]!;
159
+ current[lastKey] = value;
160
+ }
161
+
162
+ /**
163
+ * Delete a nested value from an object using dot notation.
164
+ * Cleans up empty parent objects after deletion.
165
+ */
166
+ function deleteNestedValue(obj: Record<string, unknown>, dotPath: string): void {
167
+ const keys = dotPath.split('.');
168
+
169
+ // Navigate to parent and delete the key
170
+ if (keys.length === 1) {
171
+ delete obj[keys[0]!];
172
+ return;
173
+ }
174
+
175
+ // Build path to parent
176
+ let current: Record<string, unknown> = obj;
177
+ const path: Array<{ obj: Record<string, unknown>; key: string }> = [];
178
+
179
+ for (let i = 0; i < keys.length - 1; i++) {
180
+ const key = keys[i]!;
181
+ path.push({ obj: current, key });
182
+
183
+ if (typeof current[key] !== 'object' || current[key] === null) {
184
+ return; // Path doesn't exist
185
+ }
186
+ current = current[key] as Record<string, unknown>;
187
+ }
188
+
189
+ // Delete the leaf value
190
+ const lastKey = keys[keys.length - 1]!;
191
+ delete current[lastKey];
192
+
193
+ // Clean up empty parents
194
+ for (let i = path.length - 1; i >= 0; i--) {
195
+ const { obj, key } = path[i]!;
196
+ const child = obj[key] as Record<string, unknown>;
197
+ if (Object.keys(child).length === 0) {
198
+ delete obj[key];
199
+ } else {
200
+ break; // Stop if we find a non-empty parent
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Get the default value at a dot-notation path from DEFAULT_CONFIG.
207
+ */
208
+ function getDefaultValue(dotPath: string): unknown {
209
+ return getNestedValue(DEFAULT_CONFIG, dotPath);
210
+ }
211
+
212
+ /**
213
+ * Parse a string value, attempting JSON.parse for numbers/booleans, falling back to string.
214
+ */
215
+ function parseValue(value: string): unknown {
216
+ // Try JSON.parse for numbers, booleans, null
217
+ try {
218
+ return JSON.parse(value);
219
+ } catch {
220
+ // Fall back to string
221
+ return value;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Format a value for console output.
227
+ * Strings are printed plain, objects/arrays as JSON.
228
+ */
229
+ function formatValue(value: unknown): string {
230
+ if (typeof value === 'string') {
231
+ return value;
232
+ }
233
+ if (typeof value === 'number' || typeof value === 'boolean') {
234
+ return String(value);
235
+ }
236
+ if (value === null || value === undefined) {
237
+ return String(value);
238
+ }
239
+ return JSON.stringify(value, null, 2);
240
+ }
241
+
242
+ // ---- Config get/set handlers ----
243
+
244
+ /**
245
+ * Handle --get flag: print config value(s).
246
+ */
247
+ function handleGet(key: true | string): void {
248
+ const config = resolveConfig();
249
+
250
+ if (key === true) {
251
+ // No key specified: print full config
252
+ console.log(JSON.stringify(config, null, 2));
253
+ return;
254
+ }
255
+
256
+ // Specific key requested
257
+ const value = getNestedValue(config, key);
258
+
259
+ if (value === undefined) {
260
+ logger.error(`Config key not found: ${key}`);
261
+ process.exit(1);
262
+ }
263
+
264
+ console.log(formatValue(value));
265
+ }
266
+
267
+ /**
268
+ * Handle --set flag: update config file with a new value.
269
+ */
270
+ function handleSet(args: string[]): void {
271
+ if (args.length !== 2) {
272
+ logger.error('--set requires exactly 2 arguments: key and value');
273
+ process.exit(1);
274
+ }
275
+
276
+ const [key, rawValue] = args as [string, string];
277
+ const value = parseValue(rawValue);
278
+ const configPath = getConfigPath();
279
+
280
+ // Read current user config (or start with empty)
281
+ let userConfig: UserConfig = {};
282
+ if (fs.existsSync(configPath)) {
283
+ try {
284
+ const content = fs.readFileSync(configPath, 'utf-8');
285
+ userConfig = JSON.parse(content) as UserConfig;
286
+ } catch (error) {
287
+ logger.error(`Failed to read config file: ${error}`);
288
+ process.exit(1);
289
+ }
290
+ }
291
+
292
+ // Check if value matches default
293
+ const defaultValue = getDefaultValue(key);
294
+
295
+ if (defaultValue === undefined) {
296
+ logger.error(`Config key not found in schema: ${key}`);
297
+ process.exit(1);
298
+ }
299
+
300
+ // Deep equality check for objects
301
+ const valuesMatch = JSON.stringify(value) === JSON.stringify(defaultValue);
302
+
303
+ if (valuesMatch) {
304
+ // Remove from config file (keep config minimal)
305
+ deleteNestedValue(userConfig as Record<string, unknown>, key);
306
+ logger.info(`Value matches default, removing ${key} from config`);
307
+ } else {
308
+ // Set the value
309
+ setNestedValue(userConfig as Record<string, unknown>, key, value);
310
+ logger.info(`Set ${key} = ${formatValue(value)}`);
311
+ }
312
+
313
+ // Validate the resulting config
314
+ try {
315
+ validateConfig(userConfig);
316
+ } catch (error) {
317
+ if (error instanceof ConfigValidationError) {
318
+ logger.error(`Validation error: ${error.message}`);
319
+ process.exit(1);
320
+ }
321
+ throw error;
322
+ }
323
+
324
+ // Save or delete config file
325
+ if (Object.keys(userConfig).length === 0) {
326
+ // Config is empty, delete the file
327
+ if (fs.existsSync(configPath)) {
328
+ fs.unlinkSync(configPath);
329
+ logger.info('Config is empty, removed file');
330
+ }
331
+ } else {
332
+ saveConfig(configPath, userConfig);
333
+ logger.success('Config updated successfully');
334
+ }
335
+ }
336
+
117
337
  export function createConfigCommand(): Command {
118
338
  const command = new Command('config')
119
339
  .description('View and edit RAF configuration with Claude')
120
340
  .argument('[prompt...]', 'Optional initial prompt for the config session')
121
341
  .option('--reset', 'Delete config file and restore all defaults')
342
+ .option('--get [key]', 'Show config value (all config if no key, or specific dot-notation key)')
343
+ .option('--set <items...>', 'Set a config value using dot-notation key and value')
122
344
  .action(async (promptParts: string[], options: ConfigCommandOptions) => {
345
+ // --reset takes precedence
123
346
  if (options.reset) {
124
347
  await handleReset();
125
348
  return;
126
349
  }
127
350
 
351
+ // --get and --set are mutually exclusive
352
+ if (options.get !== undefined && options.set !== undefined) {
353
+ logger.error('Cannot use --get and --set together');
354
+ process.exit(1);
355
+ }
356
+
357
+ // Handle --get
358
+ if (options.get !== undefined) {
359
+ handleGet(options.get);
360
+ return;
361
+ }
362
+
363
+ // Handle --set
364
+ if (options.set !== undefined) {
365
+ handleSet(options.set);
366
+ return;
367
+ }
368
+
369
+ // Default: run interactive session
128
370
  const initialPrompt = promptParts.length > 0 ? promptParts.join(' ') : undefined;
129
371
  await runConfigSession(initialPrompt);
130
372
  });
@@ -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, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling } from '../utils/config.js';
16
+ import { getConfig, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
17
17
  import type { PlanFrontmatter } from '../utils/frontmatter.js';
18
18
  import { getVersion } from '../utils/version.js';
19
19
  import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
@@ -53,6 +53,8 @@ import {
53
53
  resolveWorktreeProjectByIdentifier,
54
54
  pushMainBranch,
55
55
  pullMainBranch,
56
+ detectMainBranch,
57
+ rebaseOntoMain,
56
58
  } from '../core/worktree.js';
57
59
  import { createPullRequest, prPreflight } from '../core/pull-request.js';
58
60
  import type { DoCommandOptions } from '../types/config.js';
@@ -224,6 +226,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
224
226
  // Variables for worktree context (set when --worktree is used)
225
227
  let worktreeRoot: string | undefined;
226
228
  let originalBranch: string | undefined;
229
+ let mainBranchName: string | null = null;
227
230
 
228
231
  if (worktreeMode) {
229
232
  // Validate git repo
@@ -241,6 +244,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
241
244
  // Sync main branch before worktree operations (if enabled)
242
245
  if (getSyncMainBranch()) {
243
246
  const syncResult = pullMainBranch();
247
+ mainBranchName = syncResult.mainBranch;
244
248
  if (syncResult.success) {
245
249
  if (syncResult.hadChanges) {
246
250
  logger.info(`Synced ${syncResult.mainBranch} from remote`);
@@ -462,6 +466,20 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
462
466
  }
463
467
  throw error;
464
468
  }
469
+
470
+ // Rebase worktree branch onto main before execution (if sync is enabled)
471
+ if (getSyncMainBranch()) {
472
+ const mainBranch = mainBranchName ?? detectMainBranch();
473
+ if (mainBranch) {
474
+ const rebaseResult = rebaseOntoMain(mainBranch, worktreeRoot);
475
+ if (rebaseResult.success) {
476
+ logger.info(`Rebased onto ${mainBranch}`);
477
+ } else {
478
+ logger.warn(`Could not rebase onto ${mainBranch}: ${rebaseResult.error}`);
479
+ logger.warn('Continuing with current branch state.');
480
+ }
481
+ }
482
+ }
465
483
  }
466
484
 
467
485
  // Execute project
@@ -1003,6 +1021,8 @@ async function executeSingleProject(
1003
1021
  const attemptUsageData: import('../types/config.js').UsageData[] = [];
1004
1022
  // Track failure history for each attempt (attempt number -> reason)
1005
1023
  const failureHistory: Array<{ attempt: number; reason: string }> = [];
1024
+ // Track current model for display in status line (updated in retry loop)
1025
+ let currentModel: string | undefined;
1006
1026
 
1007
1027
  // Set up timer for elapsed time tracking
1008
1028
  const statusLine = createStatusLine();
@@ -1013,7 +1033,8 @@ async function executeSingleProject(
1013
1033
  return;
1014
1034
  }
1015
1035
  // Show running status with task name and timer (updates in place)
1016
- statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed, taskId));
1036
+ const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1037
+ statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed, taskId, modelShortName));
1017
1038
  });
1018
1039
  timer.start();
1019
1040
 
@@ -1036,6 +1057,9 @@ async function executeSingleProject(
1036
1057
  isRetry,
1037
1058
  );
1038
1059
 
1060
+ // Update current model for timer callback display
1061
+ currentModel = modelResolution.model;
1062
+
1039
1063
  // Log missing frontmatter warning on first attempt only
1040
1064
  if (!isRetry && modelResolution.missingFrontmatter) {
1041
1065
  logger.warn(` No effort frontmatter found — using ceiling model`);
@@ -1211,13 +1235,16 @@ Task completed. No detailed report provided.
1211
1235
  logger.success(` Task ${taskLabel} completed (${elapsedFormatted})`);
1212
1236
  } else {
1213
1237
  // Minimal mode: show completed task line
1214
- logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id));
1238
+ const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1239
+ logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id, modelShortName));
1215
1240
  }
1216
1241
 
1217
1242
  // Track and display token usage for this task
1218
1243
  if (attemptUsageData.length > 0) {
1219
1244
  const entry = tokenTracker.addTask(task.id, attemptUsageData);
1220
- logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u)));
1245
+ logger.dim(formatTaskTokenSummary(entry, {
1246
+ showCacheTokens: getShowCacheTokens(),
1247
+ }));
1221
1248
  }
1222
1249
 
1223
1250
  completedInSession.add(task.id);
@@ -1239,13 +1266,16 @@ Task completed. No detailed report provided.
1239
1266
  logger.info(` Analyzing failure with ${analysisModel}...`);
1240
1267
  } else {
1241
1268
  // Minimal mode: show failed task line
1242
- logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id));
1269
+ const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1270
+ logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id, modelShortName));
1243
1271
  }
1244
1272
 
1245
1273
  // Track token usage even for failed tasks (partial data still useful for totals)
1246
1274
  if (attemptUsageData.length > 0) {
1247
1275
  const entry = tokenTracker.addTask(task.id, attemptUsageData);
1248
- logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u)));
1276
+ logger.dim(formatTaskTokenSummary(entry, {
1277
+ showCacheTokens: getShowCacheTokens(),
1278
+ }));
1249
1279
  }
1250
1280
 
1251
1281
  // Analyze failure and generate structured report
@@ -1358,7 +1388,9 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1358
1388
  if (trackerEntries.length > 0) {
1359
1389
  logger.newline();
1360
1390
  const totals = tokenTracker.getTotals();
1361
- logger.dim(formatTokenTotalSummary(totals.usage, totals.cost));
1391
+ logger.dim(formatTokenTotalSummary(totals.usage, totals.cost, {
1392
+ showCacheTokens: getShowCacheTokens(),
1393
+ }));
1362
1394
  }
1363
1395
 
1364
1396
  // Show retry history for tasks that had failures (even if eventually successful)