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
@@ -39,38 +39,12 @@ export interface CommitFormatConfig {
39
39
  prefix: string;
40
40
  }
41
41
 
42
- /** Pricing category derived from model family name. */
43
- export type PricingCategory = 'opus' | 'sonnet' | 'haiku';
44
-
45
- /** Per-direction pricing for a single model category, in dollars per million tokens. */
46
- export interface ModelPricing {
47
- inputPerMTok: number;
48
- outputPerMTok: number;
49
- cacheReadPerMTok: number;
50
- cacheCreatePerMTok: number;
51
- }
52
-
53
- /** Pricing config: per-category pricing in dollars per million tokens. */
54
- export interface PricingConfig {
55
- opus: ModelPricing;
56
- sonnet: ModelPricing;
57
- haiku: ModelPricing;
58
- }
59
-
60
42
  /** Display options for token usage summaries. */
61
43
  export interface DisplayConfig {
62
- /** Show estimated 5h rate limit window percentage. Default: true */
63
- showRateLimitEstimate: boolean;
64
44
  /** Show cache token counts in summaries. Default: true */
65
45
  showCacheTokens: boolean;
66
46
  }
67
47
 
68
- /** Rate limit window configuration. */
69
- export interface RateLimitWindowConfig {
70
- /** Sonnet-equivalent token cap for the 5h window. Default: 88000 */
71
- sonnetTokenCap: number;
72
- }
73
-
74
48
  export interface RafConfig {
75
49
  models: ModelsConfig;
76
50
  /** Maps task complexity labels (low/medium/high) to models. Used for per-task effort frontmatter. */
@@ -82,9 +56,7 @@ export interface RafConfig {
82
56
  /** Sync main branch with remote before worktree/PR operations. Default: true */
83
57
  syncMainBranch: boolean;
84
58
  commitFormat: CommitFormatConfig;
85
- pricing: PricingConfig;
86
59
  display: DisplayConfig;
87
- rateLimitWindow: RateLimitWindowConfig;
88
60
  }
89
61
 
90
62
  export const DEFAULT_CONFIG: RafConfig = {
@@ -112,33 +84,9 @@ export const DEFAULT_CONFIG: RafConfig = {
112
84
  amend: '{prefix}[{projectId}] Amend: {projectName}',
113
85
  prefix: 'RAF',
114
86
  },
115
- pricing: {
116
- opus: {
117
- inputPerMTok: 15,
118
- outputPerMTok: 75,
119
- cacheReadPerMTok: 1.5,
120
- cacheCreatePerMTok: 18.75,
121
- },
122
- sonnet: {
123
- inputPerMTok: 3,
124
- outputPerMTok: 15,
125
- cacheReadPerMTok: 0.3,
126
- cacheCreatePerMTok: 3.75,
127
- },
128
- haiku: {
129
- inputPerMTok: 1,
130
- outputPerMTok: 5,
131
- cacheReadPerMTok: 0.1,
132
- cacheCreatePerMTok: 1.25,
133
- },
134
- },
135
87
  display: {
136
- showRateLimitEstimate: true,
137
88
  showCacheTokens: true,
138
89
  },
139
- rateLimitWindow: {
140
- sonnetTokenCap: 88000,
141
- },
142
90
  };
143
91
 
144
92
  /** Deep partial type for user config files — all fields optional at every level */
@@ -201,6 +149,8 @@ export interface ModelTokenUsage {
201
149
  outputTokens: number;
202
150
  cacheReadInputTokens: number;
203
151
  cacheCreationInputTokens: number;
152
+ /** Cost in USD for this model's usage (provided by Claude CLI). */
153
+ costUsd: number;
204
154
  }
205
155
 
206
156
  /** Token usage data extracted from Claude CLI stream-json result event. */
@@ -212,4 +162,6 @@ export interface UsageData {
212
162
  cacheCreationInputTokens: number;
213
163
  /** Per-model breakdown (e.g., { "claude-opus-4-6": { ... } }). */
214
164
  modelUsage: Record<string, ModelTokenUsage>;
165
+ /** Total cost in USD for this usage (provided by Claude CLI). */
166
+ totalCostUsd: number;
215
167
  }
@@ -12,11 +12,7 @@ import {
12
12
  TaskEffortLevel,
13
13
  ModelScenario,
14
14
  CommitFormatType,
15
- PricingCategory,
16
- ModelPricing,
17
- PricingConfig,
18
15
  DisplayConfig,
19
- RateLimitWindowConfig,
20
16
  EffortMappingConfig,
21
17
  } from '../types/config.js';
22
18
 
@@ -38,12 +34,9 @@ export function getClaudeSettingsPath(): string {
38
34
 
39
35
  const VALID_TOP_LEVEL_KEYS = new Set<string>([
40
36
  'models', 'effortMapping', 'timeout', 'maxRetries', 'autoCommit',
41
- 'worktree', 'syncMainBranch', 'commitFormat', 'pricing', 'display', 'rateLimitWindow',
37
+ 'worktree', 'syncMainBranch', 'commitFormat', 'display',
42
38
  ]);
43
39
 
44
- const VALID_PRICING_CATEGORIES = new Set<string>(['opus', 'sonnet', 'haiku']);
45
- const VALID_PRICING_FIELDS = new Set<string>(['inputPerMTok', 'outputPerMTok', 'cacheReadPerMTok', 'cacheCreatePerMTok']);
46
-
47
40
  const VALID_MODEL_KEYS = new Set<string>([
48
41
  'plan', 'execute', 'nameGeneration', 'failureAnalysis', 'prGeneration', 'config',
49
42
  ]);
@@ -52,9 +45,7 @@ const VALID_EFFORT_MAPPING_KEYS = new Set<string>(['low', 'medium', 'high']);
52
45
 
53
46
  const VALID_COMMIT_FORMAT_KEYS = new Set<string>(['task', 'plan', 'amend', 'prefix']);
54
47
 
55
- const VALID_DISPLAY_KEYS = new Set<string>(['showRateLimitEstimate', 'showCacheTokens']);
56
-
57
- const VALID_RATE_LIMIT_WINDOW_KEYS = new Set<string>(['sonnetTokenCap']);
48
+ const VALID_DISPLAY_KEYS = new Set<string>(['showCacheTokens']);
58
49
 
59
50
  export class ConfigValidationError extends Error {
60
51
  constructor(message: string) {
@@ -167,27 +158,6 @@ export function validateConfig(config: unknown): UserConfig {
167
158
  }
168
159
  }
169
160
 
170
- // pricing
171
- if (obj.pricing !== undefined) {
172
- if (typeof obj.pricing !== 'object' || obj.pricing === null || Array.isArray(obj.pricing)) {
173
- throw new ConfigValidationError('pricing must be an object');
174
- }
175
- const pricing = obj.pricing as Record<string, unknown>;
176
- checkUnknownKeys(pricing, VALID_PRICING_CATEGORIES, 'pricing');
177
- for (const [category, catVal] of Object.entries(pricing)) {
178
- if (typeof catVal !== 'object' || catVal === null || Array.isArray(catVal)) {
179
- throw new ConfigValidationError(`pricing.${category} must be an object`);
180
- }
181
- const fields = catVal as Record<string, unknown>;
182
- checkUnknownKeys(fields, VALID_PRICING_FIELDS, `pricing.${category}`);
183
- for (const [field, val] of Object.entries(fields)) {
184
- if (typeof val !== 'number' || val < 0 || !Number.isFinite(val)) {
185
- throw new ConfigValidationError(`pricing.${category}.${field} must be a non-negative number`);
186
- }
187
- }
188
- }
189
- }
190
-
191
161
  // display
192
162
  if (obj.display !== undefined) {
193
163
  if (typeof obj.display !== 'object' || obj.display === null || Array.isArray(obj.display)) {
@@ -202,20 +172,6 @@ export function validateConfig(config: unknown): UserConfig {
202
172
  }
203
173
  }
204
174
 
205
- // rateLimitWindow
206
- if (obj.rateLimitWindow !== undefined) {
207
- if (typeof obj.rateLimitWindow !== 'object' || obj.rateLimitWindow === null || Array.isArray(obj.rateLimitWindow)) {
208
- throw new ConfigValidationError('rateLimitWindow must be an object');
209
- }
210
- const rlw = obj.rateLimitWindow as Record<string, unknown>;
211
- checkUnknownKeys(rlw, VALID_RATE_LIMIT_WINDOW_KEYS, 'rateLimitWindow');
212
- if (rlw.sonnetTokenCap !== undefined) {
213
- if (typeof rlw.sonnetTokenCap !== 'number' || rlw.sonnetTokenCap <= 0 || !Number.isFinite(rlw.sonnetTokenCap)) {
214
- throw new ConfigValidationError('rateLimitWindow.sonnetTokenCap must be a positive number');
215
- }
216
- }
217
- }
218
-
219
175
  return config as UserConfig;
220
176
  }
221
177
 
@@ -233,19 +189,9 @@ function deepMerge(defaults: RafConfig, overrides: UserConfig): RafConfig {
233
189
  if (overrides.commitFormat) {
234
190
  result.commitFormat = { ...defaults.commitFormat, ...overrides.commitFormat };
235
191
  }
236
- if (overrides.pricing) {
237
- result.pricing = {
238
- opus: { ...defaults.pricing.opus, ...overrides.pricing.opus },
239
- sonnet: { ...defaults.pricing.sonnet, ...overrides.pricing.sonnet },
240
- haiku: { ...defaults.pricing.haiku, ...overrides.pricing.haiku },
241
- };
242
- }
243
192
  if (overrides.display) {
244
193
  result.display = { ...defaults.display, ...overrides.display };
245
194
  }
246
- if (overrides.rateLimitWindow) {
247
- result.rateLimitWindow = { ...defaults.rateLimitWindow, ...overrides.rateLimitWindow };
248
- }
249
195
  if (overrides.timeout !== undefined) result.timeout = overrides.timeout;
250
196
  if (overrides.maxRetries !== undefined) result.maxRetries = overrides.maxRetries;
251
197
  if (overrides.autoCommit !== undefined) result.autoCommit = overrides.autoCommit;
@@ -271,7 +217,6 @@ export function resolveConfig(configPath?: string): RafConfig {
271
217
  effortMapping: { ...DEFAULT_CONFIG.effortMapping },
272
218
  commitFormat: { ...DEFAULT_CONFIG.commitFormat },
273
219
  display: { ...DEFAULT_CONFIG.display },
274
- rateLimitWindow: { ...DEFAULT_CONFIG.rateLimitWindow },
275
220
  };
276
221
  }
277
222
 
@@ -459,40 +404,6 @@ export function resolveFullModelId(modelName: string): string {
459
404
  return modelName;
460
405
  }
461
406
 
462
- /**
463
- * Map a full model ID (e.g., `claude-opus-4-6`) or short alias to a pricing category.
464
- * Returns null if the model cannot be mapped.
465
- */
466
- export function resolveModelPricingCategory(modelId: string): PricingCategory | null {
467
- // Short aliases map directly
468
- if (modelId === 'opus' || modelId === 'sonnet' || modelId === 'haiku') {
469
- return modelId;
470
- }
471
- // Full model IDs: extract family from `claude-{family}-{version}`
472
- const match = modelId.match(/^claude-([a-z]+)-/);
473
- if (match) {
474
- const family = match[1];
475
- if (family === 'opus' || family === 'sonnet' || family === 'haiku') {
476
- return family;
477
- }
478
- }
479
- return null;
480
- }
481
-
482
- /**
483
- * Get pricing config for a specific model category.
484
- */
485
- export function getPricing(category: PricingCategory): ModelPricing {
486
- return getResolvedConfig().pricing[category];
487
- }
488
-
489
- /**
490
- * Get the full pricing config.
491
- */
492
- export function getPricingConfig(): PricingConfig {
493
- return getResolvedConfig().pricing;
494
- }
495
-
496
407
  /**
497
408
  * Get the full display config.
498
409
  */
@@ -500,20 +411,6 @@ export function getDisplayConfig(): DisplayConfig {
500
411
  return getResolvedConfig().display;
501
412
  }
502
413
 
503
- /**
504
- * Get the full rate limit window config.
505
- */
506
- export function getRateLimitWindowConfig(): RateLimitWindowConfig {
507
- return getResolvedConfig().rateLimitWindow;
508
- }
509
-
510
- /**
511
- * Get whether to show rate limit estimate in token summaries.
512
- */
513
- export function getShowRateLimitEstimate(): boolean {
514
- return getResolvedConfig().display.showRateLimitEstimate;
515
- }
516
-
517
414
  /**
518
415
  * Get whether to show cache tokens in summaries.
519
416
  */
@@ -521,13 +418,6 @@ export function getShowCacheTokens(): boolean {
521
418
  return getResolvedConfig().display.showCacheTokens;
522
419
  }
523
420
 
524
- /**
525
- * Get the Sonnet-equivalent token cap for the 5h rate limit window.
526
- */
527
- export function getSonnetTokenCap(): number {
528
- return getResolvedConfig().rateLimitWindow.sonnetTokenCap;
529
- }
530
-
531
421
  /**
532
422
  * Render a commit message template by replacing {placeholder} tokens with values.
533
423
  * Unknown placeholders are left as-is.
@@ -26,10 +26,20 @@ export interface FrontmatterParseResult {
26
26
  /**
27
27
  * Parse Obsidian-style frontmatter from plan file content.
28
28
  *
29
- * Format: `key: value` lines at the top of the file, terminated by a `---` line.
30
- * There is NO opening `---` delimiter just properties followed by `---`.
29
+ * Supports two formats:
30
+ * 1. Standard format (preferred): `---` delimiter at the top and bottom
31
+ * 2. Legacy format (backward compatibility): properties followed by closing `---` only
31
32
  *
32
- * Example:
33
+ * Standard format example:
34
+ * ```
35
+ * ---
36
+ * effort: medium
37
+ * model: sonnet
38
+ * ---
39
+ * # Task: ...
40
+ * ```
41
+ *
42
+ * Legacy format example:
33
43
  * ```
34
44
  * effort: medium
35
45
  * model: sonnet
@@ -50,15 +60,35 @@ export function parsePlanFrontmatter(content: string): FrontmatterParseResult {
50
60
  warnings: [],
51
61
  };
52
62
 
53
- // Find the closing `---` delimiter
54
- const delimiterIndex = content.indexOf('---');
55
- if (delimiterIndex === -1) {
56
- // No delimiter found - no frontmatter
57
- return result;
58
- }
63
+ const trimmedContent = content.trimStart();
59
64
 
60
- // Extract the frontmatter section (everything before the delimiter)
61
- const frontmatterSection = content.substring(0, delimiterIndex);
65
+ let frontmatterSection: string;
66
+
67
+ if (trimmedContent.startsWith('---')) {
68
+ // Standard format: ---\nkey: value\n---
69
+ const afterOpener = trimmedContent.substring(3);
70
+ // Skip the rest of the opener line (handles "---\n" or "--- \n")
71
+ const openerEnd = afterOpener.indexOf('\n');
72
+ if (openerEnd === -1) {
73
+ // No newline after opening delimiter - no valid frontmatter
74
+ return result;
75
+ }
76
+ const rest = afterOpener.substring(openerEnd + 1);
77
+ const closerIndex = rest.indexOf('---');
78
+ if (closerIndex === -1) {
79
+ // No closing delimiter - no valid frontmatter
80
+ return result;
81
+ }
82
+ frontmatterSection = rest.substring(0, closerIndex);
83
+ } else {
84
+ // Legacy format: key: value\n---
85
+ const delimiterIndex = content.indexOf('---');
86
+ if (delimiterIndex === -1) {
87
+ // No delimiter found - no frontmatter
88
+ return result;
89
+ }
90
+ frontmatterSection = content.substring(0, delimiterIndex);
91
+ }
62
92
 
63
93
  // Parse key: value lines
64
94
  const lines = frontmatterSection.split('\n');
@@ -3,7 +3,9 @@ import { logger } from './logger.js';
3
3
  import { sanitizeProjectName } from './validation.js';
4
4
  import { getModel } from './config.js';
5
5
 
6
- const NAME_GENERATION_PROMPT = `Generate a short, punchy, creative project name (1-3 words, kebab-case).
6
+ const NAME_GENERATION_PROMPT = `Output ONLY the kebab-case name. No introduction, no explanation, no quotes.
7
+
8
+ Generate a short, punchy, creative project name (1-3 words, kebab-case).
7
9
 
8
10
  Be creative! Use metaphors, analogies, or evocative words that capture the SPIRIT of the project.
9
11
  Don't literally describe what it does - make it memorable and fun.
@@ -15,26 +17,15 @@ Good examples:
15
17
  - Refactoring → 'spring-cleaning', 'phoenix', 'makeover'
16
18
  - New feature → 'moonshot', 'secret-sauce', 'magic-wand'
17
19
 
18
- Output ONLY the kebab-case name. No quotes, no explanation.
19
-
20
20
  Project description:`;
21
21
 
22
- const MULTI_NAME_GENERATION_PROMPT = `Generate 5 creative project names for the description below.
23
-
24
- IMPORTANT: Each name should use a DIFFERENT naming style:
25
- 1. **Metaphorical** - Use a metaphor or analogy (e.g., 'phoenix', 'lighthouse', 'compass')
26
- 2. **Fun/Playful** - Make it fun or quirky (e.g., 'turbo-boost', 'magic-beans', 'ninja-move')
27
- 3. **Action-oriented** - Focus on what it does with flair (e.g., 'bug-squasher', 'speed-demon', 'data-whisperer')
28
- 4. **Abstract** - Use abstract/poetic concepts (e.g., 'horizon', 'cascade', 'catalyst')
29
- 5. **Cultural reference** - Reference pop culture, mythology, or literature (e.g., 'atlas', 'merlin', 'gandalf')
22
+ const MULTI_NAME_GENERATION_PROMPT = `Output EXACTLY 5 project names, one per line. Do NOT include any introduction, explanation, preamble, numbering, or quotes.
30
23
 
31
24
  Rules:
32
- - Each name should be 1-3 words in kebab-case
33
- - Names must be lowercase with hyphens only
25
+ - Each name: 1-3 words, kebab-case, lowercase with hyphens only
26
+ - Use varied styles: metaphorical, playful, action-oriented, abstract, cultural reference
34
27
  - Make them memorable and evocative
35
- - If the project has many unrelated tasks, prefer abstract/metaphorical/fun names over descriptive ones
36
-
37
- Output format: ONLY output 5 names, one per line, no numbers, no explanations, no quotes.
28
+ - For projects with many unrelated tasks, prefer abstract/metaphorical names
38
29
 
39
30
  Project description:`;
40
31
 
@@ -11,10 +11,6 @@ import type { CostBreakdown, TaskUsageEntry } from './token-tracker.js';
11
11
  export interface TokenSummaryOptions {
12
12
  /** Whether to show cache token counts. Default: true */
13
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
14
  }
19
15
 
20
16
  /**
@@ -49,7 +45,8 @@ function truncate(str: string, maxLength: number): string {
49
45
  * @param name - Task name
50
46
  * @param elapsedMs - Optional elapsed time in milliseconds
51
47
  * @param taskId - Optional task ID prefix display
52
- * @returns Formatted string like "● 001-auth-login 1:23" or " 001-auth-login 1/5"
48
+ * @param model - Optional model short name to display (e.g., "sonnet", "opus", "haiku")
49
+ * @returns Formatted string like "● 001-auth-login (sonnet) 1:23" or "✓ 001-auth-login (opus) 1/5"
53
50
  */
54
51
  export function formatTaskProgress(
55
52
  current: number,
@@ -57,19 +54,21 @@ export function formatTaskProgress(
57
54
  status: TaskStatus,
58
55
  name: string,
59
56
  elapsedMs?: number,
60
- taskId?: string
57
+ taskId?: string,
58
+ model?: string
61
59
  ): string {
62
60
  const symbol = SYMBOLS[status];
63
61
  const displayName = truncate(name || 'task', 40);
64
62
  const idPrefix = taskId ? `${taskId}-` : '';
63
+ const modelSuffix = model ? ` (${model})` : '';
65
64
 
66
65
  // Show elapsed time for running tasks, completed tasks, and failed tasks
67
66
  if (elapsedMs !== undefined) {
68
67
  const timeStr = formatElapsedTime(elapsedMs);
69
- return `${symbol} ${idPrefix}${displayName} ${timeStr}`;
68
+ return `${symbol} ${idPrefix}${displayName}${modelSuffix} ${timeStr}`;
70
69
  }
71
70
 
72
- return `${symbol} ${idPrefix}${displayName} ${current}/${total}`;
71
+ return `${symbol} ${idPrefix}${displayName}${modelSuffix} ${current}/${total}`;
73
72
  }
74
73
 
75
74
  /**
@@ -155,17 +154,6 @@ export function formatCost(cost: number): string {
155
154
  return `$${cost.toFixed(2)}`;
156
155
  }
157
156
 
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
-
169
157
  /**
170
158
  * Formats a single line of token usage (for a single attempt or total).
171
159
  * Used internally by formatTaskTokenSummary.
@@ -177,7 +165,7 @@ function formatTokenLine(
177
165
  indent: string = ' ',
178
166
  options: TokenSummaryOptions = {}
179
167
  ): string {
180
- const { showCacheTokens = true, showRateLimitEstimate = false, rateLimitPercentage } = options;
168
+ const { showCacheTokens = true } = options;
181
169
  const parts: string[] = [];
182
170
  const tokenPart = `${formatNumber(usage.inputTokens)} in / ${formatNumber(usage.outputTokens)} out`;
183
171
  parts.push(prefix ? `${prefix}: ${tokenPart}` : `Tokens: ${tokenPart}`);
@@ -195,27 +183,21 @@ function formatTokenLine(
195
183
  }
196
184
  }
197
185
 
198
- parts.push(`Est. cost: ${formatCost(costValue)}`);
199
-
200
- if (showRateLimitEstimate && rateLimitPercentage !== undefined) {
201
- parts.push(formatRateLimitPercentage(rateLimitPercentage));
202
- }
186
+ parts.push(`Cost: ${formatCost(costValue)}`);
203
187
 
204
188
  return `${indent}${parts.join(' | ')}`;
205
189
  }
206
190
 
207
191
  /**
208
192
  * Formats a per-task token usage summary.
209
- * For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42 | ~2% of 5h window"
193
+ * For single-attempt tasks: " Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Cost: $0.42"
210
194
  * For multi-attempt tasks: shows per-attempt breakdown plus total.
211
195
  *
212
196
  * @param entry - The TaskUsageEntry containing accumulated usage, cost, and attempts array
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
197
+ * @param options - Display options for showing cache tokens
215
198
  */
216
199
  export function formatTaskTokenSummary(
217
200
  entry: TaskUsageEntry,
218
- calculateAttemptCost?: (usage: UsageData) => CostBreakdown,
219
201
  options: TokenSummaryOptions = {}
220
202
  ): string {
221
203
  // Single-attempt: render exactly as before (no per-attempt breakdown)
@@ -224,19 +206,10 @@ export function formatTaskTokenSummary(
224
206
  }
225
207
 
226
208
  // 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
-
234
209
  const lines: string[] = [];
235
210
  entry.attempts.forEach((attemptUsage, i) => {
236
- const attemptCost = calculateAttemptCost
237
- ? calculateAttemptCost(attemptUsage).totalCost
238
- : 0;
239
- lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' ', perAttemptOptions));
211
+ const attemptCost = attemptUsage.totalCostUsd;
212
+ lines.push(formatTokenLine(attemptUsage, attemptCost, `Attempt ${i + 1}`, ' ', options));
240
213
  });
241
214
  lines.push(formatTokenLine(entry.usage, entry.cost.totalCost, 'Total', ' ', options));
242
215
  return lines.join('\n');
@@ -248,14 +221,14 @@ export function formatTaskTokenSummary(
248
221
  *
249
222
  * @param usage - Total usage data
250
223
  * @param cost - Total cost breakdown
251
- * @param options - Display options for cache tokens and rate limit
224
+ * @param options - Display options for cache tokens
252
225
  */
253
226
  export function formatTokenTotalSummary(
254
227
  usage: UsageData,
255
228
  cost: CostBreakdown,
256
229
  options: TokenSummaryOptions = {}
257
230
  ): string {
258
- const { showCacheTokens = true, showRateLimitEstimate = false, rateLimitPercentage } = options;
231
+ const { showCacheTokens = true } = options;
259
232
  const lines: string[] = [];
260
233
  const divider = '── Token Usage Summary ──────────────────';
261
234
  lines.push(divider);
@@ -272,11 +245,8 @@ export function formatTokenTotalSummary(
272
245
  lines.push(`Cache: ${cacheParts.join(' / ')}`);
273
246
  }
274
247
 
275
- lines.push(`Estimated cost: ${formatCost(cost.totalCost)}`);
248
+ lines.push(`Total cost: ${formatCost(cost.totalCost)}`);
276
249
 
277
- if (showRateLimitEstimate && rateLimitPercentage !== undefined) {
278
- lines.push(formatRateLimitPercentage(rateLimitPercentage));
279
- }
280
250
  lines.push('─────────────────────────────────────────');
281
251
  return lines.join('\n');
282
252
  }