rafcode 2.1.0 → 2.2.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 (130) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/CLAUDE.md +59 -11
  3. package/RAF/ahslfe-config-wizard/decisions.md +34 -0
  4. package/RAF/ahslfe-config-wizard/input.md +1 -0
  5. package/RAF/ahslfe-config-wizard/outcomes/01-define-config-schema.md +38 -0
  6. package/RAF/ahslfe-config-wizard/outcomes/02-refactor-codebase-to-use-config.md +67 -0
  7. package/RAF/ahslfe-config-wizard/outcomes/03-create-config-documentation.md +37 -0
  8. package/RAF/ahslfe-config-wizard/outcomes/04-implement-raf-config-command.md +47 -0
  9. package/RAF/ahslfe-config-wizard/outcomes/05-update-claude-md.md +26 -0
  10. package/RAF/ahslfe-config-wizard/plans/01-define-config-schema.md +73 -0
  11. package/RAF/ahslfe-config-wizard/plans/02-refactor-codebase-to-use-config.md +74 -0
  12. package/RAF/ahslfe-config-wizard/plans/03-create-config-documentation.md +57 -0
  13. package/RAF/ahslfe-config-wizard/plans/04-implement-raf-config-command.md +66 -0
  14. package/RAF/ahslfe-config-wizard/plans/05-update-claude-md.md +60 -0
  15. package/RAF/ahstvo-token-tracker/decisions.md +44 -0
  16. package/RAF/ahstvo-token-tracker/input.md +3 -0
  17. package/RAF/ahstvo-token-tracker/outcomes/01-full-model-id-support.md +43 -0
  18. package/RAF/ahstvo-token-tracker/outcomes/02-name-generation-no-session.md +33 -0
  19. package/RAF/ahstvo-token-tracker/outcomes/03-unify-stream-json-execution.md +48 -0
  20. package/RAF/ahstvo-token-tracker/outcomes/04-token-tracking-cost-calculation.md +53 -0
  21. package/RAF/ahstvo-token-tracker/outcomes/05-token-cost-console-reporting.md +57 -0
  22. package/RAF/ahstvo-token-tracker/outcomes/06-runtime-verbose-toggle.md +53 -0
  23. package/RAF/ahstvo-token-tracker/outcomes/07-readme-config-docs.md +36 -0
  24. package/RAF/ahstvo-token-tracker/plans/01-full-model-id-support.md +35 -0
  25. package/RAF/ahstvo-token-tracker/plans/02-name-generation-no-session.md +36 -0
  26. package/RAF/ahstvo-token-tracker/plans/03-unify-stream-json-execution.md +44 -0
  27. package/RAF/ahstvo-token-tracker/plans/04-token-tracking-cost-calculation.md +56 -0
  28. package/RAF/ahstvo-token-tracker/plans/05-token-cost-console-reporting.md +55 -0
  29. package/RAF/ahstvo-token-tracker/plans/06-runtime-verbose-toggle.md +48 -0
  30. package/RAF/ahstvo-token-tracker/plans/07-readme-config-docs.md +44 -0
  31. package/README.md +34 -0
  32. package/dist/commands/config.d.ts +3 -0
  33. package/dist/commands/config.d.ts.map +1 -0
  34. package/dist/commands/config.js +173 -0
  35. package/dist/commands/config.js.map +1 -0
  36. package/dist/commands/do.d.ts.map +1 -1
  37. package/dist/commands/do.js +50 -28
  38. package/dist/commands/do.js.map +1 -1
  39. package/dist/commands/plan.d.ts.map +1 -1
  40. package/dist/commands/plan.js +3 -2
  41. package/dist/commands/plan.js.map +1 -1
  42. package/dist/core/claude-runner.d.ts +17 -13
  43. package/dist/core/claude-runner.d.ts.map +1 -1
  44. package/dist/core/claude-runner.js +42 -257
  45. package/dist/core/claude-runner.js.map +1 -1
  46. package/dist/core/failure-analyzer.d.ts.map +1 -1
  47. package/dist/core/failure-analyzer.js +6 -3
  48. package/dist/core/failure-analyzer.js.map +1 -1
  49. package/dist/core/git.d.ts.map +1 -1
  50. package/dist/core/git.js +10 -3
  51. package/dist/core/git.js.map +1 -1
  52. package/dist/core/pull-request.d.ts +1 -1
  53. package/dist/core/pull-request.d.ts.map +1 -1
  54. package/dist/core/pull-request.js +7 -4
  55. package/dist/core/pull-request.js.map +1 -1
  56. package/dist/core/shutdown-handler.d.ts.map +1 -1
  57. package/dist/core/shutdown-handler.js +0 -4
  58. package/dist/core/shutdown-handler.js.map +1 -1
  59. package/dist/index.js +2 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/parsers/stream-renderer.d.ts +16 -4
  62. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  63. package/dist/parsers/stream-renderer.js +35 -5
  64. package/dist/parsers/stream-renderer.js.map +1 -1
  65. package/dist/prompts/execution.d.ts.map +1 -1
  66. package/dist/prompts/execution.js +11 -1
  67. package/dist/prompts/execution.js.map +1 -1
  68. package/dist/types/config.d.ts +95 -5
  69. package/dist/types/config.d.ts.map +1 -1
  70. package/dist/types/config.js +63 -3
  71. package/dist/types/config.js.map +1 -1
  72. package/dist/utils/config.d.ts +59 -7
  73. package/dist/utils/config.d.ts.map +1 -1
  74. package/dist/utils/config.js +276 -21
  75. package/dist/utils/config.js.map +1 -1
  76. package/dist/utils/name-generator.d.ts +3 -7
  77. package/dist/utils/name-generator.d.ts.map +1 -1
  78. package/dist/utils/name-generator.js +75 -61
  79. package/dist/utils/name-generator.js.map +1 -1
  80. package/dist/utils/terminal-symbols.d.ts +21 -0
  81. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  82. package/dist/utils/terminal-symbols.js +62 -0
  83. package/dist/utils/terminal-symbols.js.map +1 -1
  84. package/dist/utils/token-tracker.d.ts +45 -0
  85. package/dist/utils/token-tracker.d.ts.map +1 -0
  86. package/dist/utils/token-tracker.js +107 -0
  87. package/dist/utils/token-tracker.js.map +1 -0
  88. package/dist/utils/validation.d.ts +5 -5
  89. package/dist/utils/validation.d.ts.map +1 -1
  90. package/dist/utils/validation.js +10 -6
  91. package/dist/utils/validation.js.map +1 -1
  92. package/dist/utils/verbose-toggle.d.ts +33 -0
  93. package/dist/utils/verbose-toggle.d.ts.map +1 -0
  94. package/dist/utils/verbose-toggle.js +94 -0
  95. package/dist/utils/verbose-toggle.js.map +1 -0
  96. package/package.json +1 -1
  97. package/src/commands/config.ts +204 -0
  98. package/src/commands/do.ts +59 -27
  99. package/src/commands/plan.ts +3 -2
  100. package/src/core/claude-runner.ts +58 -311
  101. package/src/core/failure-analyzer.ts +6 -3
  102. package/src/core/git.ts +10 -3
  103. package/src/core/pull-request.ts +7 -4
  104. package/src/core/shutdown-handler.ts +0 -5
  105. package/src/index.ts +2 -0
  106. package/src/parsers/stream-renderer.ts +55 -8
  107. package/src/prompts/config-docs.md +331 -0
  108. package/src/prompts/execution.ts +13 -1
  109. package/src/types/config.ts +156 -8
  110. package/src/utils/config.ts +335 -21
  111. package/src/utils/name-generator.ts +84 -71
  112. package/src/utils/terminal-symbols.ts +68 -0
  113. package/src/utils/token-tracker.ts +135 -0
  114. package/src/utils/validation.ts +15 -10
  115. package/src/utils/verbose-toggle.ts +103 -0
  116. package/tests/unit/claude-runner.test.ts +216 -403
  117. package/tests/unit/config-command.test.ts +163 -0
  118. package/tests/unit/config.test.ts +608 -30
  119. package/tests/unit/name-generator.test.ts +99 -75
  120. package/tests/unit/pull-request.test.ts +2 -0
  121. package/tests/unit/stream-renderer.test.ts +83 -30
  122. package/tests/unit/terminal-symbols.test.ts +157 -0
  123. package/tests/unit/token-tracker.test.ts +352 -0
  124. package/tests/unit/verbose-toggle.test.ts +204 -0
  125. package/RAF/ahrtxf-session-sentinel/decisions.md +0 -19
  126. package/RAF/ahrtxf-session-sentinel/input.md +0 -1
  127. package/RAF/ahrtxf-session-sentinel/outcomes/01-capture-session-id.md +0 -37
  128. package/RAF/ahrtxf-session-sentinel/outcomes/02-resume-flag.md +0 -45
  129. package/RAF/ahrtxf-session-sentinel/plans/01-capture-session-id.md +0 -41
  130. package/RAF/ahrtxf-session-sentinel/plans/02-resume-flag.md +0 -51
@@ -0,0 +1,204 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as readline from 'node:readline';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { Command } from 'commander';
6
+ import { ClaudeRunner } from '../core/claude-runner.js';
7
+ import { shutdownHandler } from '../core/shutdown-handler.js';
8
+ import { logger } from '../utils/logger.js';
9
+ import {
10
+ getConfigPath,
11
+ getModel,
12
+ getEffort,
13
+ validateConfig,
14
+ ConfigValidationError,
15
+ } from '../utils/config.js';
16
+
17
+ interface ConfigCommandOptions {
18
+ reset?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Load the config documentation markdown from src/prompts/config-docs.md.
23
+ * Resolved relative to this file's location in the dist/ tree.
24
+ */
25
+ function loadConfigDocs(): string {
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+ // From dist/commands/config.js -> ../../src/prompts/config-docs.md
29
+ const docsPath = path.join(__dirname, '..', '..', 'src', 'prompts', 'config-docs.md');
30
+ return fs.readFileSync(docsPath, 'utf-8');
31
+ }
32
+
33
+ /**
34
+ * Read the current user config file contents, or a message indicating no file exists.
35
+ */
36
+ function getCurrentConfigState(configPath: string): string {
37
+ if (!fs.existsSync(configPath)) {
38
+ return 'No config file exists yet. All settings use defaults. The file will be created at: ' + configPath;
39
+ }
40
+ try {
41
+ const content = fs.readFileSync(configPath, 'utf-8');
42
+ return `Current config file (${configPath}):\n\`\`\`json\n${content}\`\`\``;
43
+ } catch {
44
+ return 'Config file exists but could not be read: ' + configPath;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Build the system prompt for the config editing Claude session.
50
+ */
51
+ function buildConfigSystemPrompt(configDocs: string, configState: string): string {
52
+ return [
53
+ 'You are helping the user edit their RAF configuration.',
54
+ 'You have full permission to read and write ~/.raf/raf.config.json.',
55
+ '',
56
+ '# Current Config State',
57
+ configState,
58
+ '',
59
+ '# Config Documentation',
60
+ configDocs,
61
+ ].join('\n');
62
+ }
63
+
64
+ /**
65
+ * Validate the config file after the Claude session ends and report results.
66
+ */
67
+ function postSessionValidation(configPath: string): void {
68
+ if (!fs.existsSync(configPath)) {
69
+ logger.info('No config file exists — using all defaults.');
70
+ return;
71
+ }
72
+
73
+ try {
74
+ const content = fs.readFileSync(configPath, 'utf-8');
75
+ const parsed: unknown = JSON.parse(content);
76
+ validateConfig(parsed);
77
+ logger.success('Config updated successfully.');
78
+
79
+ // Show a summary of what's set
80
+ const userConfig = parsed as Record<string, unknown>;
81
+ const keys = Object.keys(userConfig);
82
+ if (keys.length > 0) {
83
+ logger.info(`Custom settings: ${keys.join(', ')}`);
84
+ }
85
+ } catch (error) {
86
+ if (error instanceof ConfigValidationError) {
87
+ logger.warn(`Config validation warning: ${error.message}`);
88
+ logger.warn('The file was not deleted — you can fix it manually or run `raf config` again.');
89
+ } else if (error instanceof SyntaxError) {
90
+ logger.warn('Config file contains invalid JSON.');
91
+ logger.warn('The file was not deleted — you can fix it manually or run `raf config` again.');
92
+ } else {
93
+ logger.warn(`Could not validate config: ${error}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Prompt the user for Y/N confirmation via readline.
100
+ */
101
+ async function confirm(message: string): Promise<boolean> {
102
+ const rl = readline.createInterface({
103
+ input: process.stdin,
104
+ output: process.stdout,
105
+ });
106
+
107
+ return new Promise((resolve) => {
108
+ rl.question(message, (answer) => {
109
+ rl.close();
110
+ resolve(answer.trim().toLowerCase() === 'y');
111
+ });
112
+ });
113
+ }
114
+
115
+ export function createConfigCommand(): Command {
116
+ const command = new Command('config')
117
+ .description('View and edit RAF configuration with Claude')
118
+ .argument('[prompt...]', 'Optional initial prompt for the config session')
119
+ .option('--reset', 'Delete config file and restore all defaults')
120
+ .action(async (promptParts: string[], options: ConfigCommandOptions) => {
121
+ if (options.reset) {
122
+ await handleReset();
123
+ return;
124
+ }
125
+
126
+ const initialPrompt = promptParts.length > 0 ? promptParts.join(' ') : undefined;
127
+ await runConfigSession(initialPrompt);
128
+ });
129
+
130
+ return command;
131
+ }
132
+
133
+ async function handleReset(): Promise<void> {
134
+ const configPath = getConfigPath();
135
+
136
+ if (!fs.existsSync(configPath)) {
137
+ logger.info('No config file exists — already using defaults.');
138
+ return;
139
+ }
140
+
141
+ const confirmed = await confirm(
142
+ 'This will delete ~/.raf/raf.config.json and restore all defaults. Continue? [y/N] '
143
+ );
144
+
145
+ if (!confirmed) {
146
+ logger.info('Cancelled.');
147
+ return;
148
+ }
149
+
150
+ fs.unlinkSync(configPath);
151
+ logger.success('Config file deleted. All settings restored to defaults.');
152
+ }
153
+
154
+ async function runConfigSession(initialPrompt?: string): Promise<void> {
155
+ const configPath = getConfigPath();
156
+ const model = getModel('config');
157
+ const effort = getEffort('config');
158
+
159
+ // Set effort level env var for the Claude session
160
+ process.env['CLAUDE_CODE_EFFORT_LEVEL'] = effort;
161
+
162
+ // Load config docs
163
+ let configDocs: string;
164
+ try {
165
+ configDocs = loadConfigDocs();
166
+ } catch (error) {
167
+ logger.error(`Failed to load config documentation: ${error}`);
168
+ process.exit(1);
169
+ }
170
+
171
+ // Build system prompt
172
+ const configState = getCurrentConfigState(configPath);
173
+ const systemPrompt = buildConfigSystemPrompt(configDocs, configState);
174
+
175
+ // Build user message
176
+ const userMessage = initialPrompt
177
+ ?? 'Show me my current config and help me make changes.';
178
+
179
+ // Set up Claude runner
180
+ const claudeRunner = new ClaudeRunner({ model });
181
+ shutdownHandler.init();
182
+ shutdownHandler.registerClaudeRunner(claudeRunner);
183
+
184
+ logger.info('Starting config session with Claude...');
185
+ logger.info(`Using model: ${model}`);
186
+ logger.newline();
187
+
188
+ try {
189
+ const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
190
+ dangerouslySkipPermissions: true,
191
+ });
192
+
193
+ if (exitCode !== 0) {
194
+ logger.warn(`Claude exited with code ${exitCode}`);
195
+ }
196
+
197
+ // Post-session validation
198
+ logger.newline();
199
+ postSessionValidation(configPath);
200
+ } catch (error) {
201
+ logger.error(`Config session failed: ${error}`);
202
+ throw error;
203
+ }
204
+ }
@@ -13,14 +13,18 @@ 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 } from '../utils/config.js';
16
+ import { getConfig, getEffort, getWorktreeDefault } from '../utils/config.js';
17
17
  import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
18
18
  import { createStatusLine } from '../utils/status-line.js';
19
19
  import {
20
20
  formatProjectHeader,
21
21
  formatSummary,
22
22
  formatTaskProgress,
23
+ formatTaskTokenSummary,
24
+ formatTokenTotalSummary,
23
25
  } from '../utils/terminal-symbols.js';
26
+ import { TokenTracker } from '../utils/token-tracker.js';
27
+ import { VerboseToggle } from '../utils/verbose-toggle.js';
24
28
  import {
25
29
  deriveProjectState,
26
30
  discoverProjects,
@@ -124,7 +128,6 @@ export function createDoCommand(): Command {
124
128
  .option('-m, --model <name>', 'Claude model to use (sonnet, haiku, opus)')
125
129
  .option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
126
130
  .option('-w, --worktree', 'Execute tasks in a git worktree')
127
- .option('-r, --resume <session-id>', 'Resume an interrupted Claude session')
128
131
  .action(async (project: string | undefined, options: DoCommandOptions) => {
129
132
  await runDoCommand(project, options);
130
133
  });
@@ -135,12 +138,12 @@ export function createDoCommand(): Command {
135
138
  async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
136
139
  const rafDir = getRafDir();
137
140
  let projectIdentifier = projectIdentifierArg;
138
- let worktreeMode = options.worktree ?? false;
141
+ let worktreeMode = options.worktree ?? getWorktreeDefault();
139
142
 
140
143
  // Validate and resolve model option
141
144
  let model: string;
142
145
  try {
143
- model = resolveModelOption(options.model as string | undefined, options.sonnet);
146
+ model = resolveModelOption(options.model as string | undefined, options.sonnet, 'execute');
144
147
  } catch (error) {
145
148
  logger.error((error as Error).message);
146
149
  process.exit(1);
@@ -357,7 +360,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
357
360
  const verbose = options.verbose ?? false;
358
361
  const debug = options.debug ?? false;
359
362
  const force = options.force ?? false;
360
- const resumeSessionId = options.resume;
361
363
  const maxRetries = config.maxRetries;
362
364
  const autoCommit = config.autoCommit;
363
365
 
@@ -395,7 +397,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
395
397
  showModel: true,
396
398
  model,
397
399
  worktreeCwd: worktreeRoot,
398
- resumeSessionId,
399
400
  }
400
401
  );
401
402
  } catch (error) {
@@ -661,8 +662,6 @@ interface SingleProjectOptions {
661
662
  model: string;
662
663
  /** Worktree root directory. When set, Claude runs with cwd in the worktree. */
663
664
  worktreeCwd?: string;
664
- /** Session ID to resume an interrupted Claude session. Only applies to the first pending task. */
665
- resumeSessionId?: string;
666
665
  }
667
666
 
668
667
  async function executeSingleProject(
@@ -670,8 +669,7 @@ async function executeSingleProject(
670
669
  projectName: string,
671
670
  options: SingleProjectOptions
672
671
  ): Promise<ProjectExecutionResult> {
673
- const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model, worktreeCwd, resumeSessionId } = options;
674
- let activeResumeSessionId = resumeSessionId;
672
+ const { timeout, verbose, debug, force, maxRetries, autoCommit, showModel, model, worktreeCwd } = options;
675
673
 
676
674
  if (!validatePlansExist(projectPath)) {
677
675
  return {
@@ -717,6 +715,13 @@ async function executeSingleProject(
717
715
  shutdownHandler.init();
718
716
  shutdownHandler.registerClaudeRunner(claudeRunner);
719
717
 
718
+ // Initialize token tracker for usage reporting
719
+ const tokenTracker = new TokenTracker();
720
+
721
+ // Set up runtime verbose toggle (Tab key to toggle during execution)
722
+ const verboseToggle = new VerboseToggle(verbose);
723
+ shutdownHandler.onShutdown(() => verboseToggle.stop());
724
+
720
725
  // Start project timer
721
726
  const projectStartTime = Date.now();
722
727
 
@@ -822,6 +827,9 @@ async function executeSingleProject(
822
827
  return lines.join('\n');
823
828
  }
824
829
 
830
+ // Start verbose toggle listener (Tab key)
831
+ verboseToggle.start();
832
+
825
833
  let task = getNextTaskToProcess(state);
826
834
 
827
835
  while (task) {
@@ -868,17 +876,13 @@ async function executeSingleProject(
868
876
  logger.setContext(taskContext);
869
877
 
870
878
  // Log task execution status
871
- if (activeResumeSessionId) {
872
- logger.info(`Resuming task ${taskLabel} with session ${activeResumeSessionId}`);
873
- } else if (task.status === 'failed') {
879
+ if (task.status === 'failed') {
874
880
  logger.info(`Retrying task ${taskLabel} (previously failed)...`);
875
881
  } else if (task.status === 'completed' && force) {
876
882
  logger.info(`Re-running task ${taskLabel} (force mode)...`);
877
883
  } else {
878
884
  logger.info(`Executing task ${taskLabel}...`);
879
885
  }
880
- } else if (activeResumeSessionId) {
881
- logger.info(`Resuming task ${task.id} with session ${activeResumeSessionId}`);
882
886
  }
883
887
 
884
888
  // Get previous outcomes for context
@@ -901,6 +905,7 @@ async function executeSingleProject(
901
905
  let attempts = 0;
902
906
  let lastOutput = '';
903
907
  let failureReason = '';
908
+ let lastUsageData: import('../types/config.js').UsageData | undefined;
904
909
  // Track failure history for each attempt (attempt number -> reason)
905
910
  const failureHistory: Array<{ attempt: number; reason: string }> = [];
906
911
 
@@ -950,17 +955,23 @@ async function executeSingleProject(
950
955
  } : undefined;
951
956
 
952
957
  // Run Claude (use worktree root as cwd if in worktree mode)
953
- let result;
954
- if (activeResumeSessionId && attempts === 1) {
955
- // Resume mode: use --resume flag instead of normal prompt execution
956
- result = await claudeRunner.runResume(activeResumeSessionId, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' });
957
- } else {
958
- result = verbose
959
- ? await claudeRunner.runVerbose(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' })
960
- : await claudeRunner.run(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' });
961
- }
958
+ const executeEffort = getEffort('execute');
959
+ const runnerOptions = {
960
+ timeout,
961
+ outcomeFilePath,
962
+ commitContext,
963
+ cwd: worktreeCwd,
964
+ effortLevel: executeEffort,
965
+ verboseCheck: () => verboseToggle.isVerbose,
966
+ };
967
+ const result = verbose
968
+ ? await claudeRunner.runVerbose(prompt, runnerOptions)
969
+ : await claudeRunner.run(prompt, runnerOptions);
962
970
 
963
971
  lastOutput = result.output;
972
+ if (result.usageData) {
973
+ lastUsageData = result.usageData;
974
+ }
964
975
 
965
976
  // Parse result
966
977
  const parsed = parseOutput(result.output);
@@ -1075,6 +1086,13 @@ Task completed. No detailed report provided.
1075
1086
  // Minimal mode: show completed task line
1076
1087
  logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id));
1077
1088
  }
1089
+
1090
+ // Track and display token usage for this task
1091
+ if (lastUsageData) {
1092
+ const entry = tokenTracker.addTask(task.id, lastUsageData);
1093
+ logger.dim(formatTaskTokenSummary(entry.usage, entry.cost));
1094
+ }
1095
+
1078
1096
  completedInSession.add(task.id);
1079
1097
  } else {
1080
1098
  // Stash any uncommitted changes on complete failure
@@ -1096,6 +1114,12 @@ Task completed. No detailed report provided.
1096
1114
  logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id));
1097
1115
  }
1098
1116
 
1117
+ // Track token usage even for failed tasks (partial data still useful for totals)
1118
+ if (lastUsageData) {
1119
+ const entry = tokenTracker.addTask(task.id, lastUsageData);
1120
+ logger.dim(formatTaskTokenSummary(entry.usage, entry.cost));
1121
+ }
1122
+
1099
1123
  // Analyze failure and generate structured report
1100
1124
  const analysisReport = await analyzeFailure(lastOutput, failureReason, task.id);
1101
1125
 
@@ -1120,9 +1144,6 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1120
1144
  logger.newline();
1121
1145
  }
1122
1146
 
1123
- // Clear resume flag after first task — subsequent tasks use normal execution
1124
- activeResumeSessionId = undefined;
1125
-
1126
1147
  // Clear context before next task
1127
1148
  logger.clearContext();
1128
1149
 
@@ -1133,6 +1154,9 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1133
1154
  task = getNextTaskToProcess(state);
1134
1155
  }
1135
1156
 
1157
+ // Stop verbose toggle listener before summary output
1158
+ verboseToggle.stop();
1159
+
1136
1160
  // Ensure context is cleared for summary
1137
1161
  logger.clearContext();
1138
1162
 
@@ -1201,6 +1225,14 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1201
1225
  }
1202
1226
  }
1203
1227
 
1228
+ // Show token usage summary if any tasks reported usage data
1229
+ const trackerEntries = tokenTracker.getEntries();
1230
+ if (trackerEntries.length > 0) {
1231
+ logger.newline();
1232
+ const totals = tokenTracker.getTotals();
1233
+ logger.dim(formatTokenTotalSummary(totals.usage, totals.cost));
1234
+ }
1235
+
1204
1236
  // Show retry history for tasks that had failures (even if eventually successful)
1205
1237
  if (projectRetryHistory.length > 0) {
1206
1238
  logger.newline();
@@ -15,6 +15,7 @@ import {
15
15
  resolveModelOption,
16
16
  } from '../utils/validation.js';
17
17
  import { logger } from '../utils/logger.js';
18
+ import { getWorktreeDefault } from '../utils/config.js';
18
19
  import { generateProjectNames } from '../utils/name-generator.js';
19
20
  import { pickProjectName } from '../ui/name-picker.js';
20
21
  import {
@@ -73,14 +74,14 @@ export function createPlanCommand(): Command {
73
74
  // Validate and resolve model option
74
75
  let model: string;
75
76
  try {
76
- model = resolveModelOption(options.model, options.sonnet);
77
+ model = resolveModelOption(options.model, options.sonnet, 'plan');
77
78
  } catch (error) {
78
79
  logger.error((error as Error).message);
79
80
  process.exit(1);
80
81
  }
81
82
 
82
83
  const autoMode = options.auto ?? false;
83
- const worktreeMode = options.worktree ?? false;
84
+ const worktreeMode = options.worktree ?? getWorktreeDefault();
84
85
 
85
86
  if (options.amend) {
86
87
  if (!projectName) {