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.
- package/.claude/settings.local.json +4 -1
- package/CLAUDE.md +59 -11
- package/RAF/ahslfe-config-wizard/decisions.md +34 -0
- package/RAF/ahslfe-config-wizard/input.md +1 -0
- package/RAF/ahslfe-config-wizard/outcomes/01-define-config-schema.md +38 -0
- package/RAF/ahslfe-config-wizard/outcomes/02-refactor-codebase-to-use-config.md +67 -0
- package/RAF/ahslfe-config-wizard/outcomes/03-create-config-documentation.md +37 -0
- package/RAF/ahslfe-config-wizard/outcomes/04-implement-raf-config-command.md +47 -0
- package/RAF/ahslfe-config-wizard/outcomes/05-update-claude-md.md +26 -0
- package/RAF/ahslfe-config-wizard/plans/01-define-config-schema.md +73 -0
- package/RAF/ahslfe-config-wizard/plans/02-refactor-codebase-to-use-config.md +74 -0
- package/RAF/ahslfe-config-wizard/plans/03-create-config-documentation.md +57 -0
- package/RAF/ahslfe-config-wizard/plans/04-implement-raf-config-command.md +66 -0
- package/RAF/ahslfe-config-wizard/plans/05-update-claude-md.md +60 -0
- package/RAF/ahstvo-token-tracker/decisions.md +44 -0
- package/RAF/ahstvo-token-tracker/input.md +3 -0
- package/RAF/ahstvo-token-tracker/outcomes/01-full-model-id-support.md +43 -0
- package/RAF/ahstvo-token-tracker/outcomes/02-name-generation-no-session.md +33 -0
- package/RAF/ahstvo-token-tracker/outcomes/03-unify-stream-json-execution.md +48 -0
- package/RAF/ahstvo-token-tracker/outcomes/04-token-tracking-cost-calculation.md +53 -0
- package/RAF/ahstvo-token-tracker/outcomes/05-token-cost-console-reporting.md +57 -0
- package/RAF/ahstvo-token-tracker/outcomes/06-runtime-verbose-toggle.md +53 -0
- package/RAF/ahstvo-token-tracker/outcomes/07-readme-config-docs.md +36 -0
- package/RAF/ahstvo-token-tracker/plans/01-full-model-id-support.md +35 -0
- package/RAF/ahstvo-token-tracker/plans/02-name-generation-no-session.md +36 -0
- package/RAF/ahstvo-token-tracker/plans/03-unify-stream-json-execution.md +44 -0
- package/RAF/ahstvo-token-tracker/plans/04-token-tracking-cost-calculation.md +56 -0
- package/RAF/ahstvo-token-tracker/plans/05-token-cost-console-reporting.md +55 -0
- package/RAF/ahstvo-token-tracker/plans/06-runtime-verbose-toggle.md +48 -0
- package/RAF/ahstvo-token-tracker/plans/07-readme-config-docs.md +44 -0
- package/README.md +34 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +173 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +50 -28
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +3 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +17 -13
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +42 -257
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +6 -3
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +10 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/pull-request.d.ts +1 -1
- package/dist/core/pull-request.d.ts.map +1 -1
- package/dist/core/pull-request.js +7 -4
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/shutdown-handler.d.ts.map +1 -1
- package/dist/core/shutdown-handler.js +0 -4
- package/dist/core/shutdown-handler.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +16 -4
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +35 -5
- package/dist/parsers/stream-renderer.js.map +1 -1
- package/dist/prompts/execution.d.ts.map +1 -1
- package/dist/prompts/execution.js +11 -1
- package/dist/prompts/execution.js.map +1 -1
- package/dist/types/config.d.ts +95 -5
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +63 -3
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +59 -7
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +276 -21
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/name-generator.d.ts +3 -7
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +75 -61
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +21 -0
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +62 -0
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +45 -0
- package/dist/utils/token-tracker.d.ts.map +1 -0
- package/dist/utils/token-tracker.js +107 -0
- package/dist/utils/token-tracker.js.map +1 -0
- package/dist/utils/validation.d.ts +5 -5
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +10 -6
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/verbose-toggle.d.ts +33 -0
- package/dist/utils/verbose-toggle.d.ts.map +1 -0
- package/dist/utils/verbose-toggle.js +94 -0
- package/dist/utils/verbose-toggle.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/config.ts +204 -0
- package/src/commands/do.ts +59 -27
- package/src/commands/plan.ts +3 -2
- package/src/core/claude-runner.ts +58 -311
- package/src/core/failure-analyzer.ts +6 -3
- package/src/core/git.ts +10 -3
- package/src/core/pull-request.ts +7 -4
- package/src/core/shutdown-handler.ts +0 -5
- package/src/index.ts +2 -0
- package/src/parsers/stream-renderer.ts +55 -8
- package/src/prompts/config-docs.md +331 -0
- package/src/prompts/execution.ts +13 -1
- package/src/types/config.ts +156 -8
- package/src/utils/config.ts +335 -21
- package/src/utils/name-generator.ts +84 -71
- package/src/utils/terminal-symbols.ts +68 -0
- package/src/utils/token-tracker.ts +135 -0
- package/src/utils/validation.ts +15 -10
- package/src/utils/verbose-toggle.ts +103 -0
- package/tests/unit/claude-runner.test.ts +216 -403
- package/tests/unit/config-command.test.ts +163 -0
- package/tests/unit/config.test.ts +608 -30
- package/tests/unit/name-generator.test.ts +99 -75
- package/tests/unit/pull-request.test.ts +2 -0
- package/tests/unit/stream-renderer.test.ts +83 -30
- package/tests/unit/terminal-symbols.test.ts +157 -0
- package/tests/unit/token-tracker.test.ts +352 -0
- package/tests/unit/verbose-toggle.test.ts +204 -0
- package/RAF/ahrtxf-session-sentinel/decisions.md +0 -19
- package/RAF/ahrtxf-session-sentinel/input.md +0 -1
- package/RAF/ahrtxf-session-sentinel/outcomes/01-capture-session-id.md +0 -37
- package/RAF/ahrtxf-session-sentinel/outcomes/02-resume-flag.md +0 -45
- package/RAF/ahrtxf-session-sentinel/plans/01-capture-session-id.md +0 -41
- 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
|
+
}
|
package/src/commands/do.ts
CHANGED
|
@@ -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 ??
|
|
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
|
|
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 (
|
|
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
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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();
|
package/src/commands/plan.ts
CHANGED
|
@@ -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 ??
|
|
84
|
+
const worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
84
85
|
|
|
85
86
|
if (options.amend) {
|
|
86
87
|
if (!projectName) {
|