rafcode 2.4.1-0 → 2.5.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 (75) hide show
  1. package/CLAUDE.md +4 -4
  2. package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
  3. package/RAF/ahwqwq-model-whisperer/input.md +5 -0
  4. package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
  5. package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
  6. package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
  7. package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
  8. package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
  9. package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
  10. package/RAF/ahwvrz-legacy-sunset/decisions.md +10 -0
  11. package/RAF/ahwvrz-legacy-sunset/input.md +10 -0
  12. package/RAF/ahwvrz-legacy-sunset/outcomes/01-remove-migrate-command.md +30 -0
  13. package/RAF/ahwvrz-legacy-sunset/outcomes/02-fix-resume-worktree-resolution.md +62 -0
  14. package/RAF/ahwvrz-legacy-sunset/plans/01-remove-migrate-command.md +65 -0
  15. package/RAF/ahwvrz-legacy-sunset/plans/02-fix-resume-worktree-resolution.md +72 -0
  16. package/README.md +0 -17
  17. package/dist/commands/do.js +13 -15
  18. package/dist/commands/do.js.map +1 -1
  19. package/dist/commands/plan.d.ts.map +1 -1
  20. package/dist/commands/plan.js +98 -2
  21. package/dist/commands/plan.js.map +1 -1
  22. package/dist/core/claude-runner.d.ts +8 -0
  23. package/dist/core/claude-runner.d.ts.map +1 -1
  24. package/dist/core/claude-runner.js +72 -0
  25. package/dist/core/claude-runner.js.map +1 -1
  26. package/dist/index.js +0 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/parsers/stream-renderer.d.ts +2 -0
  29. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  30. package/dist/parsers/stream-renderer.js +2 -0
  31. package/dist/parsers/stream-renderer.js.map +1 -1
  32. package/dist/prompts/amend.d.ts.map +1 -1
  33. package/dist/prompts/amend.js +3 -1
  34. package/dist/prompts/amend.js.map +1 -1
  35. package/dist/prompts/planning.d.ts.map +1 -1
  36. package/dist/prompts/planning.js +4 -1
  37. package/dist/prompts/planning.js.map +1 -1
  38. package/dist/types/config.d.ts +4 -28
  39. package/dist/types/config.d.ts.map +1 -1
  40. package/dist/types/config.js +0 -24
  41. package/dist/types/config.js.map +1 -1
  42. package/dist/utils/config.d.ts +1 -26
  43. package/dist/utils/config.d.ts.map +1 -1
  44. package/dist/utils/config.js +2 -98
  45. package/dist/utils/config.js.map +1 -1
  46. package/dist/utils/terminal-symbols.d.ts +7 -16
  47. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  48. package/dist/utils/terminal-symbols.js +16 -42
  49. package/dist/utils/terminal-symbols.js.map +1 -1
  50. package/dist/utils/token-tracker.d.ts +4 -30
  51. package/dist/utils/token-tracker.d.ts.map +1 -1
  52. package/dist/utils/token-tracker.js +17 -98
  53. package/dist/utils/token-tracker.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/commands/do.ts +14 -15
  56. package/src/commands/plan.ts +112 -1
  57. package/src/core/claude-runner.ts +81 -0
  58. package/src/index.ts +0 -2
  59. package/src/parsers/stream-renderer.ts +4 -0
  60. package/src/prompts/amend.ts +3 -1
  61. package/src/prompts/config-docs.md +1 -72
  62. package/src/prompts/planning.ts +4 -1
  63. package/src/types/config.ts +4 -57
  64. package/src/utils/config.ts +2 -112
  65. package/src/utils/terminal-symbols.ts +16 -46
  66. package/src/utils/token-tracker.ts +19 -113
  67. package/tests/unit/claude-runner.test.ts +1 -0
  68. package/tests/unit/config-command.test.ts +4 -13
  69. package/tests/unit/config.test.ts +6 -148
  70. package/tests/unit/plan-resume-worktree-resolution.test.ts +153 -0
  71. package/tests/unit/stream-renderer.test.ts +82 -0
  72. package/tests/unit/terminal-symbols.test.ts +86 -124
  73. package/tests/unit/token-tracker.test.ts +159 -679
  74. package/src/commands/migrate.ts +0 -269
  75. package/tests/unit/migrate-command.test.ts +0 -197
@@ -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.1-0",
3
+ "version": "2.5.0",
4
4
  "description": "Automated Task Planning & Execution with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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, getShowRateLimitEstimate, getShowCacheTokens } 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';
@@ -1021,6 +1021,8 @@ async function executeSingleProject(
1021
1021
  const attemptUsageData: import('../types/config.js').UsageData[] = [];
1022
1022
  // Track failure history for each attempt (attempt number -> reason)
1023
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;
1024
1026
 
1025
1027
  // Set up timer for elapsed time tracking
1026
1028
  const statusLine = createStatusLine();
@@ -1031,7 +1033,8 @@ async function executeSingleProject(
1031
1033
  return;
1032
1034
  }
1033
1035
  // Show running status with task name and timer (updates in place)
1034
- 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));
1035
1038
  });
1036
1039
  timer.start();
1037
1040
 
@@ -1054,6 +1057,9 @@ async function executeSingleProject(
1054
1057
  isRetry,
1055
1058
  );
1056
1059
 
1060
+ // Update current model for timer callback display
1061
+ currentModel = modelResolution.model;
1062
+
1057
1063
  // Log missing frontmatter warning on first attempt only
1058
1064
  if (!isRetry && modelResolution.missingFrontmatter) {
1059
1065
  logger.warn(` No effort frontmatter found — using ceiling model`);
@@ -1229,17 +1235,15 @@ Task completed. No detailed report provided.
1229
1235
  logger.success(` Task ${taskLabel} completed (${elapsedFormatted})`);
1230
1236
  } else {
1231
1237
  // Minimal mode: show completed task line
1232
- 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));
1233
1240
  }
1234
1241
 
1235
1242
  // Track and display token usage for this task
1236
1243
  if (attemptUsageData.length > 0) {
1237
1244
  const entry = tokenTracker.addTask(task.id, attemptUsageData);
1238
- const taskRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
1239
- logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
1245
+ logger.dim(formatTaskTokenSummary(entry, {
1240
1246
  showCacheTokens: getShowCacheTokens(),
1241
- showRateLimitEstimate: getShowRateLimitEstimate(),
1242
- rateLimitPercentage: taskRateLimitPct,
1243
1247
  }));
1244
1248
  }
1245
1249
 
@@ -1262,17 +1266,15 @@ Task completed. No detailed report provided.
1262
1266
  logger.info(` Analyzing failure with ${analysisModel}...`);
1263
1267
  } else {
1264
1268
  // Minimal mode: show failed task line
1265
- 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));
1266
1271
  }
1267
1272
 
1268
1273
  // Track token usage even for failed tasks (partial data still useful for totals)
1269
1274
  if (attemptUsageData.length > 0) {
1270
1275
  const entry = tokenTracker.addTask(task.id, attemptUsageData);
1271
- const taskRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
1272
- logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
1276
+ logger.dim(formatTaskTokenSummary(entry, {
1273
1277
  showCacheTokens: getShowCacheTokens(),
1274
- showRateLimitEstimate: getShowRateLimitEstimate(),
1275
- rateLimitPercentage: taskRateLimitPct,
1276
1278
  }));
1277
1279
  }
1278
1280
 
@@ -1386,11 +1388,8 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1386
1388
  if (trackerEntries.length > 0) {
1387
1389
  logger.newline();
1388
1390
  const totals = tokenTracker.getTotals();
1389
- const totalRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
1390
1391
  logger.dim(formatTokenTotalSummary(totals.usage, totals.cost, {
1391
1392
  showCacheTokens: getShowCacheTokens(),
1392
- showRateLimitEstimate: getShowRateLimitEstimate(),
1393
- rateLimitPercentage: totalRateLimitPct,
1394
1393
  }));
1395
1394
  }
1396
1395
 
@@ -49,6 +49,7 @@ import {
49
49
  removeWorktree,
50
50
  computeWorktreeBaseDir,
51
51
  pullMainBranch,
52
+ resolveWorktreeProjectByIdentifier,
52
53
  } from '../core/worktree.js';
53
54
 
54
55
  interface PlanCommandOptions {
@@ -57,6 +58,7 @@ interface PlanCommandOptions {
57
58
  sonnet?: boolean;
58
59
  auto?: boolean;
59
60
  worktree?: boolean;
61
+ resume?: string;
60
62
  }
61
63
 
62
64
  export function createPlanCommand(): Command {
@@ -71,6 +73,7 @@ export function createPlanCommand(): Command {
71
73
  .option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
72
74
  .option('-y, --auto', "Skip Claude's permission prompts for file operations")
73
75
  .option('-w, --worktree', 'Create a git worktree for isolated planning')
76
+ .option('-r, --resume <identifier>', 'Resume a planning session for an existing project')
74
77
  .action(async (projectName: string | undefined, options: PlanCommandOptions) => {
75
78
  // Validate and resolve model option
76
79
  let model: string;
@@ -84,7 +87,9 @@ export function createPlanCommand(): Command {
84
87
  const autoMode = options.auto ?? false;
85
88
  const worktreeMode = options.worktree ?? getWorktreeDefault();
86
89
 
87
- if (options.amend) {
90
+ if (options.resume) {
91
+ await runResumeCommand(options.resume, model);
92
+ } else if (options.amend) {
88
93
  if (!projectName) {
89
94
  logger.error('--amend requires a project identifier');
90
95
  logger.error('Usage: raf plan <project> --amend');
@@ -676,3 +681,109 @@ ${taskList}
676
681
  # Describe what you want to add below:
677
682
  `;
678
683
  }
684
+
685
+ async function runResumeCommand(identifier: string, model?: string): Promise<void> {
686
+ // Validate environment
687
+ const validation = validateEnvironment();
688
+ reportValidation(validation);
689
+
690
+ if (!validation.valid) {
691
+ process.exit(1);
692
+ }
693
+
694
+ // Try to resolve the project from worktrees first, then fall back to main repo
695
+ const repoBasename = getRepoBasename();
696
+ const rafDir = getRafDir();
697
+
698
+ let projectPath: string | undefined;
699
+ let resumeCwd: string | undefined;
700
+ let folderName: string | undefined;
701
+
702
+ // 1. Try worktree resolution first (if we're in a git repo)
703
+ if (repoBasename) {
704
+ const worktreeResolution = resolveWorktreeProjectByIdentifier(repoBasename, identifier);
705
+
706
+ if (worktreeResolution) {
707
+ // Found in worktree - validate and use it
708
+ const wtValidation = validateWorktree(worktreeResolution.worktreeRoot, '');
709
+
710
+ if (wtValidation.isValidWorktree) {
711
+ folderName = worktreeResolution.folder;
712
+ const repoRoot = getRepoRoot()!;
713
+ const rafRelativePath = path.relative(repoRoot, rafDir);
714
+ projectPath = path.join(worktreeResolution.worktreeRoot, rafRelativePath, folderName);
715
+ resumeCwd = worktreeResolution.worktreeRoot;
716
+ logger.info(`Resuming session in worktree: ${resumeCwd}`);
717
+ } else {
718
+ logger.warn(`Worktree found but invalid: ${worktreeResolution.worktreeRoot}`);
719
+ logger.warn('Falling back to main repo resolution.');
720
+ // Fall through to main repo resolution
721
+ }
722
+ }
723
+ }
724
+
725
+ // 2. If not found in worktree (or invalid), try main repo
726
+ if (!projectPath) {
727
+ const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
728
+
729
+ if (!mainResolution.path) {
730
+ if (mainResolution.error === 'ambiguous' && mainResolution.matches) {
731
+ logger.error(`Ambiguous project name: ${identifier}`);
732
+ logger.error('Multiple projects match:');
733
+ for (const match of mainResolution.matches) {
734
+ logger.error(` - ${match.folder}`);
735
+ }
736
+ logger.error('Please specify the project ID or full folder name.');
737
+ } else {
738
+ logger.error(`Project not found: ${identifier}`);
739
+ }
740
+ process.exit(1);
741
+ }
742
+
743
+ projectPath = mainResolution.path;
744
+ folderName = path.basename(projectPath);
745
+ resumeCwd = projectPath; // Use main repo project path as CWD
746
+ }
747
+
748
+ logger.info(`Project: ${folderName}`);
749
+ if (model) {
750
+ logger.info(`Model: ${model}`);
751
+ }
752
+ logger.newline();
753
+
754
+ // Set up shutdown handler
755
+ const claudeRunner = new ClaudeRunner({ model });
756
+ shutdownHandler.init();
757
+ shutdownHandler.registerClaudeRunner(claudeRunner);
758
+
759
+ // Launch Claude's resume picker
760
+ logger.info('Starting Claude session resume picker...');
761
+ logger.newline();
762
+
763
+ try {
764
+ const exitCode = await claudeRunner.runResume({ cwd: resumeCwd });
765
+
766
+ if (exitCode !== 0) {
767
+ logger.warn(`Claude exited with code ${exitCode}`);
768
+ }
769
+
770
+ // Check for created/updated plan files after resume session
771
+ const plansDir = getPlansDir(projectPath);
772
+ const planFiles = fs.existsSync(plansDir)
773
+ ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
774
+ : [];
775
+
776
+ if (planFiles.length > 0) {
777
+ logger.newline();
778
+ logger.success(`Session complete! Project has ${planFiles.length} plan(s).`);
779
+ logger.newline();
780
+ logger.info('Plans:');
781
+ for (const planFile of planFiles) {
782
+ logger.info(` - plans/${planFile}`);
783
+ }
784
+ }
785
+ } catch (error) {
786
+ logger.error(`Resume session failed: ${error}`);
787
+ throw error;
788
+ }
789
+ }
@@ -364,6 +364,87 @@ export class ClaudeRunner {
364
364
  });
365
365
  }
366
366
 
367
+ /**
368
+ * Resume a Claude planning session using the interactive session picker.
369
+ * Launches `claude --resume` (or `claude -r`) to show available sessions for the CWD.
370
+ * Minimal approach - no system prompt or user message injection.
371
+ *
372
+ * @param options - Runner options (cwd)
373
+ */
374
+ async runResume(options: ClaudeRunnerOptions = {}): Promise<number> {
375
+ const { cwd = process.cwd() } = options;
376
+
377
+ return new Promise((resolve) => {
378
+ const args = ['--resume', '--model', this.model];
379
+
380
+ logger.debug(`Starting Claude session resume picker with model: ${this.model}`);
381
+
382
+ this.activeProcess = pty.spawn(getClaudePath(), args, {
383
+ name: 'xterm-256color',
384
+ cols: process.stdout.columns ?? 80,
385
+ rows: process.stdout.rows ?? 24,
386
+ cwd,
387
+ env: process.env as Record<string, string>,
388
+ });
389
+
390
+ // Set raw mode to pass through all input
391
+ if (process.stdin.isTTY) {
392
+ process.stdin.setRawMode(true);
393
+ }
394
+ process.stdin.resume();
395
+
396
+ // Pipe input to Claude
397
+ const onData = (data: Buffer): void => {
398
+ if (this.activeProcess && !this.killed) {
399
+ this.activeProcess.write(data.toString());
400
+ }
401
+ };
402
+ process.stdin.on('data', onData);
403
+
404
+ // Store disposables for proper cleanup
405
+ const disposables: IDisposable[] = [];
406
+
407
+ // Pipe output to stdout
408
+ disposables.push(this.activeProcess.onData((data) => {
409
+ process.stdout.write(data);
410
+ }));
411
+
412
+ disposables.push(this.activeProcess.onExit(({ exitCode }) => {
413
+ // Cleanup stdin
414
+ process.stdin.off('data', onData);
415
+ if (process.stdin.isTTY) {
416
+ process.stdin.setRawMode(false);
417
+ }
418
+ process.stdin.pause();
419
+
420
+ // Dispose all event listeners to prevent FD leaks
421
+ for (const disposable of disposables) {
422
+ try {
423
+ disposable.dispose();
424
+ } catch {
425
+ // Ignore disposal errors
426
+ }
427
+ }
428
+
429
+ // Ensure PTY is fully cleaned up
430
+ if (this.activeProcess) {
431
+ try {
432
+ this.activeProcess.kill();
433
+ } catch {
434
+ // Ignore - process may already be dead
435
+ }
436
+ this.activeProcess = null;
437
+ }
438
+
439
+ if (this.killed) {
440
+ resolve(130); // SIGINT exit code
441
+ } else {
442
+ resolve(exitCode);
443
+ }
444
+ }));
445
+ });
446
+ }
447
+
367
448
  /**
368
449
  * Run Claude non-interactively and collect output.
369
450
  * Uses stream-json format internally to capture token usage data.
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@ import { Command } from 'commander';
4
4
  import { createPlanCommand } from './commands/plan.js';
5
5
  import { createDoCommand } from './commands/do.js';
6
6
  import { createStatusCommand } from './commands/status.js';
7
- import { createMigrateCommand } from './commands/migrate.js';
8
7
  import { createConfigCommand } from './commands/config.js';
9
8
  import { getVersion } from './utils/version.js';
10
9
 
@@ -18,7 +17,6 @@ program
18
17
  program.addCommand(createPlanCommand());
19
18
  program.addCommand(createDoCommand());
20
19
  program.addCommand(createStatusCommand());
21
- program.addCommand(createMigrateCommand());
22
20
  program.addCommand(createConfigCommand());
23
21
 
24
22
  program.parse();
@@ -23,11 +23,13 @@ export interface StreamEvent {
23
23
  cache_read_input_tokens?: number;
24
24
  cache_creation_input_tokens?: number;
25
25
  };
26
+ total_cost_usd?: number;
26
27
  modelUsage?: Record<string, {
27
28
  inputTokens?: number;
28
29
  outputTokens?: number;
29
30
  cacheReadInputTokens?: number;
30
31
  cacheCreationInputTokens?: number;
32
+ costUSD?: number;
31
33
  }>;
32
34
  tool_use_result?: {
33
35
  type?: string;
@@ -175,6 +177,7 @@ function extractUsageData(event: StreamEvent): UsageData | undefined {
175
177
  outputTokens: data.outputTokens ?? 0,
176
178
  cacheReadInputTokens: data.cacheReadInputTokens ?? 0,
177
179
  cacheCreationInputTokens: data.cacheCreationInputTokens ?? 0,
180
+ costUsd: data.costUSD ?? 0,
178
181
  };
179
182
  }
180
183
  }
@@ -185,5 +188,6 @@ function extractUsageData(event: StreamEvent): UsageData | undefined {
185
188
  cacheReadInputTokens: usage?.cache_read_input_tokens ?? 0,
186
189
  cacheCreationInputTokens: usage?.cache_creation_input_tokens ?? 0,
187
190
  modelUsage,
191
+ totalCostUsd: event.total_cost_usd ?? 0,
188
192
  };
189
193
  }
@@ -200,7 +200,9 @@ This is rarely needed — prefer using the \`effort\` label so the user's config
200
200
 
201
201
  After creating all new plan files:
202
202
  1. Provide a summary of:
203
- - The new tasks you've created
203
+ - The new tasks you've created, including the effort level for each task. Example format:
204
+ - Task 02: add-caching (effort: medium)
205
+ - Task 03: update-docs (effort: low)
204
206
  - How they relate to existing tasks
205
207
  - Total task count in the project
206
208
  2. Display this exit message to the user: