rafcode 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/CLAUDE.md +59 -11
  3. package/RAF/ahslfe-config-wizard/decisions.md +34 -0
  4. package/RAF/ahslfe-config-wizard/input.md +1 -0
  5. package/RAF/ahslfe-config-wizard/outcomes/01-define-config-schema.md +38 -0
  6. package/RAF/ahslfe-config-wizard/outcomes/02-refactor-codebase-to-use-config.md +67 -0
  7. package/RAF/ahslfe-config-wizard/outcomes/03-create-config-documentation.md +37 -0
  8. package/RAF/ahslfe-config-wizard/outcomes/04-implement-raf-config-command.md +47 -0
  9. package/RAF/ahslfe-config-wizard/outcomes/05-update-claude-md.md +26 -0
  10. package/RAF/ahslfe-config-wizard/plans/01-define-config-schema.md +73 -0
  11. package/RAF/ahslfe-config-wizard/plans/02-refactor-codebase-to-use-config.md +74 -0
  12. package/RAF/ahslfe-config-wizard/plans/03-create-config-documentation.md +57 -0
  13. package/RAF/ahslfe-config-wizard/plans/04-implement-raf-config-command.md +66 -0
  14. package/RAF/ahslfe-config-wizard/plans/05-update-claude-md.md +60 -0
  15. package/RAF/ahstvo-token-tracker/decisions.md +44 -0
  16. package/RAF/ahstvo-token-tracker/input.md +3 -0
  17. package/RAF/ahstvo-token-tracker/outcomes/01-full-model-id-support.md +43 -0
  18. package/RAF/ahstvo-token-tracker/outcomes/02-name-generation-no-session.md +33 -0
  19. package/RAF/ahstvo-token-tracker/outcomes/03-unify-stream-json-execution.md +48 -0
  20. package/RAF/ahstvo-token-tracker/outcomes/04-token-tracking-cost-calculation.md +53 -0
  21. package/RAF/ahstvo-token-tracker/outcomes/05-token-cost-console-reporting.md +57 -0
  22. package/RAF/ahstvo-token-tracker/outcomes/06-runtime-verbose-toggle.md +53 -0
  23. package/RAF/ahstvo-token-tracker/outcomes/07-readme-config-docs.md +36 -0
  24. package/RAF/ahstvo-token-tracker/plans/01-full-model-id-support.md +35 -0
  25. package/RAF/ahstvo-token-tracker/plans/02-name-generation-no-session.md +36 -0
  26. package/RAF/ahstvo-token-tracker/plans/03-unify-stream-json-execution.md +44 -0
  27. package/RAF/ahstvo-token-tracker/plans/04-token-tracking-cost-calculation.md +56 -0
  28. package/RAF/ahstvo-token-tracker/plans/05-token-cost-console-reporting.md +55 -0
  29. package/RAF/ahstvo-token-tracker/plans/06-runtime-verbose-toggle.md +48 -0
  30. package/RAF/ahstvo-token-tracker/plans/07-readme-config-docs.md +44 -0
  31. package/README.md +34 -0
  32. package/dist/commands/config.d.ts +3 -0
  33. package/dist/commands/config.d.ts.map +1 -0
  34. package/dist/commands/config.js +173 -0
  35. package/dist/commands/config.js.map +1 -0
  36. package/dist/commands/do.d.ts.map +1 -1
  37. package/dist/commands/do.js +50 -28
  38. package/dist/commands/do.js.map +1 -1
  39. package/dist/commands/plan.d.ts.map +1 -1
  40. package/dist/commands/plan.js +3 -2
  41. package/dist/commands/plan.js.map +1 -1
  42. package/dist/core/claude-runner.d.ts +17 -13
  43. package/dist/core/claude-runner.d.ts.map +1 -1
  44. package/dist/core/claude-runner.js +42 -257
  45. package/dist/core/claude-runner.js.map +1 -1
  46. package/dist/core/failure-analyzer.d.ts.map +1 -1
  47. package/dist/core/failure-analyzer.js +6 -3
  48. package/dist/core/failure-analyzer.js.map +1 -1
  49. package/dist/core/git.d.ts.map +1 -1
  50. package/dist/core/git.js +10 -3
  51. package/dist/core/git.js.map +1 -1
  52. package/dist/core/pull-request.d.ts +1 -1
  53. package/dist/core/pull-request.d.ts.map +1 -1
  54. package/dist/core/pull-request.js +7 -4
  55. package/dist/core/pull-request.js.map +1 -1
  56. package/dist/core/shutdown-handler.d.ts.map +1 -1
  57. package/dist/core/shutdown-handler.js +0 -4
  58. package/dist/core/shutdown-handler.js.map +1 -1
  59. package/dist/index.js +2 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/parsers/stream-renderer.d.ts +16 -4
  62. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  63. package/dist/parsers/stream-renderer.js +35 -5
  64. package/dist/parsers/stream-renderer.js.map +1 -1
  65. package/dist/prompts/execution.d.ts.map +1 -1
  66. package/dist/prompts/execution.js +11 -1
  67. package/dist/prompts/execution.js.map +1 -1
  68. package/dist/types/config.d.ts +95 -5
  69. package/dist/types/config.d.ts.map +1 -1
  70. package/dist/types/config.js +63 -3
  71. package/dist/types/config.js.map +1 -1
  72. package/dist/utils/config.d.ts +59 -7
  73. package/dist/utils/config.d.ts.map +1 -1
  74. package/dist/utils/config.js +276 -21
  75. package/dist/utils/config.js.map +1 -1
  76. package/dist/utils/name-generator.d.ts +3 -7
  77. package/dist/utils/name-generator.d.ts.map +1 -1
  78. package/dist/utils/name-generator.js +75 -61
  79. package/dist/utils/name-generator.js.map +1 -1
  80. package/dist/utils/terminal-symbols.d.ts +21 -0
  81. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  82. package/dist/utils/terminal-symbols.js +62 -0
  83. package/dist/utils/terminal-symbols.js.map +1 -1
  84. package/dist/utils/token-tracker.d.ts +45 -0
  85. package/dist/utils/token-tracker.d.ts.map +1 -0
  86. package/dist/utils/token-tracker.js +107 -0
  87. package/dist/utils/token-tracker.js.map +1 -0
  88. package/dist/utils/validation.d.ts +5 -5
  89. package/dist/utils/validation.d.ts.map +1 -1
  90. package/dist/utils/validation.js +10 -6
  91. package/dist/utils/validation.js.map +1 -1
  92. package/dist/utils/verbose-toggle.d.ts +33 -0
  93. package/dist/utils/verbose-toggle.d.ts.map +1 -0
  94. package/dist/utils/verbose-toggle.js +94 -0
  95. package/dist/utils/verbose-toggle.js.map +1 -0
  96. package/package.json +1 -1
  97. package/src/commands/config.ts +204 -0
  98. package/src/commands/do.ts +59 -27
  99. package/src/commands/plan.ts +3 -2
  100. package/src/core/claude-runner.ts +58 -311
  101. package/src/core/failure-analyzer.ts +6 -3
  102. package/src/core/git.ts +10 -3
  103. package/src/core/pull-request.ts +7 -4
  104. package/src/core/shutdown-handler.ts +0 -5
  105. package/src/index.ts +2 -0
  106. package/src/parsers/stream-renderer.ts +55 -8
  107. package/src/prompts/config-docs.md +331 -0
  108. package/src/prompts/execution.ts +13 -1
  109. package/src/types/config.ts +156 -8
  110. package/src/utils/config.ts +335 -21
  111. package/src/utils/name-generator.ts +84 -71
  112. package/src/utils/terminal-symbols.ts +68 -0
  113. package/src/utils/token-tracker.ts +135 -0
  114. package/src/utils/validation.ts +15 -10
  115. package/src/utils/verbose-toggle.ts +103 -0
  116. package/tests/unit/claude-runner.test.ts +216 -403
  117. package/tests/unit/config-command.test.ts +163 -0
  118. package/tests/unit/config.test.ts +608 -30
  119. package/tests/unit/name-generator.test.ts +99 -75
  120. package/tests/unit/pull-request.test.ts +2 -0
  121. package/tests/unit/stream-renderer.test.ts +83 -30
  122. package/tests/unit/terminal-symbols.test.ts +157 -0
  123. package/tests/unit/token-tracker.test.ts +352 -0
  124. package/tests/unit/verbose-toggle.test.ts +204 -0
  125. package/RAF/ahrtxf-session-sentinel/decisions.md +0 -19
  126. package/RAF/ahrtxf-session-sentinel/input.md +0 -1
  127. package/RAF/ahrtxf-session-sentinel/outcomes/01-capture-session-id.md +0 -37
  128. package/RAF/ahrtxf-session-sentinel/outcomes/02-resume-flag.md +0 -45
  129. package/RAF/ahrtxf-session-sentinel/plans/01-capture-session-id.md +0 -41
  130. package/RAF/ahrtxf-session-sentinel/plans/02-resume-flag.md +0 -51
@@ -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('which claude', { encoding: 'utf-8' }).trim();
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,7 +80,8 @@ export interface RunResult {
71
80
  exitCode: number;
72
81
  timedOut: boolean;
73
82
  contextOverflow: boolean;
74
- sessionId?: string;
83
+ /** Token usage data from the stream-json result event. */
84
+ usageData?: UsageData;
75
85
  }
76
86
 
77
87
  const CONTEXT_OVERFLOW_PATTERNS = [
@@ -259,18 +269,9 @@ export class ClaudeRunner {
259
269
  private activeProcess: pty.IPty | null = null;
260
270
  private killed = false;
261
271
  private model: string;
262
- private _sessionId: string | undefined;
263
272
 
264
273
  constructor(config: ClaudeRunnerConfig = {}) {
265
- this.model = config.model ?? 'opus';
266
- }
267
-
268
- /**
269
- * Get the session ID captured from the most recent Claude session.
270
- * Available after the system.init event is received during run() or runVerbose().
271
- */
272
- get sessionId(): string | undefined {
273
- return this._sessionId;
274
+ this.model = config.model ?? getModel('execute');
274
275
  }
275
276
 
276
277
  /**
@@ -372,8 +373,8 @@ export class ClaudeRunner {
372
373
 
373
374
  /**
374
375
  * Run Claude non-interactively and collect output.
375
- * Used for execution phase where we parse the results.
376
- * Uses child_process.spawn with -p flag for prompt (like ralphy).
376
+ * Uses stream-json format internally to capture token usage data.
377
+ * Tool display is suppressed (non-verbose mode).
377
378
  *
378
379
  * TIMEOUT BEHAVIOR:
379
380
  * - The timeout is applied per individual call to this method
@@ -383,147 +384,7 @@ export class ClaudeRunner {
383
384
  * - Default timeout is 60 minutes if not specified
384
385
  */
385
386
  async run(prompt: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
386
- const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel } = options;
387
- // Ensure timeout is a positive number, fallback to 60 minutes
388
- const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
389
- const timeoutMs = validatedTimeout * 60 * 1000;
390
-
391
- return new Promise((resolve) => {
392
- let output = '';
393
- let stderr = '';
394
- let timedOut = false;
395
- let contextOverflow = false;
396
- let sessionId: string | undefined;
397
-
398
- const claudePath = getClaudePath();
399
-
400
- logger.debug(`Starting Claude execution session with model: ${this.model}`);
401
- logger.debug(`Claude path: ${claudePath}`);
402
-
403
- // Build env, optionally injecting effort level
404
- const env = effortLevel
405
- ? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
406
- : process.env;
407
-
408
- // Use --output-format stream-json to get structured events including session_id.
409
- // Unlike runVerbose(), output is parsed silently without writing to stdout.
410
- // --dangerously-skip-permissions bypasses interactive prompts
411
- // -p enables print mode (non-interactive)
412
- const proc = spawn(claudePath, [
413
- '--dangerously-skip-permissions',
414
- '--model',
415
- this.model,
416
- '--append-system-prompt',
417
- prompt,
418
- '--output-format',
419
- 'stream-json',
420
- '--verbose',
421
- '-p',
422
- 'Execute the task as described in the system prompt.',
423
- ], {
424
- cwd,
425
- env,
426
- stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
427
- });
428
-
429
- // Track this process
430
- this.activeProcess = proc as any;
431
-
432
- // Set up timeout
433
- const timeoutHandle = setTimeout(() => {
434
- timedOut = true;
435
- logger.warn('Claude session timed out');
436
- if (sessionId) {
437
- logger.info(`Session ID: ${sessionId}`);
438
- }
439
- proc.kill('SIGTERM');
440
- }, timeoutMs);
441
-
442
- // Set up completion detection (stdout marker + outcome file polling)
443
- const completionDetector = createCompletionDetector(
444
- () => proc.kill('SIGTERM'),
445
- outcomeFilePath,
446
- commitContext,
447
- );
448
-
449
- // Buffer for incomplete NDJSON lines (data chunks may split across line boundaries)
450
- let lineBuffer = '';
451
-
452
- // Collect stdout - parse NDJSON silently (no display output)
453
- proc.stdout.on('data', (data) => {
454
- lineBuffer += data.toString();
455
-
456
- // Process complete lines from the NDJSON stream
457
- let newlineIndex: number;
458
- while ((newlineIndex = lineBuffer.indexOf('\n')) !== -1) {
459
- const line = lineBuffer.substring(0, newlineIndex);
460
- lineBuffer = lineBuffer.substring(newlineIndex + 1);
461
-
462
- const result = renderStreamEvent(line);
463
-
464
- if (result.sessionId && !sessionId) {
465
- sessionId = result.sessionId;
466
- this._sessionId = sessionId;
467
- }
468
-
469
- if (result.textContent) {
470
- output += result.textContent;
471
-
472
- // Check for completion marker to start grace period
473
- completionDetector.checkOutput(output);
474
-
475
- // Check for context overflow
476
- for (const pattern of CONTEXT_OVERFLOW_PATTERNS) {
477
- if (pattern.test(result.textContent)) {
478
- contextOverflow = true;
479
- logger.warn('Context overflow detected');
480
- if (sessionId) {
481
- logger.info(`Session ID: ${sessionId}`);
482
- }
483
- proc.kill('SIGTERM');
484
- break;
485
- }
486
- }
487
- }
488
-
489
- // Silently discard display output (unlike runVerbose)
490
- }
491
- });
492
-
493
- // Collect stderr
494
- proc.stderr.on('data', (data) => {
495
- stderr += data.toString();
496
- });
497
-
498
- proc.on('close', (exitCode) => {
499
- // Process any remaining data in the line buffer
500
- if (lineBuffer.trim()) {
501
- const result = renderStreamEvent(lineBuffer);
502
- if (result.sessionId && !sessionId) {
503
- sessionId = result.sessionId;
504
- }
505
- if (result.textContent) {
506
- output += result.textContent;
507
- }
508
- }
509
-
510
- clearTimeout(timeoutHandle);
511
- completionDetector.cleanup();
512
- this.activeProcess = null;
513
-
514
- if (stderr) {
515
- logger.debug(`Claude stderr: ${stderr}`);
516
- }
517
-
518
- resolve({
519
- output,
520
- exitCode: exitCode ?? (this.killed ? 130 : 1),
521
- timedOut,
522
- contextOverflow,
523
- sessionId,
524
- });
525
- });
526
- });
387
+ return this._runStreamJson(prompt, options, false);
527
388
  }
528
389
 
529
390
  /**
@@ -539,21 +400,40 @@ export class ClaudeRunner {
539
400
  * - Default timeout is 60 minutes if not specified
540
401
  */
541
402
  async runVerbose(prompt: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
542
- const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel } = options;
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;
543
419
  // Ensure timeout is a positive number, fallback to 60 minutes
544
420
  const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
545
421
  const timeoutMs = validatedTimeout * 60 * 1000;
546
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
+
547
427
  return new Promise((resolve) => {
548
428
  let output = '';
549
429
  let stderr = '';
550
430
  let timedOut = false;
551
431
  let contextOverflow = false;
552
- let sessionId: string | undefined;
432
+ let usageData: import('../types/config.js').UsageData | undefined;
553
433
 
554
434
  const claudePath = getClaudePath();
555
435
 
556
- logger.debug(`Starting Claude execution session (verbose/stream-json) with model: ${this.model}`);
436
+ logger.debug(`Starting Claude execution session (stream-json, verbose=${verbose}) with model: ${this.model}`);
557
437
  logger.debug(`Prompt length: ${prompt.length}, timeout: ${timeoutMs}ms, cwd: ${cwd}`);
558
438
  logger.debug(`Claude path: ${claudePath}`);
559
439
 
@@ -564,7 +444,7 @@ export class ClaudeRunner {
564
444
 
565
445
  logger.debug('Spawning process...');
566
446
  // Use --output-format stream-json --verbose to get real-time streaming events
567
- // including tool calls, file operations, and intermediate output.
447
+ // including tool calls, file operations, and token usage in the result event.
568
448
  // --dangerously-skip-permissions bypasses interactive prompts
569
449
  // -p enables print mode (non-interactive)
570
450
  const proc = spawn(claudePath, [
@@ -592,9 +472,6 @@ export class ClaudeRunner {
592
472
  const timeoutHandle = setTimeout(() => {
593
473
  timedOut = true;
594
474
  logger.warn('Claude session timed out');
595
- if (sessionId) {
596
- logger.info(`Session ID: ${sessionId}`);
597
- }
598
475
  proc.kill('SIGTERM');
599
476
  }, timeoutMs);
600
477
 
@@ -623,35 +500,32 @@ export class ClaudeRunner {
623
500
  const line = lineBuffer.substring(0, newlineIndex);
624
501
  lineBuffer = lineBuffer.substring(newlineIndex + 1);
625
502
 
626
- const result = renderStreamEvent(line);
503
+ const rendered = renderStreamEvent(line);
627
504
 
628
- if (result.sessionId && !sessionId) {
629
- sessionId = result.sessionId;
630
- this._sessionId = sessionId;
631
- }
632
-
633
- if (result.textContent) {
634
- output += result.textContent;
505
+ if (rendered.textContent) {
506
+ output += rendered.textContent;
635
507
 
636
508
  // Check for completion marker to start grace period
637
509
  completionDetector.checkOutput(output);
638
510
 
639
511
  // Check for context overflow
640
512
  for (const pattern of CONTEXT_OVERFLOW_PATTERNS) {
641
- if (pattern.test(result.textContent)) {
513
+ if (pattern.test(rendered.textContent)) {
642
514
  contextOverflow = true;
643
515
  logger.warn('Context overflow detected');
644
- if (sessionId) {
645
- logger.info(`Session ID: ${sessionId}`);
646
- }
647
516
  proc.kill('SIGTERM');
648
517
  break;
649
518
  }
650
519
  }
651
520
  }
652
521
 
653
- if (result.display) {
654
- process.stdout.write(result.display);
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);
655
529
  }
656
530
  }
657
531
  });
@@ -664,15 +538,15 @@ export class ClaudeRunner {
664
538
  proc.on('close', (exitCode) => {
665
539
  // Process any remaining data in the line buffer
666
540
  if (lineBuffer.trim()) {
667
- const result = renderStreamEvent(lineBuffer);
668
- if (result.sessionId && !sessionId) {
669
- sessionId = result.sessionId;
541
+ const rendered = renderStreamEvent(lineBuffer);
542
+ if (rendered.textContent) {
543
+ output += rendered.textContent;
670
544
  }
671
- if (result.textContent) {
672
- output += result.textContent;
545
+ if (rendered.usageData) {
546
+ usageData = rendered.usageData;
673
547
  }
674
- if (result.display) {
675
- process.stdout.write(result.display);
548
+ if (shouldDisplay() && rendered.display) {
549
+ process.stdout.write(rendered.display);
676
550
  }
677
551
  }
678
552
 
@@ -690,134 +564,7 @@ export class ClaudeRunner {
690
564
  exitCode: exitCode ?? (this.killed ? 130 : 1),
691
565
  timedOut,
692
566
  contextOverflow,
693
- sessionId,
694
- });
695
- });
696
- });
697
- }
698
-
699
- /**
700
- * Resume an interrupted Claude session by session ID.
701
- * Spawns Claude with --resume flag only — no prompt, model, or system prompt flags.
702
- * Uses stream-json output format for session ID extraction and completion detection.
703
- */
704
- async runResume(sessionId: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
705
- const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel } = options;
706
- const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
707
- const timeoutMs = validatedTimeout * 60 * 1000;
708
-
709
- return new Promise((resolve) => {
710
- let output = '';
711
- let stderr = '';
712
- let timedOut = false;
713
- let contextOverflow = false;
714
- let newSessionId: string | undefined;
715
-
716
- const claudePath = getClaudePath();
717
-
718
- logger.debug(`Resuming Claude session: ${sessionId}`);
719
-
720
- const env = effortLevel
721
- ? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
722
- : process.env;
723
-
724
- // Resume with minimal flags: --resume, --dangerously-skip-permissions, --output-format stream-json --verbose
725
- // Do NOT pass --model, --append-system-prompt, or -p — Claude restores these from the session
726
- const proc = spawn(claudePath, [
727
- '--resume',
728
- sessionId,
729
- '--dangerously-skip-permissions',
730
- '--output-format',
731
- 'stream-json',
732
- '--verbose',
733
- ], {
734
- cwd,
735
- env,
736
- stdio: ['ignore', 'pipe', 'pipe'],
737
- });
738
-
739
- this.activeProcess = proc as any;
740
-
741
- const timeoutHandle = setTimeout(() => {
742
- timedOut = true;
743
- logger.warn('Resumed Claude session timed out');
744
- if (newSessionId) {
745
- logger.info(`Session ID: ${newSessionId}`);
746
- }
747
- proc.kill('SIGTERM');
748
- }, timeoutMs);
749
-
750
- const completionDetector = createCompletionDetector(
751
- () => proc.kill('SIGTERM'),
752
- outcomeFilePath,
753
- commitContext,
754
- );
755
-
756
- let lineBuffer = '';
757
-
758
- proc.stdout.on('data', (data) => {
759
- lineBuffer += data.toString();
760
-
761
- let newlineIndex: number;
762
- while ((newlineIndex = lineBuffer.indexOf('\n')) !== -1) {
763
- const line = lineBuffer.substring(0, newlineIndex);
764
- lineBuffer = lineBuffer.substring(newlineIndex + 1);
765
-
766
- const result = renderStreamEvent(line);
767
-
768
- if (result.sessionId && !newSessionId) {
769
- newSessionId = result.sessionId;
770
- this._sessionId = newSessionId;
771
- }
772
-
773
- if (result.textContent) {
774
- output += result.textContent;
775
- completionDetector.checkOutput(output);
776
-
777
- for (const pattern of CONTEXT_OVERFLOW_PATTERNS) {
778
- if (pattern.test(result.textContent)) {
779
- contextOverflow = true;
780
- logger.warn('Context overflow detected');
781
- if (newSessionId) {
782
- logger.info(`Session ID: ${newSessionId}`);
783
- }
784
- proc.kill('SIGTERM');
785
- break;
786
- }
787
- }
788
- }
789
- }
790
- });
791
-
792
- proc.stderr.on('data', (data) => {
793
- stderr += data.toString();
794
- });
795
-
796
- proc.on('close', (exitCode) => {
797
- if (lineBuffer.trim()) {
798
- const result = renderStreamEvent(lineBuffer);
799
- if (result.sessionId && !newSessionId) {
800
- newSessionId = result.sessionId;
801
- }
802
- if (result.textContent) {
803
- output += result.textContent;
804
- }
805
- }
806
-
807
- clearTimeout(timeoutHandle);
808
- completionDetector.cleanup();
809
- this.activeProcess = null;
810
-
811
- if (stderr) {
812
- logger.debug(`Claude stderr: ${stderr}`);
813
- }
814
-
815
- resolve({
816
- output,
817
- exitCode: exitCode ?? (this.killed ? 130 : 1),
818
- timedOut,
819
- contextOverflow,
820
- sessionId: newSessionId,
567
+ usageData,
821
568
  });
822
569
  });
823
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('which claude', { encoding: 'utf-8' }).trim();
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 haiku model for fast, cost-effective analysis
311
+ // Use configured model for failure analysis
312
+ const failureModel = getModel('failureAnalysis');
310
313
  const proc = spawn(claudePath, [
311
- '--model', 'haiku',
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 prefix = options?.isAmend ? 'Amend' : 'Plan';
251
- const commitMessage = `RAF[${projectNumber}] ${prefix}: ${projectName}`;
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 ?? [])];
@@ -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 } 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 Sonnet to summarize project context.
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(
@@ -348,12 +349,13 @@ export function filterClaudeOutput(output: string): string {
348
349
  }
349
350
 
350
351
  /**
351
- * Call Claude Sonnet to generate a PR body.
352
+ * Call Claude to generate a PR body.
352
353
  */
353
354
  async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<string> {
355
+ const cmd = getClaudeCommand();
354
356
  let claudePath: string;
355
357
  try {
356
- claudePath = execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' }).trim();
358
+ claudePath = execSync(`which ${cmd}`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
357
359
  } catch {
358
360
  throw new Error('Claude CLI not found');
359
361
  }
@@ -362,8 +364,9 @@ async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<s
362
364
  let output = '';
363
365
  let stderr = '';
364
366
 
367
+ const prModel = getModel('prGeneration');
365
368
  const proc = spawn(claudePath, [
366
- '--model', 'sonnet',
369
+ '--model', prModel,
367
370
  '--dangerously-skip-permissions',
368
371
  '-p',
369
372
  prompt,
@@ -54,11 +54,6 @@ class ShutdownHandler {
54
54
  this.isShuttingDown = true;
55
55
  logger.info(`\nReceived ${signal}, shutting down gracefully...`);
56
56
 
57
- // Print session ID if available (for resumption/debugging)
58
- if (this.claudeRunner?.sessionId) {
59
- logger.info(`Session ID: ${this.claudeRunner.sessionId}`);
60
- }
61
-
62
57
  // Kill Claude process if running
63
58
  if (this.claudeRunner?.isRunning()) {
64
59
  logger.debug('Killing Claude process...');
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();