rafcode 3.2.1 → 3.8.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 +3 -1
- package/CLAUDE.md +0 -1
- package/RAF/41-echo-chamber/decisions.md +13 -0
- package/RAF/41-echo-chamber/input.md +4 -0
- package/RAF/41-echo-chamber/outcomes/1-update-codex-model-defaults.md +24 -0
- package/RAF/41-echo-chamber/outcomes/2-e2e-test-codex-provider.md +74 -0
- package/RAF/41-echo-chamber/plans/1-update-codex-model-defaults.md +28 -0
- package/RAF/41-echo-chamber/plans/2-e2e-test-codex-provider.md +103 -0
- package/RAF/42-patch-parade/decisions.md +29 -0
- package/RAF/42-patch-parade/input.md +9 -0
- package/RAF/42-patch-parade/outcomes/1-fix-codex-model-resolution.md +36 -0
- package/RAF/42-patch-parade/outcomes/2-fix-provider-aware-name-generation.md +31 -0
- package/RAF/42-patch-parade/outcomes/3-fix-codex-error-event-rendering.md +32 -0
- package/RAF/42-patch-parade/outcomes/4-update-cli-help-docs.md +28 -0
- package/RAF/42-patch-parade/outcomes/5-update-default-codex-models-to-gpt-5-4.md +33 -0
- package/RAF/42-patch-parade/outcomes/6-unify-model-config-schema.md +89 -0
- package/RAF/42-patch-parade/plans/1-fix-codex-model-resolution.md +35 -0
- package/RAF/42-patch-parade/plans/2-fix-provider-aware-name-generation.md +38 -0
- package/RAF/42-patch-parade/plans/3-fix-codex-error-event-rendering.md +32 -0
- package/RAF/42-patch-parade/plans/4-update-cli-help-docs.md +31 -0
- package/RAF/42-patch-parade/plans/5-update-default-codex-models-to-gpt-5-4.md +35 -0
- package/RAF/42-patch-parade/plans/6-unify-model-config-schema.md +46 -0
- package/RAF/43-swiss-army/decisions.md +34 -0
- package/RAF/43-swiss-army/input.md +7 -0
- package/RAF/43-swiss-army/outcomes/1-fix-model-validation.md +21 -0
- package/RAF/43-swiss-army/outcomes/2-update-commit-format.md +31 -0
- package/RAF/43-swiss-army/outcomes/3-wire-reasoning-effort.md +28 -0
- package/RAF/43-swiss-army/outcomes/4-remove-provider-flag.md +27 -0
- package/RAF/43-swiss-army/outcomes/5-config-wizard-validation.md +23 -0
- package/RAF/43-swiss-army/outcomes/6-add-fast-mode.md +32 -0
- package/RAF/43-swiss-army/outcomes/7-config-preset.md +31 -0
- package/RAF/43-swiss-army/plans/1-fix-model-validation.md +38 -0
- package/RAF/43-swiss-army/plans/2-update-commit-format.md +46 -0
- package/RAF/43-swiss-army/plans/3-wire-reasoning-effort.md +39 -0
- package/RAF/43-swiss-army/plans/4-remove-provider-flag.md +43 -0
- package/RAF/43-swiss-army/plans/5-config-wizard-validation.md +42 -0
- package/RAF/43-swiss-army/plans/6-add-fast-mode.md +46 -0
- package/RAF/43-swiss-army/plans/7-config-preset.md +51 -0
- package/RAF/44-config-api-change/decisions.md +22 -0
- package/RAF/44-config-api-change/input.md +5 -0
- package/RAF/44-config-api-change/outcomes/1-restructure-config-subcommands.md +19 -0
- package/RAF/44-config-api-change/outcomes/2-move-preset-under-config.md +17 -0
- package/RAF/44-config-api-change/outcomes/3-update-existing-tests-for-config-api.md +14 -0
- package/RAF/44-config-api-change/outcomes/4-update-config-command-docs.md +11 -0
- package/RAF/44-config-api-change/outcomes/5-fix-codex-name-generation.md +18 -0
- package/RAF/44-config-api-change/plans/1-restructure-config-subcommands.md +37 -0
- package/RAF/44-config-api-change/plans/2-move-preset-under-config.md +38 -0
- package/RAF/44-config-api-change/plans/3-update-existing-tests-for-config-api.md +38 -0
- package/RAF/44-config-api-change/plans/4-update-config-command-docs.md +36 -0
- package/RAF/44-config-api-change/plans/5-fix-codex-name-generation.md +49 -0
- package/RAF/45-signal-cairn/decisions.md +7 -0
- package/RAF/45-signal-cairn/input.md +2 -0
- package/RAF/45-signal-cairn/outcomes/1-rename-provider-to-harness.md +19 -0
- package/RAF/45-signal-cairn/outcomes/2-normalize-model-display-names.md +18 -0
- package/RAF/45-signal-cairn/plans/1-rename-provider-to-harness.md +40 -0
- package/RAF/45-signal-cairn/plans/2-normalize-model-display-names.md +41 -0
- package/RAF/45-signal-lantern/decisions.md +10 -0
- package/RAF/45-signal-lantern/input.md +2 -0
- package/RAF/45-signal-lantern/outcomes/1-add-effort-and-fast-to-do-model-display.md +15 -0
- package/RAF/45-signal-lantern/outcomes/2-capture-codex-post-run-token-usage.md +15 -0
- package/RAF/45-signal-lantern/outcomes/3-show-codex-token-summaries-without-fake-cost.md +14 -0
- package/RAF/45-signal-lantern/plans/1-add-effort-and-fast-to-do-model-display.md +38 -0
- package/RAF/45-signal-lantern/plans/2-capture-codex-post-run-token-usage.md +37 -0
- package/RAF/45-signal-lantern/plans/3-show-codex-token-summaries-without-fake-cost.md +40 -0
- package/RAF/46-lantern-arc/decisions.md +19 -0
- package/RAF/46-lantern-arc/input.md +6 -0
- package/RAF/46-lantern-arc/outcomes/1-remove-spark-alias.md +16 -0
- package/RAF/46-lantern-arc/outcomes/2-clean-up-worktree-plan-command.md +30 -0
- package/RAF/46-lantern-arc/outcomes/3-fix-token-usage-accumulation.md +32 -0
- package/RAF/46-lantern-arc/outcomes/4-display-effort-in-compact-mode.md +22 -0
- package/RAF/46-lantern-arc/outcomes/5-codex-fast-mode-research.md +38 -0
- package/RAF/46-lantern-arc/outcomes/6-optimize-llm-prompts.md +39 -0
- package/RAF/46-lantern-arc/plans/1-remove-spark-alias.md +38 -0
- package/RAF/46-lantern-arc/plans/2-clean-up-worktree-plan-command.md +33 -0
- package/RAF/46-lantern-arc/plans/3-fix-token-usage-accumulation.md +33 -0
- package/RAF/46-lantern-arc/plans/4-display-effort-in-compact-mode.md +28 -0
- package/RAF/46-lantern-arc/plans/5-codex-fast-mode-research.md +34 -0
- package/RAF/46-lantern-arc/plans/6-optimize-llm-prompts.md +48 -0
- package/RAF/47-signal-trim/decisions.md +13 -0
- package/RAF/47-signal-trim/input.md +2 -0
- package/RAF/47-signal-trim/plans/1-remove-cache-from-status.md +73 -0
- package/README.md +47 -57
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +47 -49
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts +2 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +57 -44
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +36 -153
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/preset.d.ts +3 -0
- package/dist/commands/preset.d.ts.map +1 -0
- package/dist/commands/preset.js +158 -0
- package/dist/commands/preset.js.map +1 -0
- package/dist/core/claude-runner.d.ts +2 -0
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +36 -12
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/codex-runner.d.ts +1 -0
- package/dist/core/codex-runner.d.ts.map +1 -1
- package/dist/core/codex-runner.js +26 -7
- package/dist/core/codex-runner.js.map +1 -1
- package/dist/core/failure-analyzer.js +2 -1
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/git.d.ts +2 -2
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +53 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/pull-request.js +3 -3
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/runner-factory.d.ts +4 -4
- package/dist/core/runner-factory.d.ts.map +1 -1
- package/dist/core/runner-factory.js +8 -8
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-interface.d.ts +1 -1
- package/dist/core/runner-types.d.ts +17 -4
- package/dist/core/runner-types.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.d.ts +7 -0
- package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.js +37 -4
- package/dist/parsers/codex-stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +29 -101
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/execution.d.ts.map +1 -1
- package/dist/prompts/execution.js +17 -34
- package/dist/prompts/execution.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +21 -120
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +33 -31
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +14 -28
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +36 -16
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +209 -104
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +25 -12
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +15 -2
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +36 -4
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +6 -1
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +84 -51
- package/dist/utils/token-tracker.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -2
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +4 -25
- package/dist/utils/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +60 -63
- package/src/commands/do.ts +63 -51
- package/src/commands/plan.ts +34 -165
- package/src/commands/preset.ts +186 -0
- package/src/core/claude-runner.ts +45 -5
- package/src/core/codex-runner.ts +32 -7
- package/src/core/failure-analyzer.ts +2 -1
- package/src/core/git.ts +57 -3
- package/src/core/pull-request.ts +3 -3
- package/src/core/runner-factory.ts +9 -9
- package/src/core/runner-interface.ts +1 -1
- package/src/core/runner-types.ts +17 -4
- package/src/parsers/codex-stream-renderer.ts +47 -4
- package/src/prompts/amend.ts +29 -101
- package/src/prompts/config-docs.md +206 -62
- package/src/prompts/execution.ts +17 -34
- package/src/prompts/planning.ts +21 -120
- package/src/types/config.ts +47 -58
- package/src/utils/config.ts +248 -115
- package/src/utils/name-generator.ts +29 -13
- package/src/utils/terminal-symbols.ts +46 -6
- package/src/utils/token-tracker.ts +96 -57
- package/src/utils/validation.ts +5 -30
- package/tests/unit/amend-prompt.test.ts +3 -2
- package/tests/unit/claude-runner-interactive.test.ts +21 -3
- package/tests/unit/claude-runner.test.ts +39 -0
- package/tests/unit/codex-runner.test.ts +163 -0
- package/tests/unit/codex-stream-renderer.test.ts +127 -0
- package/tests/unit/command-output.test.ts +57 -0
- package/tests/unit/commit-planning-artifacts-worktree.test.ts +24 -7
- package/tests/unit/commit-planning-artifacts.test.ts +26 -4
- package/tests/unit/config-command.test.ts +215 -303
- package/tests/unit/config.test.ts +319 -235
- package/tests/unit/dependency-integration.test.ts +27 -1
- package/tests/unit/do-model-display.test.ts +35 -0
- package/tests/unit/execution-prompt.test.ts +49 -19
- package/tests/unit/name-generator.test.ts +82 -12
- package/tests/unit/plan-command-auto-flag.test.ts +7 -10
- package/tests/unit/plan-command.test.ts +14 -17
- package/tests/unit/planning-prompt.test.ts +9 -8
- package/tests/unit/terminal-symbols.test.ts +94 -3
- package/tests/unit/token-tracker.test.ts +180 -1
- package/tests/unit/validation.test.ts +9 -41
- package/tests/unit/worktree-flag-override.test.ts +0 -186
package/src/commands/plan.ts
CHANGED
|
@@ -13,28 +13,23 @@ import {
|
|
|
13
13
|
validateEnvironment,
|
|
14
14
|
reportValidation,
|
|
15
15
|
validateProjectName,
|
|
16
|
-
resolveModelOption,
|
|
17
16
|
} from '../utils/validation.js';
|
|
18
17
|
import { logger } from '../utils/logger.js';
|
|
19
|
-
import {
|
|
18
|
+
import { formatModelDisplay, getModel } from '../utils/config.js';
|
|
19
|
+
import type { ModelEntry } from '../types/config.js';
|
|
20
20
|
import { generateProjectNames } from '../utils/name-generator.js';
|
|
21
21
|
import { pickProjectName } from '../ui/name-picker.js';
|
|
22
22
|
import {
|
|
23
23
|
getPlansDir,
|
|
24
24
|
getRafDir,
|
|
25
|
-
getNextProjectNumber,
|
|
26
|
-
formatProjectNumber,
|
|
27
25
|
resolveProjectIdentifierWithDetails,
|
|
28
26
|
getInputPath,
|
|
29
|
-
getDecisionsPath,
|
|
30
|
-
getOutcomesDir,
|
|
31
27
|
extractTaskNameFromPlanFile,
|
|
32
28
|
decodeTaskId,
|
|
33
29
|
encodeTaskId,
|
|
34
30
|
TASK_ID_PATTERN,
|
|
35
31
|
numericFileSort,
|
|
36
32
|
} from '../utils/paths.js';
|
|
37
|
-
import { sanitizeProjectName } from '../utils/validation.js';
|
|
38
33
|
import {
|
|
39
34
|
deriveProjectState,
|
|
40
35
|
isProjectComplete,
|
|
@@ -43,21 +38,14 @@ import {
|
|
|
43
38
|
import {
|
|
44
39
|
getRepoBasename,
|
|
45
40
|
getRepoRoot,
|
|
46
|
-
createWorktree,
|
|
47
41
|
validateWorktree,
|
|
48
|
-
removeWorktree,
|
|
49
|
-
pullMainBranch,
|
|
50
42
|
resolveWorktreeProjectByIdentifier,
|
|
51
43
|
} from '../core/worktree.js';
|
|
52
44
|
|
|
53
45
|
interface PlanCommandOptions {
|
|
54
46
|
amend?: boolean;
|
|
55
|
-
model?: string;
|
|
56
|
-
sonnet?: boolean;
|
|
57
47
|
auto?: boolean;
|
|
58
|
-
worktree?: boolean;
|
|
59
48
|
resume?: string;
|
|
60
|
-
provider?: string;
|
|
61
49
|
}
|
|
62
50
|
|
|
63
51
|
export function createPlanCommand(): Command {
|
|
@@ -68,30 +56,15 @@ export function createPlanCommand(): Command {
|
|
|
68
56
|
'-a, --amend',
|
|
69
57
|
'Add tasks to an existing project (requires project identifier as argument)'
|
|
70
58
|
)
|
|
71
|
-
.option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
|
|
72
|
-
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
73
59
|
.option('-y, --auto', 'Skip permission prompts for file operations')
|
|
74
|
-
.option('-w, --worktree', 'Create a git worktree for isolated planning')
|
|
75
|
-
.option('--no-worktree', 'Disable worktree mode (overrides config)')
|
|
76
60
|
.option('-r, --resume <identifier>', 'Resume a planning session for an existing project')
|
|
77
|
-
.option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
|
|
78
61
|
.action(async (projectName: string | undefined, options: PlanCommandOptions) => {
|
|
79
|
-
|
|
80
|
-
let model: string;
|
|
81
|
-
try {
|
|
82
|
-
model = resolveModelOption(options.model, options.sonnet, 'plan');
|
|
83
|
-
} catch (error) {
|
|
84
|
-
logger.error((error as Error).message);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
62
|
+
const modelEntry = getModel('plan');
|
|
87
63
|
|
|
88
64
|
const autoMode = options.auto ?? false;
|
|
89
|
-
const worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
90
|
-
|
|
91
|
-
const provider = options.provider as import('../types/config.js').HarnessProvider | undefined;
|
|
92
65
|
|
|
93
66
|
if (options.resume) {
|
|
94
|
-
await runResumeCommand(options.resume,
|
|
67
|
+
await runResumeCommand(options.resume, modelEntry);
|
|
95
68
|
} else if (options.amend) {
|
|
96
69
|
if (!projectName) {
|
|
97
70
|
logger.error('--amend requires a project identifier');
|
|
@@ -99,16 +72,16 @@ export function createPlanCommand(): Command {
|
|
|
99
72
|
logger.error(' or: raf plan --amend <project>');
|
|
100
73
|
process.exit(1);
|
|
101
74
|
}
|
|
102
|
-
await runAmendCommand(projectName,
|
|
75
|
+
await runAmendCommand(projectName, modelEntry, autoMode);
|
|
103
76
|
} else {
|
|
104
|
-
await runPlanCommand(projectName,
|
|
77
|
+
await runPlanCommand(projectName, modelEntry, autoMode);
|
|
105
78
|
}
|
|
106
79
|
});
|
|
107
80
|
|
|
108
81
|
return command;
|
|
109
82
|
}
|
|
110
83
|
|
|
111
|
-
async function runPlanCommand(projectName?: string,
|
|
84
|
+
async function runPlanCommand(projectName?: string, modelEntry?: ModelEntry, autoMode: boolean = false): Promise<void> {
|
|
112
85
|
// Validate environment
|
|
113
86
|
const validation = validateEnvironment();
|
|
114
87
|
reportValidation(validation);
|
|
@@ -151,7 +124,7 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
151
124
|
});
|
|
152
125
|
|
|
153
126
|
if (answer === 'amend') {
|
|
154
|
-
await runAmendCommand(existingFolder,
|
|
127
|
+
await runAmendCommand(existingFolder, modelEntry, autoMode);
|
|
155
128
|
return;
|
|
156
129
|
} else if (answer === 'cancel') {
|
|
157
130
|
logger.info('Aborted.');
|
|
@@ -161,15 +134,6 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
161
134
|
}
|
|
162
135
|
}
|
|
163
136
|
|
|
164
|
-
// Validate git repo for worktree mode
|
|
165
|
-
if (worktreeMode) {
|
|
166
|
-
const repoRoot = getRepoRoot();
|
|
167
|
-
if (!repoRoot) {
|
|
168
|
-
logger.error('--worktree requires a git repository');
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
137
|
// Open editor for user input
|
|
174
138
|
logger.info('Opening editor for project description...');
|
|
175
139
|
logger.info('(Save and close the editor when done)');
|
|
@@ -197,7 +161,8 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
197
161
|
// Get or generate project name
|
|
198
162
|
let finalProjectName = projectName;
|
|
199
163
|
if (!finalProjectName) {
|
|
200
|
-
const
|
|
164
|
+
const nameEntry = getModel('nameGeneration');
|
|
165
|
+
const nameModel = formatModelDisplay(nameEntry.model);
|
|
201
166
|
logger.info(`Generating project name suggestions with ${nameModel}...`);
|
|
202
167
|
const suggestedNames = await generateProjectNames(cleanInput);
|
|
203
168
|
logger.newline();
|
|
@@ -217,103 +182,32 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
217
182
|
process.exit(1);
|
|
218
183
|
}
|
|
219
184
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (worktreeMode) {
|
|
225
|
-
// Worktree mode: create worktree, then project folder inside it
|
|
226
|
-
const repoBasename = getRepoBasename()!;
|
|
227
|
-
const repoRoot = getRepoRoot()!;
|
|
228
|
-
const rafDir = getRafDir();
|
|
229
|
-
|
|
230
|
-
// Sync main branch before creating worktree (if enabled)
|
|
231
|
-
if (getSyncMainBranch()) {
|
|
232
|
-
const syncResult = pullMainBranch();
|
|
233
|
-
if (syncResult.success) {
|
|
234
|
-
if (syncResult.hadChanges) {
|
|
235
|
-
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Compute project number from main repo's RAF directory (scan worktrees too)
|
|
243
|
-
const projectNumber = getNextProjectNumber(rafDir, repoBasename);
|
|
244
|
-
const sanitizedName = sanitizeProjectName(finalProjectName);
|
|
245
|
-
const folderName = `${formatProjectNumber(projectNumber)}-${sanitizedName}`;
|
|
246
|
-
|
|
247
|
-
// Create worktree
|
|
248
|
-
const result = createWorktree(repoBasename, folderName);
|
|
249
|
-
if (!result.success) {
|
|
250
|
-
logger.error(`Failed to create worktree: ${result.error}`);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
worktreePath = result.worktreePath;
|
|
255
|
-
worktreeBranch = result.branch;
|
|
256
|
-
|
|
257
|
-
// Compute project path inside worktree at the same relative path
|
|
258
|
-
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
259
|
-
projectPath = path.join(worktreePath, rafRelativePath, folderName);
|
|
260
|
-
|
|
261
|
-
// Create project folder structure inside the worktree
|
|
262
|
-
fs.mkdirSync(projectPath, { recursive: true });
|
|
263
|
-
fs.mkdirSync(getPlansDir(projectPath), { recursive: true });
|
|
264
|
-
fs.mkdirSync(getOutcomesDir(projectPath), { recursive: true });
|
|
265
|
-
fs.writeFileSync(getDecisionsPath(projectPath), '# Project Decisions\n');
|
|
185
|
+
// Standard mode: create project in main repo
|
|
186
|
+
const projectManager = new ProjectManager();
|
|
187
|
+
const projectPath = projectManager.createProject(finalProjectName);
|
|
266
188
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
logger.success(`Created worktree: ${worktreePath}`);
|
|
271
|
-
logger.success(`Branch: ${worktreeBranch}`);
|
|
272
|
-
logger.success(`Project: ${projectPath}`);
|
|
273
|
-
logger.newline();
|
|
274
|
-
} else {
|
|
275
|
-
// Standard mode: create project in main repo
|
|
276
|
-
const projectManager = new ProjectManager();
|
|
277
|
-
projectPath = projectManager.createProject(finalProjectName);
|
|
278
|
-
|
|
279
|
-
logger.success(`Created project: ${projectPath}`);
|
|
280
|
-
logger.newline();
|
|
189
|
+
logger.success(`Created project: ${projectPath}`);
|
|
190
|
+
logger.newline();
|
|
281
191
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
192
|
+
// Save input
|
|
193
|
+
projectManager.saveInput(projectPath, userInput);
|
|
285
194
|
|
|
286
195
|
// Set up shutdown handler
|
|
287
|
-
const claudeRunner = createRunner({ model,
|
|
196
|
+
const claudeRunner = createRunner({ model: modelEntry?.model, harness: modelEntry?.harness, reasoningEffort: modelEntry?.reasoningEffort, fast: modelEntry?.fast });
|
|
288
197
|
shutdownHandler.init();
|
|
289
198
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
290
199
|
|
|
291
200
|
// Register cleanup callback
|
|
292
201
|
shutdownHandler.onShutdown(() => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const plansDir = getPlansDir(projectPath);
|
|
296
|
-
const hasPlanFiles = fs.existsSync(plansDir) &&
|
|
297
|
-
fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).length > 0;
|
|
298
|
-
if (!hasPlanFiles) {
|
|
299
|
-
const removal = removeWorktree(worktreePath);
|
|
300
|
-
if (removal.success) {
|
|
301
|
-
logger.debug(`Cleaned up worktree: ${worktreePath}`);
|
|
302
|
-
} else {
|
|
303
|
-
logger.warn(`Failed to clean up worktree: ${removal.error}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
const projectManager = new ProjectManager();
|
|
308
|
-
projectManager.cleanupEmptyProject(projectPath);
|
|
309
|
-
}
|
|
202
|
+
const projectManager = new ProjectManager();
|
|
203
|
+
projectManager.cleanupEmptyProject(projectPath);
|
|
310
204
|
});
|
|
311
205
|
|
|
312
206
|
// Run planning session
|
|
313
207
|
logger.info('Starting planning session...');
|
|
314
208
|
logger.info('The planner will interview you about each task.');
|
|
315
|
-
if (
|
|
316
|
-
logger.info(`Using model: ${model}`);
|
|
209
|
+
if (modelEntry) {
|
|
210
|
+
logger.info(`Using model: ${formatModelDisplay(modelEntry.model, modelEntry.harness, { includeHarness: true })}`);
|
|
317
211
|
}
|
|
318
212
|
if (autoMode) {
|
|
319
213
|
logger.warn('Auto mode enabled: permission prompts will be skipped.');
|
|
@@ -323,14 +217,11 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
323
217
|
const { systemPrompt, userMessage } = getPlanningPrompt({
|
|
324
218
|
projectPath,
|
|
325
219
|
inputContent: userInput,
|
|
326
|
-
worktreeMode,
|
|
327
220
|
});
|
|
328
221
|
|
|
329
222
|
try {
|
|
330
223
|
const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
|
|
331
224
|
dangerouslySkipPermissions: autoMode,
|
|
332
|
-
// Run session in the worktree root if in worktree mode
|
|
333
|
-
cwd: worktreePath ?? undefined,
|
|
334
225
|
});
|
|
335
226
|
|
|
336
227
|
if (exitCode !== 0) {
|
|
@@ -357,45 +248,23 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
357
248
|
// Commit planning artifacts (input.md, decisions.md, and plan files)
|
|
358
249
|
const planAbsolutePaths = planFiles.map((f) => path.join(plansDir, f));
|
|
359
250
|
await commitPlanningArtifacts(projectPath, {
|
|
360
|
-
cwd: worktreePath ?? undefined,
|
|
361
251
|
additionalFiles: planAbsolutePaths,
|
|
362
252
|
});
|
|
363
253
|
|
|
364
254
|
logger.newline();
|
|
365
|
-
|
|
366
|
-
logger.info(`Worktree: ${worktreePath}`);
|
|
367
|
-
logger.info(`Branch: ${worktreeBranch}`);
|
|
368
|
-
logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
|
|
369
|
-
} else {
|
|
370
|
-
logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
|
|
371
|
-
}
|
|
255
|
+
logger.info(`Run 'raf do ${finalProjectName}' to execute the plans.`);
|
|
372
256
|
}
|
|
373
257
|
} catch (error) {
|
|
374
258
|
logger.error(`Planning failed: ${error}`);
|
|
375
259
|
throw error;
|
|
376
260
|
} finally {
|
|
377
|
-
if
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const hasPlanFiles = fs.existsSync(plansDir) &&
|
|
381
|
-
fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).length > 0;
|
|
382
|
-
if (!hasPlanFiles) {
|
|
383
|
-
const removal = removeWorktree(worktreePath);
|
|
384
|
-
if (removal.success) {
|
|
385
|
-
logger.debug(`Cleaned up worktree: ${worktreePath}`);
|
|
386
|
-
} else {
|
|
387
|
-
logger.warn(`Failed to clean up worktree: ${removal.error}`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
} else if (!worktreeMode) {
|
|
391
|
-
// Cleanup empty project folder if no plans were created (standard mode only)
|
|
392
|
-
const projectManager = new ProjectManager();
|
|
393
|
-
projectManager.cleanupEmptyProject(projectPath);
|
|
394
|
-
}
|
|
261
|
+
// Cleanup empty project folder if no plans were created
|
|
262
|
+
const projectManager = new ProjectManager();
|
|
263
|
+
projectManager.cleanupEmptyProject(projectPath);
|
|
395
264
|
}
|
|
396
265
|
}
|
|
397
266
|
|
|
398
|
-
async function runAmendCommand(identifier: string,
|
|
267
|
+
async function runAmendCommand(identifier: string, modelEntry?: ModelEntry, autoMode: boolean = false): Promise<void> {
|
|
399
268
|
// Validate environment
|
|
400
269
|
const validation = validateEnvironment();
|
|
401
270
|
reportValidation(validation);
|
|
@@ -528,15 +397,15 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
528
397
|
fs.writeFileSync(inputPath, updatedInput);
|
|
529
398
|
|
|
530
399
|
// Set up shutdown handler
|
|
531
|
-
const claudeRunner = createRunner({ model,
|
|
400
|
+
const claudeRunner = createRunner({ model: modelEntry?.model, harness: modelEntry?.harness, reasoningEffort: modelEntry?.reasoningEffort, fast: modelEntry?.fast });
|
|
532
401
|
shutdownHandler.init();
|
|
533
402
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
534
403
|
|
|
535
404
|
// Run amend planning session
|
|
536
405
|
logger.info('Starting amendment session...');
|
|
537
406
|
logger.info('The planner will interview you about each new task.');
|
|
538
|
-
if (
|
|
539
|
-
logger.info(`Using model: ${model}`);
|
|
407
|
+
if (modelEntry) {
|
|
408
|
+
logger.info(`Using model: ${formatModelDisplay(modelEntry.model, modelEntry.harness, { includeHarness: true })}`);
|
|
540
409
|
}
|
|
541
410
|
if (autoMode) {
|
|
542
411
|
logger.warn('Auto mode enabled: permission prompts will be skipped.');
|
|
@@ -636,7 +505,7 @@ ${taskList}
|
|
|
636
505
|
`;
|
|
637
506
|
}
|
|
638
507
|
|
|
639
|
-
async function runResumeCommand(identifier: string,
|
|
508
|
+
async function runResumeCommand(identifier: string, modelEntry?: ModelEntry): Promise<void> {
|
|
640
509
|
// Validate environment
|
|
641
510
|
const validation = validateEnvironment();
|
|
642
511
|
reportValidation(validation);
|
|
@@ -700,13 +569,13 @@ async function runResumeCommand(identifier: string, model?: string, provider?: i
|
|
|
700
569
|
}
|
|
701
570
|
|
|
702
571
|
logger.info(`Project: ${folderName}`);
|
|
703
|
-
if (
|
|
704
|
-
logger.info(`Model: ${model}`);
|
|
572
|
+
if (modelEntry) {
|
|
573
|
+
logger.info(`Model: ${formatModelDisplay(modelEntry.model, modelEntry.harness, { includeHarness: true })}`);
|
|
705
574
|
}
|
|
706
575
|
logger.newline();
|
|
707
576
|
|
|
708
577
|
// Set up shutdown handler
|
|
709
|
-
const claudeRunner = createRunner({ model,
|
|
578
|
+
const claudeRunner = createRunner({ model: modelEntry?.model, harness: modelEntry?.harness, reasoningEffort: modelEntry?.reasoningEffort, fast: modelEntry?.fast });
|
|
710
579
|
shutdownHandler.init();
|
|
711
580
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
712
581
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { logger } from '../utils/logger.js';
|
|
6
|
+
import {
|
|
7
|
+
getConfigPath,
|
|
8
|
+
validateConfig,
|
|
9
|
+
ConfigValidationError,
|
|
10
|
+
} from '../utils/config.js';
|
|
11
|
+
|
|
12
|
+
const PRESETS_DIR = path.join(os.homedir(), '.raf', 'presets');
|
|
13
|
+
|
|
14
|
+
function ensurePresetsDir(): void {
|
|
15
|
+
if (!fs.existsSync(PRESETS_DIR)) {
|
|
16
|
+
fs.mkdirSync(PRESETS_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getPresetPath(name: string): string {
|
|
21
|
+
return path.join(PRESETS_DIR, `${name}.json`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validatePresetName(name: string): void {
|
|
25
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
26
|
+
logger.error('Preset name must contain only letters, numbers, hyphens, and underscores.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function savePreset(name: string): void {
|
|
32
|
+
validatePresetName(name);
|
|
33
|
+
ensurePresetsDir();
|
|
34
|
+
|
|
35
|
+
const configPath = getConfigPath();
|
|
36
|
+
if (!fs.existsSync(configPath)) {
|
|
37
|
+
logger.error('No config file found. Run `raf config wizard` or `raf config set <key> <value>` to create one first.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const presetPath = getPresetPath(name);
|
|
42
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
43
|
+
|
|
44
|
+
// Validate the config before saving
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(configContent);
|
|
47
|
+
validateConfig(parsed);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof ConfigValidationError) {
|
|
50
|
+
logger.error(`Current config is invalid: ${err.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const existed = fs.existsSync(presetPath);
|
|
57
|
+
fs.writeFileSync(presetPath, configContent);
|
|
58
|
+
logger.info(`${existed ? 'Updated' : 'Saved'} preset "${name}" at ${presetPath}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function loadPreset(name: string): void {
|
|
62
|
+
validatePresetName(name);
|
|
63
|
+
const presetPath = getPresetPath(name);
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(presetPath)) {
|
|
66
|
+
logger.error(`Preset "${name}" not found. Run \`raf config preset list\` to see available presets.`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const content = fs.readFileSync(presetPath, 'utf-8');
|
|
71
|
+
|
|
72
|
+
// Validate preset before applying
|
|
73
|
+
let parsed: unknown;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(content);
|
|
76
|
+
} catch {
|
|
77
|
+
logger.error(`Preset "${name}" contains invalid JSON.`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
validateConfig(parsed);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (err instanceof ConfigValidationError) {
|
|
85
|
+
logger.error(`Preset "${name}" is invalid: ${err.message}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const configPath = getConfigPath();
|
|
92
|
+
const configDir = path.dirname(configPath);
|
|
93
|
+
if (!fs.existsSync(configDir)) {
|
|
94
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
fs.writeFileSync(configPath, content);
|
|
97
|
+
logger.info(`Loaded preset "${name}" into ${configPath}`);
|
|
98
|
+
|
|
99
|
+
// Show a brief summary of what was loaded
|
|
100
|
+
const config = parsed as Record<string, unknown>;
|
|
101
|
+
const keys = Object.keys(config);
|
|
102
|
+
if (keys.length > 0) {
|
|
103
|
+
logger.info(` Keys: ${keys.join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function listPresets(): void {
|
|
108
|
+
ensurePresetsDir();
|
|
109
|
+
|
|
110
|
+
const files = fs.readdirSync(PRESETS_DIR).filter(f => f.endsWith('.json'));
|
|
111
|
+
if (files.length === 0) {
|
|
112
|
+
logger.info('No presets saved. Use `raf config preset save <name>` to create one.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
logger.info(`Found ${files.length} preset(s):\n`);
|
|
117
|
+
for (const file of files) {
|
|
118
|
+
const name = path.basename(file, '.json');
|
|
119
|
+
const filePath = path.join(PRESETS_DIR, file);
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
122
|
+
const config = JSON.parse(content) as Record<string, unknown>;
|
|
123
|
+
|
|
124
|
+
// Build a brief summary from models
|
|
125
|
+
const summary: string[] = [];
|
|
126
|
+
if (config.models && typeof config.models === 'object') {
|
|
127
|
+
const models = config.models as Record<string, { model?: string; harness?: string }>;
|
|
128
|
+
const harnesses = new Set<string>();
|
|
129
|
+
for (const entry of Object.values(models)) {
|
|
130
|
+
if (entry?.harness) harnesses.add(entry.harness);
|
|
131
|
+
}
|
|
132
|
+
if (harnesses.size > 0) {
|
|
133
|
+
summary.push(`harnesses: ${[...harnesses].join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
if (models.execute?.model) {
|
|
136
|
+
summary.push(`execute: ${models.execute.model}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const info = summary.length > 0 ? ` (${summary.join('; ')})` : '';
|
|
141
|
+
logger.info(` ${name}${info}`);
|
|
142
|
+
} catch {
|
|
143
|
+
logger.info(` ${name} (invalid JSON)`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function deletePreset(name: string): void {
|
|
149
|
+
validatePresetName(name);
|
|
150
|
+
const presetPath = getPresetPath(name);
|
|
151
|
+
|
|
152
|
+
if (!fs.existsSync(presetPath)) {
|
|
153
|
+
logger.error(`Preset "${name}" not found.`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fs.unlinkSync(presetPath);
|
|
158
|
+
logger.info(`Deleted preset "${name}".`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createPresetCommand(): Command {
|
|
162
|
+
const preset = new Command('preset')
|
|
163
|
+
.description('Manage named config presets under `raf config preset`');
|
|
164
|
+
|
|
165
|
+
preset
|
|
166
|
+
.command('save <name>')
|
|
167
|
+
.description('Save current config as a named preset')
|
|
168
|
+
.action((name: string) => savePreset(name));
|
|
169
|
+
|
|
170
|
+
preset
|
|
171
|
+
.command('load <name>')
|
|
172
|
+
.description('Load a preset into the active config (overwrites current config)')
|
|
173
|
+
.action((name: string) => loadPreset(name));
|
|
174
|
+
|
|
175
|
+
preset
|
|
176
|
+
.command('list')
|
|
177
|
+
.description('List all saved presets')
|
|
178
|
+
.action(() => listPresets());
|
|
179
|
+
|
|
180
|
+
preset
|
|
181
|
+
.command('delete <name>')
|
|
182
|
+
.description('Delete a saved preset')
|
|
183
|
+
.action((name: string) => deletePreset(name));
|
|
184
|
+
|
|
185
|
+
return preset;
|
|
186
|
+
}
|
|
@@ -4,6 +4,7 @@ import { execSync, spawn } from 'node:child_process';
|
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
5
5
|
import { renderStreamEvent } from '../parsers/stream-renderer.js';
|
|
6
6
|
import { getModel } from '../utils/config.js';
|
|
7
|
+
import { mergeUsageData } from '../utils/token-tracker.js';
|
|
7
8
|
import type { ICliRunner } from './runner-interface.js';
|
|
8
9
|
import type { RunnerOptions, RunnerConfig, RunResult } from './runner-types.js';
|
|
9
10
|
import { createCompletionDetector } from './completion-detector.js';
|
|
@@ -32,9 +33,13 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
32
33
|
private activeProcess: pty.IPty | null = null;
|
|
33
34
|
private killed = false;
|
|
34
35
|
private model: string;
|
|
36
|
+
private reasoningEffort?: string;
|
|
37
|
+
private fast?: boolean;
|
|
35
38
|
|
|
36
39
|
constructor(config: RunnerConfig = {}) {
|
|
37
|
-
this.model = config.model ?? getModel('execute');
|
|
40
|
+
this.model = config.model ?? getModel('execute').model;
|
|
41
|
+
this.reasoningEffort = config.reasoningEffort;
|
|
42
|
+
this.fast = config.fast;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/**
|
|
@@ -55,6 +60,16 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
55
60
|
return new Promise((resolve) => {
|
|
56
61
|
const args = ['--model', this.model];
|
|
57
62
|
|
|
63
|
+
// Add reasoning effort flag when configured
|
|
64
|
+
if (this.reasoningEffort) {
|
|
65
|
+
args.push('--effort', this.reasoningEffort);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add fast mode when configured
|
|
69
|
+
if (this.fast) {
|
|
70
|
+
args.push('--settings', '{"fastMode": true}');
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
// Add --dangerously-skip-permissions if requested (for --auto mode)
|
|
59
74
|
if (dangerouslySkipPermissions) {
|
|
60
75
|
args.push('--dangerously-skip-permissions');
|
|
@@ -147,6 +162,16 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
147
162
|
return new Promise((resolve) => {
|
|
148
163
|
const args = ['--resume', '--model', this.model];
|
|
149
164
|
|
|
165
|
+
// Add reasoning effort flag when configured
|
|
166
|
+
if (this.reasoningEffort) {
|
|
167
|
+
args.push('--effort', this.reasoningEffort);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Add fast mode when configured
|
|
171
|
+
if (this.fast) {
|
|
172
|
+
args.push('--settings', '{"fastMode": true}');
|
|
173
|
+
}
|
|
174
|
+
|
|
150
175
|
logger.debug(`Starting session resume picker with model: ${this.model}`);
|
|
151
176
|
|
|
152
177
|
this.activeProcess = pty.spawn(getClaudePath(), args, {
|
|
@@ -272,10 +297,23 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
272
297
|
// including tool calls, file operations, and token usage in the result event.
|
|
273
298
|
// --dangerously-skip-permissions bypasses interactive prompts
|
|
274
299
|
// -p enables print mode (non-interactive)
|
|
275
|
-
const
|
|
300
|
+
const execArgs = [
|
|
276
301
|
'--dangerously-skip-permissions',
|
|
277
302
|
'--model',
|
|
278
303
|
this.model,
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
// Add reasoning effort flag when configured
|
|
307
|
+
if (this.reasoningEffort) {
|
|
308
|
+
execArgs.push('--effort', this.reasoningEffort);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Add fast mode when configured
|
|
312
|
+
if (this.fast) {
|
|
313
|
+
execArgs.push('--settings', '{"fastMode": true}');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
execArgs.push(
|
|
279
317
|
'--append-system-prompt',
|
|
280
318
|
prompt,
|
|
281
319
|
'--output-format',
|
|
@@ -283,7 +321,9 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
283
321
|
'--verbose',
|
|
284
322
|
'-p',
|
|
285
323
|
'Execute the task as described in the system prompt.',
|
|
286
|
-
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const proc = spawn(claudePath, execArgs, {
|
|
287
327
|
cwd,
|
|
288
328
|
env: process.env,
|
|
289
329
|
stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
|
|
@@ -346,7 +386,7 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
346
386
|
|
|
347
387
|
// Capture usage data from result events
|
|
348
388
|
if (rendered.usageData) {
|
|
349
|
-
usageData = rendered.usageData;
|
|
389
|
+
usageData = mergeUsageData(usageData, rendered.usageData);
|
|
350
390
|
}
|
|
351
391
|
|
|
352
392
|
if (shouldDisplay() && rendered.display) {
|
|
@@ -368,7 +408,7 @@ export class ClaudeRunner implements ICliRunner {
|
|
|
368
408
|
output += rendered.textContent;
|
|
369
409
|
}
|
|
370
410
|
if (rendered.usageData) {
|
|
371
|
-
usageData = rendered.usageData;
|
|
411
|
+
usageData = mergeUsageData(usageData, rendered.usageData);
|
|
372
412
|
}
|
|
373
413
|
if (shouldDisplay() && rendered.display) {
|
|
374
414
|
process.stdout.write(rendered.display);
|