rafcode 2.1.1 → 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 (120) 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 +47 -6
  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 +19 -2
  43. package/dist/core/claude-runner.d.ts.map +1 -1
  44. package/dist/core/claude-runner.js +43 -96
  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/index.js +2 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/parsers/stream-renderer.d.ts +16 -1
  59. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  60. package/dist/parsers/stream-renderer.js +34 -4
  61. package/dist/parsers/stream-renderer.js.map +1 -1
  62. package/dist/prompts/execution.d.ts.map +1 -1
  63. package/dist/prompts/execution.js +11 -1
  64. package/dist/prompts/execution.js.map +1 -1
  65. package/dist/types/config.d.ts +95 -4
  66. package/dist/types/config.d.ts.map +1 -1
  67. package/dist/types/config.js +63 -3
  68. package/dist/types/config.js.map +1 -1
  69. package/dist/utils/config.d.ts +59 -7
  70. package/dist/utils/config.d.ts.map +1 -1
  71. package/dist/utils/config.js +276 -21
  72. package/dist/utils/config.js.map +1 -1
  73. package/dist/utils/name-generator.d.ts +3 -7
  74. package/dist/utils/name-generator.d.ts.map +1 -1
  75. package/dist/utils/name-generator.js +75 -61
  76. package/dist/utils/name-generator.js.map +1 -1
  77. package/dist/utils/terminal-symbols.d.ts +21 -0
  78. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  79. package/dist/utils/terminal-symbols.js +62 -0
  80. package/dist/utils/terminal-symbols.js.map +1 -1
  81. package/dist/utils/token-tracker.d.ts +45 -0
  82. package/dist/utils/token-tracker.d.ts.map +1 -0
  83. package/dist/utils/token-tracker.js +107 -0
  84. package/dist/utils/token-tracker.js.map +1 -0
  85. package/dist/utils/validation.d.ts +5 -5
  86. package/dist/utils/validation.d.ts.map +1 -1
  87. package/dist/utils/validation.js +10 -6
  88. package/dist/utils/validation.js.map +1 -1
  89. package/dist/utils/verbose-toggle.d.ts +33 -0
  90. package/dist/utils/verbose-toggle.d.ts.map +1 -0
  91. package/dist/utils/verbose-toggle.js +94 -0
  92. package/dist/utils/verbose-toggle.js.map +1 -0
  93. package/package.json +1 -1
  94. package/src/commands/config.ts +204 -0
  95. package/src/commands/do.ts +56 -5
  96. package/src/commands/plan.ts +3 -2
  97. package/src/core/claude-runner.ts +59 -115
  98. package/src/core/failure-analyzer.ts +6 -3
  99. package/src/core/git.ts +10 -3
  100. package/src/core/pull-request.ts +7 -4
  101. package/src/index.ts +2 -0
  102. package/src/parsers/stream-renderer.ts +54 -4
  103. package/src/prompts/config-docs.md +331 -0
  104. package/src/prompts/execution.ts +13 -1
  105. package/src/types/config.ts +156 -7
  106. package/src/utils/config.ts +335 -21
  107. package/src/utils/name-generator.ts +84 -71
  108. package/src/utils/terminal-symbols.ts +68 -0
  109. package/src/utils/token-tracker.ts +135 -0
  110. package/src/utils/validation.ts +15 -10
  111. package/src/utils/verbose-toggle.ts +103 -0
  112. package/tests/unit/claude-runner.test.ts +171 -7
  113. package/tests/unit/config-command.test.ts +163 -0
  114. package/tests/unit/config.test.ts +608 -30
  115. package/tests/unit/name-generator.test.ts +99 -75
  116. package/tests/unit/pull-request.test.ts +2 -0
  117. package/tests/unit/stream-renderer.test.ts +83 -0
  118. package/tests/unit/terminal-symbols.test.ts +157 -0
  119. package/tests/unit/token-tracker.test.ts +352 -0
  120. package/tests/unit/verbose-toggle.test.ts +204 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Runtime verbose toggle for task execution.
3
+ *
4
+ * Listens for Tab keypress on process.stdin to toggle verbose display on/off.
5
+ * When verbose is on, tool-use activity lines from stream-json are displayed.
6
+ * When verbose is off, they are suppressed (but data is still captured).
7
+ *
8
+ * Requires a TTY stdin. Silently skips setup when stdin is not a TTY (e.g., piped input).
9
+ */
10
+ export declare class VerboseToggle {
11
+ private _verbose;
12
+ private _active;
13
+ private _dataHandler;
14
+ constructor(initialVerbose: boolean);
15
+ /** Current verbose display state. */
16
+ get isVerbose(): boolean;
17
+ /** Whether the toggle listener is currently active. */
18
+ get isActive(): boolean;
19
+ /**
20
+ * Start listening for Tab keypress on stdin.
21
+ * Sets stdin to raw mode to capture individual keypresses.
22
+ * Shows a hint message about the toggle.
23
+ *
24
+ * No-op if stdin is not a TTY or if already active.
25
+ */
26
+ start(): void;
27
+ /**
28
+ * Stop listening and restore stdin to normal mode.
29
+ * Safe to call multiple times.
30
+ */
31
+ stop(): void;
32
+ }
33
+ //# sourceMappingURL=verbose-toggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verbose-toggle.d.ts","sourceRoot":"","sources":["../../src/utils/verbose-toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAyC;gBAEjD,cAAc,EAAE,OAAO;IAInC,qCAAqC;IACrC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,uDAAuD;IACvD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;IAoCb;;;OAGG;IACH,IAAI,IAAI,IAAI;CAwBb"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Runtime verbose toggle for task execution.
3
+ *
4
+ * Listens for Tab keypress on process.stdin to toggle verbose display on/off.
5
+ * When verbose is on, tool-use activity lines from stream-json are displayed.
6
+ * When verbose is off, they are suppressed (but data is still captured).
7
+ *
8
+ * Requires a TTY stdin. Silently skips setup when stdin is not a TTY (e.g., piped input).
9
+ */
10
+ import { logger } from './logger.js';
11
+ export class VerboseToggle {
12
+ _verbose;
13
+ _active = false;
14
+ _dataHandler = null;
15
+ constructor(initialVerbose) {
16
+ this._verbose = initialVerbose;
17
+ }
18
+ /** Current verbose display state. */
19
+ get isVerbose() {
20
+ return this._verbose;
21
+ }
22
+ /** Whether the toggle listener is currently active. */
23
+ get isActive() {
24
+ return this._active;
25
+ }
26
+ /**
27
+ * Start listening for Tab keypress on stdin.
28
+ * Sets stdin to raw mode to capture individual keypresses.
29
+ * Shows a hint message about the toggle.
30
+ *
31
+ * No-op if stdin is not a TTY or if already active.
32
+ */
33
+ start() {
34
+ if (this._active)
35
+ return;
36
+ if (!process.stdin.isTTY)
37
+ return;
38
+ try {
39
+ process.stdin.setRawMode(true);
40
+ }
41
+ catch {
42
+ // Cannot set raw mode — skip
43
+ return;
44
+ }
45
+ process.stdin.resume();
46
+ this._dataHandler = (data) => {
47
+ for (let i = 0; i < data.length; i++) {
48
+ const byte = data[i];
49
+ if (byte === 0x09) {
50
+ // Tab key
51
+ this._verbose = !this._verbose;
52
+ const state = this._verbose ? 'on' : 'off';
53
+ logger.dim(` [verbose: ${state}]`);
54
+ }
55
+ else if (byte === 0x03) {
56
+ // Ctrl+C — re-emit SIGINT so the shutdown handler catches it
57
+ process.emit('SIGINT');
58
+ }
59
+ }
60
+ };
61
+ process.stdin.on('data', this._dataHandler);
62
+ this._active = true;
63
+ // Show toggle hint
64
+ logger.dim(' Press Tab to toggle verbose mode');
65
+ }
66
+ /**
67
+ * Stop listening and restore stdin to normal mode.
68
+ * Safe to call multiple times.
69
+ */
70
+ stop() {
71
+ if (!this._active)
72
+ return;
73
+ if (this._dataHandler) {
74
+ process.stdin.off('data', this._dataHandler);
75
+ this._dataHandler = null;
76
+ }
77
+ try {
78
+ if (process.stdin.isTTY) {
79
+ process.stdin.setRawMode(false);
80
+ }
81
+ }
82
+ catch {
83
+ // Ignore — stdin may already be closed
84
+ }
85
+ try {
86
+ process.stdin.pause();
87
+ }
88
+ catch {
89
+ // Ignore
90
+ }
91
+ this._active = false;
92
+ }
93
+ }
94
+ //# sourceMappingURL=verbose-toggle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verbose-toggle.js","sourceRoot":"","sources":["../../src/utils/verbose-toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,OAAO,aAAa;IAChB,QAAQ,CAAU;IAClB,OAAO,GAAG,KAAK,CAAC;IAChB,YAAY,GAAoC,IAAI,CAAC;IAE7D,YAAY,cAAuB;QACjC,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC;IACjC,CAAC;IAED,qCAAqC;IACrC,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,uDAAuD;IACvD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO;QAEjC,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;YAC7B,OAAO;QACT,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAEvB,IAAI,CAAC,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAErB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAClB,UAAU;oBACV,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;oBAC3C,MAAM,CAAC,GAAG,CAAC,eAAe,KAAK,GAAG,CAAC,CAAC;gBACtC,CAAC;qBAAM,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBACzB,6DAA6D;oBAC7D,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEpB,mBAAmB;QACnB,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IACnD,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACxB,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;QAED,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rafcode",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Automated Task Planning & Execution with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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,
@@ -134,12 +138,12 @@ export function createDoCommand(): Command {
134
138
  async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
135
139
  const rafDir = getRafDir();
136
140
  let projectIdentifier = projectIdentifierArg;
137
- let worktreeMode = options.worktree ?? false;
141
+ let worktreeMode = options.worktree ?? getWorktreeDefault();
138
142
 
139
143
  // Validate and resolve model option
140
144
  let model: string;
141
145
  try {
142
- model = resolveModelOption(options.model as string | undefined, options.sonnet);
146
+ model = resolveModelOption(options.model as string | undefined, options.sonnet, 'execute');
143
147
  } catch (error) {
144
148
  logger.error((error as Error).message);
145
149
  process.exit(1);
@@ -711,6 +715,13 @@ async function executeSingleProject(
711
715
  shutdownHandler.init();
712
716
  shutdownHandler.registerClaudeRunner(claudeRunner);
713
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
+
714
725
  // Start project timer
715
726
  const projectStartTime = Date.now();
716
727
 
@@ -816,6 +827,9 @@ async function executeSingleProject(
816
827
  return lines.join('\n');
817
828
  }
818
829
 
830
+ // Start verbose toggle listener (Tab key)
831
+ verboseToggle.start();
832
+
819
833
  let task = getNextTaskToProcess(state);
820
834
 
821
835
  while (task) {
@@ -891,6 +905,7 @@ async function executeSingleProject(
891
905
  let attempts = 0;
892
906
  let lastOutput = '';
893
907
  let failureReason = '';
908
+ let lastUsageData: import('../types/config.js').UsageData | undefined;
894
909
  // Track failure history for each attempt (attempt number -> reason)
895
910
  const failureHistory: Array<{ attempt: number; reason: string }> = [];
896
911
 
@@ -940,11 +955,23 @@ async function executeSingleProject(
940
955
  } : undefined;
941
956
 
942
957
  // Run Claude (use worktree root as cwd if in worktree mode)
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
+ };
943
967
  const result = verbose
944
- ? await claudeRunner.runVerbose(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' })
945
- : await claudeRunner.run(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' });
968
+ ? await claudeRunner.runVerbose(prompt, runnerOptions)
969
+ : await claudeRunner.run(prompt, runnerOptions);
946
970
 
947
971
  lastOutput = result.output;
972
+ if (result.usageData) {
973
+ lastUsageData = result.usageData;
974
+ }
948
975
 
949
976
  // Parse result
950
977
  const parsed = parseOutput(result.output);
@@ -1059,6 +1086,13 @@ Task completed. No detailed report provided.
1059
1086
  // Minimal mode: show completed task line
1060
1087
  logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id));
1061
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
+
1062
1096
  completedInSession.add(task.id);
1063
1097
  } else {
1064
1098
  // Stash any uncommitted changes on complete failure
@@ -1080,6 +1114,12 @@ Task completed. No detailed report provided.
1080
1114
  logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id));
1081
1115
  }
1082
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
+
1083
1123
  // Analyze failure and generate structured report
1084
1124
  const analysisReport = await analyzeFailure(lastOutput, failureReason, task.id);
1085
1125
 
@@ -1114,6 +1154,9 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1114
1154
  task = getNextTaskToProcess(state);
1115
1155
  }
1116
1156
 
1157
+ // Stop verbose toggle listener before summary output
1158
+ verboseToggle.stop();
1159
+
1117
1160
  // Ensure context is cleared for summary
1118
1161
  logger.clearContext();
1119
1162
 
@@ -1182,6 +1225,14 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1182
1225
  }
1183
1226
  }
1184
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
+
1185
1236
  // Show retry history for tasks that had failures (even if eventually successful)
1186
1237
  if (projectRetryHistory.length > 0) {
1187
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) {