rafcode 2.1.1 → 2.3.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/RAF/ahtahs-token-reaper/decisions.md +37 -0
- package/RAF/ahtahs-token-reaper/input.md +20 -0
- package/RAF/ahtahs-token-reaper/outcomes/01-extend-token-tracker-data-model.md +42 -0
- package/RAF/ahtahs-token-reaper/outcomes/02-accumulate-usage-in-retry-loop.md +31 -0
- package/RAF/ahtahs-token-reaper/outcomes/03-per-attempt-display-formatting.md +60 -0
- package/RAF/ahtahs-token-reaper/outcomes/04-add-model-name-to-claude-call-logs.md +57 -0
- package/RAF/ahtahs-token-reaper/outcomes/05-handle-invalid-config-in-raf-config.md +46 -0
- package/RAF/ahtahs-token-reaper/outcomes/06-fix-verbose-toggle-timer-display.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/01-extend-token-tracker-data-model.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/02-accumulate-usage-in-retry-loop.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/03-per-attempt-display-formatting.md +43 -0
- package/RAF/ahtahs-token-reaper/plans/04-add-model-name-to-claude-call-logs.md +38 -0
- package/RAF/ahtahs-token-reaper/plans/05-handle-invalid-config-in-raf-config.md +36 -0
- package/RAF/ahtahs-token-reaper/plans/06-fix-verbose-toggle-timer-display.md +40 -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 +195 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +55 -7
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +5 -3
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +19 -2
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +43 -96
- 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 +9 -4
- package/dist/core/pull-request.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 -1
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +34 -4
- 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 -4
- 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 +65 -7
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +297 -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 +25 -0
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +87 -0
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +55 -0
- package/dist/utils/token-tracker.d.ts.map +1 -0
- package/dist/utils/token-tracker.js +142 -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 +230 -0
- package/src/commands/do.ts +64 -6
- package/src/commands/plan.ts +5 -3
- package/src/core/claude-runner.ts +59 -115
- package/src/core/failure-analyzer.ts +6 -3
- package/src/core/git.ts +10 -3
- package/src/core/pull-request.ts +9 -4
- package/src/index.ts +2 -0
- package/src/parsers/stream-renderer.ts +54 -4
- package/src/prompts/config-docs.md +331 -0
- package/src/prompts/execution.ts +13 -1
- package/src/types/config.ts +156 -7
- package/src/utils/config.ts +357 -21
- package/src/utils/name-generator.ts +84 -71
- package/src/utils/terminal-symbols.ts +103 -0
- package/src/utils/token-tracker.ts +177 -0
- package/src/utils/validation.ts +15 -10
- package/src/utils/verbose-toggle.ts +103 -0
- package/tests/unit/claude-runner.test.ts +171 -7
- package/tests/unit/config-command.test.ts +242 -0
- package/tests/unit/config.test.ts +632 -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 -0
- package/tests/unit/terminal-symbols.test.ts +245 -0
- package/tests/unit/timer-verbose-integration.test.ts +170 -0
- package/tests/unit/token-tracker.test.ts +685 -0
- package/tests/unit/verbose-toggle.test.ts +204 -0
|
@@ -4,11 +4,14 @@ import type { IDisposable } from 'node-pty';
|
|
|
4
4
|
import { execSync, spawn } from 'node:child_process';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
6
|
import { renderStreamEvent } from '../parsers/stream-renderer.js';
|
|
7
|
+
import type { UsageData } from '../types/config.js';
|
|
7
8
|
import { getHeadCommitHash, getHeadCommitMessage, isFileCommittedInHead } from './git.js';
|
|
9
|
+
import { getClaudeCommand, getModel } from '../utils/config.js';
|
|
8
10
|
|
|
9
11
|
function getClaudePath(): string {
|
|
12
|
+
const cmd = getClaudeCommand();
|
|
10
13
|
try {
|
|
11
|
-
return execSync(
|
|
14
|
+
return execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();
|
|
12
15
|
} catch {
|
|
13
16
|
throw new Error('Claude CLI not found. Please ensure it is installed and in your PATH.');
|
|
14
17
|
}
|
|
@@ -56,6 +59,12 @@ export interface ClaudeRunnerOptions {
|
|
|
56
59
|
* Only applied in non-interactive modes (run, runVerbose).
|
|
57
60
|
*/
|
|
58
61
|
effortLevel?: 'low' | 'medium' | 'high';
|
|
62
|
+
/**
|
|
63
|
+
* Dynamic verbose display callback. When provided, called for each stream event
|
|
64
|
+
* to determine whether to write display output to stdout. Overrides the static
|
|
65
|
+
* verbose parameter in _runStreamJson. Used by the runtime verbose toggle.
|
|
66
|
+
*/
|
|
67
|
+
verboseCheck?: () => boolean;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
export interface ClaudeRunnerConfig {
|
|
@@ -71,6 +80,8 @@ export interface RunResult {
|
|
|
71
80
|
exitCode: number;
|
|
72
81
|
timedOut: boolean;
|
|
73
82
|
contextOverflow: boolean;
|
|
83
|
+
/** Token usage data from the stream-json result event. */
|
|
84
|
+
usageData?: UsageData;
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
const CONTEXT_OVERFLOW_PATTERNS = [
|
|
@@ -260,7 +271,7 @@ export class ClaudeRunner {
|
|
|
260
271
|
private model: string;
|
|
261
272
|
|
|
262
273
|
constructor(config: ClaudeRunnerConfig = {}) {
|
|
263
|
-
this.model = config.model ?? '
|
|
274
|
+
this.model = config.model ?? getModel('execute');
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
/**
|
|
@@ -362,8 +373,8 @@ export class ClaudeRunner {
|
|
|
362
373
|
|
|
363
374
|
/**
|
|
364
375
|
* Run Claude non-interactively and collect output.
|
|
365
|
-
*
|
|
366
|
-
*
|
|
376
|
+
* Uses stream-json format internally to capture token usage data.
|
|
377
|
+
* Tool display is suppressed (non-verbose mode).
|
|
367
378
|
*
|
|
368
379
|
* TIMEOUT BEHAVIOR:
|
|
369
380
|
* - The timeout is applied per individual call to this method
|
|
@@ -373,103 +384,7 @@ export class ClaudeRunner {
|
|
|
373
384
|
* - Default timeout is 60 minutes if not specified
|
|
374
385
|
*/
|
|
375
386
|
async run(prompt: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
|
|
376
|
-
|
|
377
|
-
// Ensure timeout is a positive number, fallback to 60 minutes
|
|
378
|
-
const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
|
|
379
|
-
const timeoutMs = validatedTimeout * 60 * 1000;
|
|
380
|
-
|
|
381
|
-
return new Promise((resolve) => {
|
|
382
|
-
let output = '';
|
|
383
|
-
let stderr = '';
|
|
384
|
-
let timedOut = false;
|
|
385
|
-
let contextOverflow = false;
|
|
386
|
-
|
|
387
|
-
const claudePath = getClaudePath();
|
|
388
|
-
|
|
389
|
-
logger.debug(`Starting Claude execution session with model: ${this.model}`);
|
|
390
|
-
logger.debug(`Claude path: ${claudePath}`);
|
|
391
|
-
|
|
392
|
-
// Build env, optionally injecting effort level
|
|
393
|
-
const env = effortLevel
|
|
394
|
-
? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
|
|
395
|
-
: process.env;
|
|
396
|
-
|
|
397
|
-
// Use --append-system-prompt to add RAF instructions to system prompt
|
|
398
|
-
// This gives RAF instructions stronger precedence than passing as user message
|
|
399
|
-
// --dangerously-skip-permissions bypasses interactive prompts
|
|
400
|
-
// -p enables print mode (non-interactive)
|
|
401
|
-
const proc = spawn(claudePath, [
|
|
402
|
-
'--dangerously-skip-permissions',
|
|
403
|
-
'--model',
|
|
404
|
-
this.model,
|
|
405
|
-
'--append-system-prompt',
|
|
406
|
-
prompt,
|
|
407
|
-
'-p',
|
|
408
|
-
'Execute the task as described in the system prompt.',
|
|
409
|
-
], {
|
|
410
|
-
cwd,
|
|
411
|
-
env,
|
|
412
|
-
stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Track this process
|
|
416
|
-
this.activeProcess = proc as any;
|
|
417
|
-
|
|
418
|
-
// Set up timeout
|
|
419
|
-
const timeoutHandle = setTimeout(() => {
|
|
420
|
-
timedOut = true;
|
|
421
|
-
logger.warn('Claude session timed out');
|
|
422
|
-
proc.kill('SIGTERM');
|
|
423
|
-
}, timeoutMs);
|
|
424
|
-
|
|
425
|
-
// Set up completion detection (stdout marker + outcome file polling)
|
|
426
|
-
const completionDetector = createCompletionDetector(
|
|
427
|
-
() => proc.kill('SIGTERM'),
|
|
428
|
-
outcomeFilePath,
|
|
429
|
-
commitContext,
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
// Collect stdout
|
|
433
|
-
proc.stdout.on('data', (data) => {
|
|
434
|
-
const text = data.toString();
|
|
435
|
-
output += text;
|
|
436
|
-
|
|
437
|
-
// Check for completion marker to start grace period
|
|
438
|
-
completionDetector.checkOutput(output);
|
|
439
|
-
|
|
440
|
-
// Check for context overflow
|
|
441
|
-
for (const pattern of CONTEXT_OVERFLOW_PATTERNS) {
|
|
442
|
-
if (pattern.test(text)) {
|
|
443
|
-
contextOverflow = true;
|
|
444
|
-
logger.warn('Context overflow detected');
|
|
445
|
-
proc.kill('SIGTERM');
|
|
446
|
-
break;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
// Collect stderr
|
|
452
|
-
proc.stderr.on('data', (data) => {
|
|
453
|
-
stderr += data.toString();
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
proc.on('close', (exitCode) => {
|
|
457
|
-
clearTimeout(timeoutHandle);
|
|
458
|
-
completionDetector.cleanup();
|
|
459
|
-
this.activeProcess = null;
|
|
460
|
-
|
|
461
|
-
if (stderr) {
|
|
462
|
-
logger.debug(`Claude stderr: ${stderr}`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
resolve({
|
|
466
|
-
output,
|
|
467
|
-
exitCode: exitCode ?? (this.killed ? 130 : 1),
|
|
468
|
-
timedOut,
|
|
469
|
-
contextOverflow,
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
});
|
|
387
|
+
return this._runStreamJson(prompt, options, false);
|
|
473
388
|
}
|
|
474
389
|
|
|
475
390
|
/**
|
|
@@ -485,20 +400,40 @@ export class ClaudeRunner {
|
|
|
485
400
|
* - Default timeout is 60 minutes if not specified
|
|
486
401
|
*/
|
|
487
402
|
async runVerbose(prompt: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
|
|
488
|
-
|
|
403
|
+
return this._runStreamJson(prompt, options, true);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Internal unified execution method using stream-json format.
|
|
408
|
+
* Both run() and runVerbose() delegate to this method.
|
|
409
|
+
*
|
|
410
|
+
* @param verbose - When true, tool descriptions and text are printed to stdout.
|
|
411
|
+
* When false, display output is suppressed but usage data is still captured.
|
|
412
|
+
*/
|
|
413
|
+
private async _runStreamJson(
|
|
414
|
+
prompt: string,
|
|
415
|
+
options: ClaudeRunnerOptions,
|
|
416
|
+
verbose: boolean,
|
|
417
|
+
): Promise<RunResult> {
|
|
418
|
+
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel, verboseCheck } = options;
|
|
489
419
|
// Ensure timeout is a positive number, fallback to 60 minutes
|
|
490
420
|
const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
|
|
491
421
|
const timeoutMs = validatedTimeout * 60 * 1000;
|
|
492
422
|
|
|
423
|
+
// When verboseCheck callback is provided, use it to dynamically determine display.
|
|
424
|
+
// Otherwise, fall back to the static verbose parameter.
|
|
425
|
+
const shouldDisplay = verboseCheck ?? (() => verbose);
|
|
426
|
+
|
|
493
427
|
return new Promise((resolve) => {
|
|
494
428
|
let output = '';
|
|
495
429
|
let stderr = '';
|
|
496
430
|
let timedOut = false;
|
|
497
431
|
let contextOverflow = false;
|
|
432
|
+
let usageData: import('../types/config.js').UsageData | undefined;
|
|
498
433
|
|
|
499
434
|
const claudePath = getClaudePath();
|
|
500
435
|
|
|
501
|
-
logger.debug(`Starting Claude execution session (
|
|
436
|
+
logger.debug(`Starting Claude execution session (stream-json, verbose=${verbose}) with model: ${this.model}`);
|
|
502
437
|
logger.debug(`Prompt length: ${prompt.length}, timeout: ${timeoutMs}ms, cwd: ${cwd}`);
|
|
503
438
|
logger.debug(`Claude path: ${claudePath}`);
|
|
504
439
|
|
|
@@ -509,7 +444,7 @@ export class ClaudeRunner {
|
|
|
509
444
|
|
|
510
445
|
logger.debug('Spawning process...');
|
|
511
446
|
// Use --output-format stream-json --verbose to get real-time streaming events
|
|
512
|
-
// including tool calls, file operations, and
|
|
447
|
+
// including tool calls, file operations, and token usage in the result event.
|
|
513
448
|
// --dangerously-skip-permissions bypasses interactive prompts
|
|
514
449
|
// -p enables print mode (non-interactive)
|
|
515
450
|
const proc = spawn(claudePath, [
|
|
@@ -565,17 +500,17 @@ export class ClaudeRunner {
|
|
|
565
500
|
const line = lineBuffer.substring(0, newlineIndex);
|
|
566
501
|
lineBuffer = lineBuffer.substring(newlineIndex + 1);
|
|
567
502
|
|
|
568
|
-
const
|
|
503
|
+
const rendered = renderStreamEvent(line);
|
|
569
504
|
|
|
570
|
-
if (textContent) {
|
|
571
|
-
output += textContent;
|
|
505
|
+
if (rendered.textContent) {
|
|
506
|
+
output += rendered.textContent;
|
|
572
507
|
|
|
573
508
|
// Check for completion marker to start grace period
|
|
574
509
|
completionDetector.checkOutput(output);
|
|
575
510
|
|
|
576
511
|
// Check for context overflow
|
|
577
512
|
for (const pattern of CONTEXT_OVERFLOW_PATTERNS) {
|
|
578
|
-
if (pattern.test(textContent)) {
|
|
513
|
+
if (pattern.test(rendered.textContent)) {
|
|
579
514
|
contextOverflow = true;
|
|
580
515
|
logger.warn('Context overflow detected');
|
|
581
516
|
proc.kill('SIGTERM');
|
|
@@ -584,8 +519,13 @@ export class ClaudeRunner {
|
|
|
584
519
|
}
|
|
585
520
|
}
|
|
586
521
|
|
|
587
|
-
|
|
588
|
-
|
|
522
|
+
// Capture usage data from result events
|
|
523
|
+
if (rendered.usageData) {
|
|
524
|
+
usageData = rendered.usageData;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (shouldDisplay() && rendered.display) {
|
|
528
|
+
process.stdout.write(rendered.display);
|
|
589
529
|
}
|
|
590
530
|
}
|
|
591
531
|
});
|
|
@@ -598,12 +538,15 @@ export class ClaudeRunner {
|
|
|
598
538
|
proc.on('close', (exitCode) => {
|
|
599
539
|
// Process any remaining data in the line buffer
|
|
600
540
|
if (lineBuffer.trim()) {
|
|
601
|
-
const
|
|
602
|
-
if (textContent) {
|
|
603
|
-
output += textContent;
|
|
541
|
+
const rendered = renderStreamEvent(lineBuffer);
|
|
542
|
+
if (rendered.textContent) {
|
|
543
|
+
output += rendered.textContent;
|
|
544
|
+
}
|
|
545
|
+
if (rendered.usageData) {
|
|
546
|
+
usageData = rendered.usageData;
|
|
604
547
|
}
|
|
605
|
-
if (display) {
|
|
606
|
-
process.stdout.write(display);
|
|
548
|
+
if (shouldDisplay() && rendered.display) {
|
|
549
|
+
process.stdout.write(rendered.display);
|
|
607
550
|
}
|
|
608
551
|
}
|
|
609
552
|
|
|
@@ -621,6 +564,7 @@ export class ClaudeRunner {
|
|
|
621
564
|
exitCode: exitCode ?? (this.killed ? 130 : 1),
|
|
622
565
|
timedOut,
|
|
623
566
|
contextOverflow,
|
|
567
|
+
usageData,
|
|
624
568
|
});
|
|
625
569
|
});
|
|
626
570
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
+
import { getModel, getClaudeCommand } from '../utils/config.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Failure types that can be detected programmatically without using the API.
|
|
@@ -212,8 +213,9 @@ function extractRelevantOutput(output: string, maxLines: number): string {
|
|
|
212
213
|
* Get the path to Claude CLI.
|
|
213
214
|
*/
|
|
214
215
|
function getClaudePath(): string {
|
|
216
|
+
const cmd = getClaudeCommand();
|
|
215
217
|
try {
|
|
216
|
-
return execSync(
|
|
218
|
+
return execSync(`which ${cmd}`, { encoding: 'utf-8' }).trim();
|
|
217
219
|
} catch {
|
|
218
220
|
throw new Error('Claude CLI not found. Please ensure it is installed and in your PATH.');
|
|
219
221
|
}
|
|
@@ -306,9 +308,10 @@ Respond with ONLY a markdown report in this exact format:
|
|
|
306
308
|
|
|
307
309
|
const claudePath = getClaudePath();
|
|
308
310
|
|
|
309
|
-
// Use
|
|
311
|
+
// Use configured model for failure analysis
|
|
312
|
+
const failureModel = getModel('failureAnalysis');
|
|
310
313
|
const proc = spawn(claudePath, [
|
|
311
|
-
'--model',
|
|
314
|
+
'--model', failureModel,
|
|
312
315
|
'--dangerously-skip-permissions',
|
|
313
316
|
'-p',
|
|
314
317
|
prompt,
|
package/src/core/git.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { execSync } from 'node:child_process';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { logger } from '../utils/logger.js';
|
|
4
4
|
import { extractProjectNumber, extractProjectName } from '../utils/paths.js';
|
|
5
|
+
import { getCommitFormat, getCommitPrefix, renderCommitMessage } from '../utils/config.js';
|
|
5
6
|
|
|
6
7
|
export interface GitStatus {
|
|
7
8
|
isRepo: boolean;
|
|
@@ -246,9 +247,15 @@ export async function commitPlanningArtifacts(projectPath: string, options?: { c
|
|
|
246
247
|
const inputFile = path.join(projectPath, 'input.md');
|
|
247
248
|
const decisionsFile = path.join(projectPath, 'decisions.md');
|
|
248
249
|
|
|
249
|
-
// Build commit message
|
|
250
|
-
const
|
|
251
|
-
const
|
|
250
|
+
// Build commit message from config template
|
|
251
|
+
const formatType = options?.isAmend ? 'amend' as const : 'plan' as const;
|
|
252
|
+
const template = getCommitFormat(formatType);
|
|
253
|
+
const commitPrefix = getCommitPrefix();
|
|
254
|
+
const commitMessage = renderCommitMessage(template, {
|
|
255
|
+
prefix: commitPrefix,
|
|
256
|
+
projectId: projectNumber,
|
|
257
|
+
projectName,
|
|
258
|
+
});
|
|
252
259
|
|
|
253
260
|
// Build list of files to stage (absolute paths)
|
|
254
261
|
const absoluteFiles = [inputFile, decisionsFile, ...(options?.additionalFiles ?? [])];
|
package/src/core/pull-request.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { getModel, getClaudeCommand, getModelShortName } from '../utils/config.js';
|
|
6
7
|
import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN } from '../utils/paths.js';
|
|
7
8
|
|
|
8
9
|
export interface PrCreateResult {
|
|
@@ -222,7 +223,7 @@ export function readProjectContext(projectPath: string): {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
/**
|
|
225
|
-
* Generate a PR body by calling Claude
|
|
226
|
+
* Generate a PR body by calling Claude to summarize project context.
|
|
226
227
|
* Falls back to a simple body if Claude is unavailable.
|
|
227
228
|
*/
|
|
228
229
|
export async function generatePrBody(
|
|
@@ -278,6 +279,8 @@ Respond with ONLY the PR body in this exact format (no extra text, no code fence
|
|
|
278
279
|
[1-3 bullet points describing how to verify these changes work correctly]`;
|
|
279
280
|
|
|
280
281
|
try {
|
|
282
|
+
const prModel = getModelShortName(getModel('prGeneration'));
|
|
283
|
+
logger.info(`Generating PR with ${prModel}...`);
|
|
281
284
|
const body = await callClaudeForPrBody(prompt, timeoutMs);
|
|
282
285
|
return body;
|
|
283
286
|
} catch (error) {
|
|
@@ -348,12 +351,13 @@ export function filterClaudeOutput(output: string): string {
|
|
|
348
351
|
}
|
|
349
352
|
|
|
350
353
|
/**
|
|
351
|
-
* Call Claude
|
|
354
|
+
* Call Claude to generate a PR body.
|
|
352
355
|
*/
|
|
353
356
|
async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<string> {
|
|
357
|
+
const cmd = getClaudeCommand();
|
|
354
358
|
let claudePath: string;
|
|
355
359
|
try {
|
|
356
|
-
claudePath = execSync(
|
|
360
|
+
claudePath = execSync(`which ${cmd}`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
357
361
|
} catch {
|
|
358
362
|
throw new Error('Claude CLI not found');
|
|
359
363
|
}
|
|
@@ -362,8 +366,9 @@ async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<s
|
|
|
362
366
|
let output = '';
|
|
363
367
|
let stderr = '';
|
|
364
368
|
|
|
369
|
+
const prModel = getModel('prGeneration');
|
|
365
370
|
const proc = spawn(claudePath, [
|
|
366
|
-
'--model',
|
|
371
|
+
'--model', prModel,
|
|
367
372
|
'--dangerously-skip-permissions',
|
|
368
373
|
'-p',
|
|
369
374
|
prompt,
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createPlanCommand } from './commands/plan.js';
|
|
|
5
5
|
import { createDoCommand } from './commands/do.js';
|
|
6
6
|
import { createStatusCommand } from './commands/status.js';
|
|
7
7
|
import { createMigrateCommand } from './commands/migrate.js';
|
|
8
|
+
import { createConfigCommand } from './commands/config.js';
|
|
8
9
|
import { getVersion } from './utils/version.js';
|
|
9
10
|
|
|
10
11
|
const program = new Command();
|
|
@@ -18,5 +19,6 @@ program.addCommand(createPlanCommand());
|
|
|
18
19
|
program.addCommand(createDoCommand());
|
|
19
20
|
program.addCommand(createStatusCommand());
|
|
20
21
|
program.addCommand(createMigrateCommand());
|
|
22
|
+
program.addCommand(createConfigCommand());
|
|
21
23
|
|
|
22
24
|
program.parse();
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* - system (init): Session initialization info
|
|
6
6
|
* - assistant: Claude's response with text or tool_use content blocks
|
|
7
7
|
* - user: Tool results (tool_result content blocks)
|
|
8
|
-
* - result: Final result with success/failure status
|
|
8
|
+
* - result: Final result with success/failure status and token usage
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { UsageData, ModelTokenUsage } from '../types/config.js';
|
|
12
|
+
|
|
11
13
|
export interface StreamEvent {
|
|
12
14
|
type: string;
|
|
13
15
|
subtype?: string;
|
|
@@ -15,6 +17,18 @@ export interface StreamEvent {
|
|
|
15
17
|
content?: ContentBlock[];
|
|
16
18
|
};
|
|
17
19
|
result?: string;
|
|
20
|
+
usage?: {
|
|
21
|
+
input_tokens?: number;
|
|
22
|
+
output_tokens?: number;
|
|
23
|
+
cache_read_input_tokens?: number;
|
|
24
|
+
cache_creation_input_tokens?: number;
|
|
25
|
+
};
|
|
26
|
+
modelUsage?: Record<string, {
|
|
27
|
+
inputTokens?: number;
|
|
28
|
+
outputTokens?: number;
|
|
29
|
+
cacheReadInputTokens?: number;
|
|
30
|
+
cacheCreationInputTokens?: number;
|
|
31
|
+
}>;
|
|
18
32
|
tool_use_result?: {
|
|
19
33
|
type?: string;
|
|
20
34
|
file?: {
|
|
@@ -72,6 +86,8 @@ export interface RenderResult {
|
|
|
72
86
|
display: string;
|
|
73
87
|
/** Text content to accumulate for output parsing (completion markers, etc.) */
|
|
74
88
|
textContent: string;
|
|
89
|
+
/** Usage data extracted from result events (only present on result events) */
|
|
90
|
+
usageData?: UsageData;
|
|
75
91
|
}
|
|
76
92
|
|
|
77
93
|
/**
|
|
@@ -132,8 +148,42 @@ function renderAssistant(event: StreamEvent): RenderResult {
|
|
|
132
148
|
return { display, textContent };
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
function renderResult(
|
|
151
|
+
function renderResult(event: StreamEvent): RenderResult {
|
|
136
152
|
// The result event's text duplicates the last assistant message,
|
|
137
|
-
// which is already captured. Skip to avoid double-counting.
|
|
138
|
-
|
|
153
|
+
// which is already captured. Skip text to avoid double-counting.
|
|
154
|
+
// But extract usage data if present.
|
|
155
|
+
const usageData = extractUsageData(event);
|
|
156
|
+
return { display: '', textContent: '', usageData };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Extract usage data from a stream-json result event.
|
|
161
|
+
* Returns undefined if no usage data is present.
|
|
162
|
+
*/
|
|
163
|
+
function extractUsageData(event: StreamEvent): UsageData | undefined {
|
|
164
|
+
if (!event.usage && !event.modelUsage) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const usage = event.usage;
|
|
169
|
+
const modelUsage: Record<string, ModelTokenUsage> = {};
|
|
170
|
+
|
|
171
|
+
if (event.modelUsage) {
|
|
172
|
+
for (const [model, data] of Object.entries(event.modelUsage)) {
|
|
173
|
+
modelUsage[model] = {
|
|
174
|
+
inputTokens: data.inputTokens ?? 0,
|
|
175
|
+
outputTokens: data.outputTokens ?? 0,
|
|
176
|
+
cacheReadInputTokens: data.cacheReadInputTokens ?? 0,
|
|
177
|
+
cacheCreationInputTokens: data.cacheCreationInputTokens ?? 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
184
|
+
outputTokens: usage?.output_tokens ?? 0,
|
|
185
|
+
cacheReadInputTokens: usage?.cache_read_input_tokens ?? 0,
|
|
186
|
+
cacheCreationInputTokens: usage?.cache_creation_input_tokens ?? 0,
|
|
187
|
+
modelUsage,
|
|
188
|
+
};
|
|
139
189
|
}
|