rafcode 2.4.0 → 2.5.0-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 (108) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CLAUDE.md +7 -5
  3. package/RAF/ahwidh-quick-fix-gremlin/decisions.md +37 -0
  4. package/RAF/ahwidh-quick-fix-gremlin/input.md +35 -0
  5. package/RAF/ahwidh-quick-fix-gremlin/outcomes/01-fix-name-generation-prompt.md +33 -0
  6. package/RAF/ahwidh-quick-fix-gremlin/outcomes/02-fix-amend-commit-scope.md +43 -0
  7. package/RAF/ahwidh-quick-fix-gremlin/outcomes/03-fix-diverged-main-branch-sync.md +32 -0
  8. package/RAF/ahwidh-quick-fix-gremlin/outcomes/04-wire-rate-limit-to-do-command.md +61 -0
  9. package/RAF/ahwidh-quick-fix-gremlin/outcomes/05-add-config-get-set-flags.md +125 -0
  10. package/RAF/ahwidh-quick-fix-gremlin/outcomes/06-sync-worktree-branch-before-execution.md +96 -0
  11. package/RAF/ahwidh-quick-fix-gremlin/outcomes/07-update-frontmatter-format.md +107 -0
  12. package/RAF/ahwidh-quick-fix-gremlin/outcomes/08-remove-plan-token-report.md +76 -0
  13. package/RAF/ahwidh-quick-fix-gremlin/plans/01-fix-name-generation-prompt.md +52 -0
  14. package/RAF/ahwidh-quick-fix-gremlin/plans/02-fix-amend-commit-scope.md +48 -0
  15. package/RAF/ahwidh-quick-fix-gremlin/plans/03-fix-diverged-main-branch-sync.md +49 -0
  16. package/RAF/ahwidh-quick-fix-gremlin/plans/04-wire-rate-limit-to-do-command.md +78 -0
  17. package/RAF/ahwidh-quick-fix-gremlin/plans/05-add-config-get-set-flags.md +101 -0
  18. package/RAF/ahwidh-quick-fix-gremlin/plans/06-sync-worktree-branch-before-execution.md +92 -0
  19. package/RAF/ahwidh-quick-fix-gremlin/plans/07-update-frontmatter-format.md +105 -0
  20. package/RAF/ahwidh-quick-fix-gremlin/plans/08-remove-plan-token-report.md +50 -0
  21. package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
  22. package/RAF/ahwqwq-model-whisperer/input.md +5 -0
  23. package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
  24. package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
  25. package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
  26. package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
  27. package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
  28. package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
  29. package/dist/commands/config.d.ts.map +1 -1
  30. package/dist/commands/config.js +209 -1
  31. package/dist/commands/config.js.map +1 -1
  32. package/dist/commands/do.d.ts.map +1 -1
  33. package/dist/commands/do.js +37 -8
  34. package/dist/commands/do.js.map +1 -1
  35. package/dist/commands/plan.d.ts.map +1 -1
  36. package/dist/commands/plan.js +92 -54
  37. package/dist/commands/plan.js.map +1 -1
  38. package/dist/core/claude-runner.d.ts +8 -6
  39. package/dist/core/claude-runner.d.ts.map +1 -1
  40. package/dist/core/claude-runner.js +73 -5
  41. package/dist/core/claude-runner.js.map +1 -1
  42. package/dist/core/worktree.d.ts +12 -0
  43. package/dist/core/worktree.d.ts.map +1 -1
  44. package/dist/core/worktree.js +33 -1
  45. package/dist/core/worktree.js.map +1 -1
  46. package/dist/parsers/stream-renderer.d.ts +2 -0
  47. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  48. package/dist/parsers/stream-renderer.js +2 -0
  49. package/dist/parsers/stream-renderer.js.map +1 -1
  50. package/dist/prompts/amend.d.ts.map +1 -1
  51. package/dist/prompts/amend.js +3 -1
  52. package/dist/prompts/amend.js.map +1 -1
  53. package/dist/prompts/planning.d.ts.map +1 -1
  54. package/dist/prompts/planning.js +3 -1
  55. package/dist/prompts/planning.js.map +1 -1
  56. package/dist/types/config.d.ts +4 -24
  57. package/dist/types/config.d.ts.map +1 -1
  58. package/dist/types/config.js +0 -24
  59. package/dist/types/config.js.map +1 -1
  60. package/dist/utils/config.d.ts +1 -26
  61. package/dist/utils/config.d.ts.map +1 -1
  62. package/dist/utils/config.js +2 -98
  63. package/dist/utils/config.js.map +1 -1
  64. package/dist/utils/frontmatter.d.ts +13 -3
  65. package/dist/utils/frontmatter.d.ts.map +1 -1
  66. package/dist/utils/frontmatter.js +40 -10
  67. package/dist/utils/frontmatter.js.map +1 -1
  68. package/dist/utils/name-generator.d.ts.map +1 -1
  69. package/dist/utils/name-generator.js +7 -16
  70. package/dist/utils/name-generator.js.map +1 -1
  71. package/dist/utils/terminal-symbols.d.ts +7 -16
  72. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  73. package/dist/utils/terminal-symbols.js +16 -42
  74. package/dist/utils/terminal-symbols.js.map +1 -1
  75. package/dist/utils/token-tracker.d.ts +4 -30
  76. package/dist/utils/token-tracker.d.ts.map +1 -1
  77. package/dist/utils/token-tracker.js +17 -98
  78. package/dist/utils/token-tracker.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/commands/config.ts +242 -0
  81. package/src/commands/do.ts +39 -7
  82. package/src/commands/plan.ts +101 -58
  83. package/src/core/claude-runner.ts +82 -12
  84. package/src/core/worktree.ts +37 -1
  85. package/src/parsers/stream-renderer.ts +4 -0
  86. package/src/prompts/amend.ts +3 -1
  87. package/src/prompts/config-docs.md +1 -72
  88. package/src/prompts/planning.ts +3 -1
  89. package/src/types/config.ts +4 -52
  90. package/src/utils/config.ts +2 -112
  91. package/src/utils/frontmatter.ts +41 -11
  92. package/src/utils/name-generator.ts +7 -16
  93. package/src/utils/terminal-symbols.ts +16 -46
  94. package/src/utils/token-tracker.ts +19 -113
  95. package/tests/unit/claude-runner.test.ts +1 -0
  96. package/tests/unit/commit-planning-artifacts-worktree.test.ts +6 -14
  97. package/tests/unit/commit-planning-artifacts.test.ts +4 -12
  98. package/tests/unit/config-command.test.ts +161 -0
  99. package/tests/unit/config.test.ts +6 -148
  100. package/tests/unit/frontmatter.test.ts +95 -1
  101. package/tests/unit/name-generator.test.ts +1 -1
  102. package/tests/unit/post-execution-picker.test.ts +1 -0
  103. package/tests/unit/stream-renderer.test.ts +82 -0
  104. package/tests/unit/terminal-symbols.test.ts +86 -124
  105. package/tests/unit/token-tracker.test.ts +159 -679
  106. package/tests/unit/worktree.test.ts +68 -1
  107. package/src/utils/session-parser.ts +0 -161
  108. package/tests/unit/session-parser.test.ts +0 -301
@@ -1,6 +1,5 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import * as crypto from 'node:crypto';
4
3
  import { Command } from 'commander';
5
4
  import { ProjectManager } from '../core/project-manager.js';
6
5
  import { ClaudeRunner } from '../core/claude-runner.js';
@@ -16,10 +15,7 @@ import {
16
15
  resolveModelOption,
17
16
  } from '../utils/validation.js';
18
17
  import { logger } from '../utils/logger.js';
19
- import { getWorktreeDefault, getModel, getModelShortName, getDisplayConfig, getPricingConfig, getSyncMainBranch } from '../utils/config.js';
20
- import { TokenTracker } from '../utils/token-tracker.js';
21
- import { parseSessionById } from '../utils/session-parser.js';
22
- import { formatTokenTotalSummary, TokenSummaryOptions } from '../utils/terminal-symbols.js';
18
+ import { getWorktreeDefault, getModel, getModelShortName, getSyncMainBranch } from '../utils/config.js';
23
19
  import { generateProjectNames } from '../utils/name-generator.js';
24
20
  import { pickProjectName } from '../ui/name-picker.js';
25
21
  import {
@@ -61,6 +57,7 @@ interface PlanCommandOptions {
61
57
  sonnet?: boolean;
62
58
  auto?: boolean;
63
59
  worktree?: boolean;
60
+ resume?: string;
64
61
  }
65
62
 
66
63
  export function createPlanCommand(): Command {
@@ -75,6 +72,7 @@ export function createPlanCommand(): Command {
75
72
  .option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
76
73
  .option('-y, --auto', "Skip Claude's permission prompts for file operations")
77
74
  .option('-w, --worktree', 'Create a git worktree for isolated planning')
75
+ .option('-r, --resume <identifier>', 'Resume a planning session for an existing project')
78
76
  .action(async (projectName: string | undefined, options: PlanCommandOptions) => {
79
77
  // Validate and resolve model option
80
78
  let model: string;
@@ -88,7 +86,9 @@ export function createPlanCommand(): Command {
88
86
  const autoMode = options.auto ?? false;
89
87
  const worktreeMode = options.worktree ?? getWorktreeDefault();
90
88
 
91
- if (options.amend) {
89
+ if (options.resume) {
90
+ await runResumeCommand(options.resume, model);
91
+ } else if (options.amend) {
92
92
  if (!projectName) {
93
93
  logger.error('--amend requires a project identifier');
94
94
  logger.error('Usage: raf plan <project> --amend');
@@ -289,25 +289,17 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
289
289
  worktreeMode,
290
290
  });
291
291
 
292
- // Generate session ID for token tracking
293
- const sessionId = crypto.randomUUID();
294
- const sessionCwd = worktreePath ?? process.cwd();
295
-
296
292
  try {
297
293
  const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
298
294
  dangerouslySkipPermissions: autoMode,
299
295
  // Run Claude session in the worktree root if in worktree mode
300
296
  cwd: worktreePath ?? undefined,
301
- sessionId,
302
297
  });
303
298
 
304
299
  if (exitCode !== 0) {
305
300
  logger.warn(`Claude exited with code ${exitCode}`);
306
301
  }
307
302
 
308
- // Parse session file and display token usage summary
309
- displayPlanSessionTokenSummary(sessionId, sessionCwd);
310
-
311
303
  // Check for created plan files
312
304
  const plansDir = getPlansDir(projectPath);
313
305
  const planFiles = fs.existsSync(plansDir)
@@ -604,25 +596,17 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
604
596
  worktreeMode,
605
597
  });
606
598
 
607
- // Generate session ID for token tracking
608
- const sessionId = crypto.randomUUID();
609
- const sessionCwd = worktreePath ?? process.cwd();
610
-
611
599
  try {
612
600
  const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
613
601
  dangerouslySkipPermissions: autoMode,
614
602
  // Run Claude session in the worktree root if in worktree mode
615
603
  cwd: worktreePath ?? undefined,
616
- sessionId,
617
604
  });
618
605
 
619
606
  if (exitCode !== 0) {
620
607
  logger.warn(`Claude exited with code ${exitCode}`);
621
608
  }
622
609
 
623
- // Parse session file and display token usage summary
624
- displayPlanSessionTokenSummary(sessionId, sessionCwd);
625
-
626
610
  // Check for new plan files
627
611
  const allPlanFiles = fs.existsSync(plansDir)
628
612
  ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
@@ -647,11 +631,9 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
647
631
  logger.info(` - plans/${planFile}`);
648
632
  }
649
633
 
650
- // Commit planning artifacts (input.md, decisions.md, and new plan files)
651
- const newPlanPaths = newPlanFiles.map(f => path.join(plansDir, f));
634
+ // Commit planning artifacts (input.md, decisions.md only plan files committed during execution)
652
635
  await commitPlanningArtifacts(projectPath, {
653
636
  cwd: worktreePath ?? undefined,
654
- additionalFiles: newPlanPaths,
655
637
  isAmend: true,
656
638
  });
657
639
 
@@ -699,43 +681,104 @@ ${taskList}
699
681
  `;
700
682
  }
701
683
 
702
- /**
703
- * Display token usage summary for a plan/amend session.
704
- * Parses the Claude session file and displays formatted usage data.
705
- */
706
- function displayPlanSessionTokenSummary(sessionId: string, cwd: string): void {
707
- const result = parseSessionById(sessionId, cwd);
684
+ async function runResumeCommand(identifier: string, model?: string): Promise<void> {
685
+ // Validate environment
686
+ const validation = validateEnvironment();
687
+ reportValidation(validation);
708
688
 
709
- if (!result.success) {
710
- // Session file not found or couldn't be parsed - just log debug and continue
711
- logger.debug(`Could not parse session file: ${result.error}`);
712
- return;
689
+ if (!validation.valid) {
690
+ process.exit(1);
691
+ }
692
+
693
+ // First, try to resolve the project from main repo
694
+ const rafDir = getRafDir();
695
+ const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
696
+
697
+ if (!mainResolution.path) {
698
+ if (mainResolution.error === 'ambiguous' && mainResolution.matches) {
699
+ logger.error(`Ambiguous project name: ${identifier}`);
700
+ logger.error('Multiple projects match:');
701
+ for (const match of mainResolution.matches) {
702
+ logger.error(` - ${match.folder}`);
703
+ }
704
+ logger.error('Please specify the project ID or full folder name.');
705
+ } else {
706
+ logger.error(`Project not found: ${identifier}`);
707
+ }
708
+ process.exit(1);
709
+ }
710
+
711
+ const projectPath = mainResolution.path;
712
+ const folderName = path.basename(projectPath);
713
+
714
+ // Determine if this is a worktree project by checking if a worktree exists
715
+ let resumeCwd = projectPath; // Default to main repo project path
716
+ const repoBasename = getRepoBasename();
717
+
718
+ if (repoBasename) {
719
+ const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
720
+
721
+ // Check if a worktree exists for this project
722
+ if (fs.existsSync(worktreeBaseDir)) {
723
+ const entries = fs.readdirSync(worktreeBaseDir, { withFileTypes: true });
724
+ const worktreeEntry = entries.find(
725
+ (entry) => entry.isDirectory() && entry.name === folderName
726
+ );
727
+
728
+ if (worktreeEntry) {
729
+ // Worktree exists - use it as the CWD
730
+ const worktreePath = path.join(worktreeBaseDir, worktreeEntry.name);
731
+ const wtValidation = validateWorktree(worktreePath, '');
732
+ if (wtValidation.isValidWorktree) {
733
+ resumeCwd = worktreePath;
734
+ logger.info(`Resuming session in worktree: ${worktreePath}`);
735
+ } else {
736
+ logger.warn(`Worktree found but invalid: ${worktreePath}`);
737
+ logger.warn('Falling back to main repo path.');
738
+ }
739
+ }
740
+ }
713
741
  }
714
742
 
715
- // Check if there's any usage data
716
- const totalTokens = result.usage.inputTokens + result.usage.outputTokens;
717
- if (totalTokens === 0) {
718
- logger.debug('No token usage data found in session file');
719
- return;
743
+ logger.info(`Project: ${folderName}`);
744
+ if (model) {
745
+ logger.info(`Model: ${model}`);
720
746
  }
747
+ logger.newline();
748
+
749
+ // Set up shutdown handler
750
+ const claudeRunner = new ClaudeRunner({ model });
751
+ shutdownHandler.init();
752
+ shutdownHandler.registerClaudeRunner(claudeRunner);
721
753
 
722
- // Create tracker and add the session as a single "task"
723
- const pricingConfig = getPricingConfig();
724
- const tracker = new TokenTracker(pricingConfig);
725
- const entry = tracker.addTask('plan', [result.usage]);
726
-
727
- // Get display options
728
- const displayConfig = getDisplayConfig();
729
- const options: TokenSummaryOptions = {
730
- showCacheTokens: displayConfig.showCacheTokens,
731
- showRateLimitEstimate: displayConfig.showRateLimitEstimate,
732
- rateLimitPercentage: displayConfig.showRateLimitEstimate
733
- ? tracker.calculateRateLimitPercentage(entry.cost.totalCost)
734
- : undefined,
735
- };
736
-
737
- // Display the summary
754
+ // Launch Claude's resume picker
755
+ logger.info('Starting Claude session resume picker...');
738
756
  logger.newline();
739
- const summary = formatTokenTotalSummary(result.usage, entry.cost, options);
740
- console.log(summary);
757
+
758
+ try {
759
+ const exitCode = await claudeRunner.runResume({ cwd: resumeCwd });
760
+
761
+ if (exitCode !== 0) {
762
+ logger.warn(`Claude exited with code ${exitCode}`);
763
+ }
764
+
765
+ // Check for created/updated plan files after resume session
766
+ const plansDir = getPlansDir(projectPath);
767
+ const planFiles = fs.existsSync(plansDir)
768
+ ? fs.readdirSync(plansDir).filter((f) => f.endsWith('.md')).sort()
769
+ : [];
770
+
771
+ if (planFiles.length > 0) {
772
+ logger.newline();
773
+ logger.success(`Session complete! Project has ${planFiles.length} plan(s).`);
774
+ logger.newline();
775
+ logger.info('Plans:');
776
+ for (const planFile of planFiles) {
777
+ logger.info(` - plans/${planFile}`);
778
+ }
779
+ }
780
+ } catch (error) {
781
+ logger.error(`Resume session failed: ${error}`);
782
+ throw error;
783
+ }
741
784
  }
@@ -31,12 +31,6 @@ export interface ClaudeRunnerOptions {
31
31
  * Claude will still ask planning interview questions.
32
32
  */
33
33
  dangerouslySkipPermissions?: boolean;
34
- /**
35
- * Session ID for Claude CLI. When provided, passed as --session-id to enable
36
- * locating the session file after the session ends for token tracking.
37
- * Only used in interactive mode (runInteractive).
38
- */
39
- sessionId?: string;
40
34
  /**
41
35
  * Path to the outcome file. When provided, enables completion detection:
42
36
  * - Monitors stdout for completion markers (<promise>COMPLETE/FAILED</promise>)
@@ -286,7 +280,7 @@ export class ClaudeRunner {
286
280
  userMessage: string,
287
281
  options: ClaudeRunnerOptions = {}
288
282
  ): Promise<number> {
289
- const { cwd = process.cwd(), dangerouslySkipPermissions = false, sessionId } = options;
283
+ const { cwd = process.cwd(), dangerouslySkipPermissions = false } = options;
290
284
 
291
285
  return new Promise((resolve) => {
292
286
  const args = ['--model', this.model];
@@ -296,11 +290,6 @@ export class ClaudeRunner {
296
290
  args.push('--dangerously-skip-permissions');
297
291
  }
298
292
 
299
- // Add --session-id if provided (for token tracking)
300
- if (sessionId) {
301
- args.push('--session-id', sessionId);
302
- }
303
-
304
293
  // System instructions via --append-system-prompt
305
294
  args.push('--append-system-prompt', systemPrompt);
306
295
 
@@ -375,6 +364,87 @@ export class ClaudeRunner {
375
364
  });
376
365
  }
377
366
 
367
+ /**
368
+ * Resume a Claude planning session using the interactive session picker.
369
+ * Launches `claude --resume` (or `claude -r`) to show available sessions for the CWD.
370
+ * Minimal approach - no system prompt or user message injection.
371
+ *
372
+ * @param options - Runner options (cwd)
373
+ */
374
+ async runResume(options: ClaudeRunnerOptions = {}): Promise<number> {
375
+ const { cwd = process.cwd() } = options;
376
+
377
+ return new Promise((resolve) => {
378
+ const args = ['--resume', '--model', this.model];
379
+
380
+ logger.debug(`Starting Claude session resume picker with model: ${this.model}`);
381
+
382
+ this.activeProcess = pty.spawn(getClaudePath(), args, {
383
+ name: 'xterm-256color',
384
+ cols: process.stdout.columns ?? 80,
385
+ rows: process.stdout.rows ?? 24,
386
+ cwd,
387
+ env: process.env as Record<string, string>,
388
+ });
389
+
390
+ // Set raw mode to pass through all input
391
+ if (process.stdin.isTTY) {
392
+ process.stdin.setRawMode(true);
393
+ }
394
+ process.stdin.resume();
395
+
396
+ // Pipe input to Claude
397
+ const onData = (data: Buffer): void => {
398
+ if (this.activeProcess && !this.killed) {
399
+ this.activeProcess.write(data.toString());
400
+ }
401
+ };
402
+ process.stdin.on('data', onData);
403
+
404
+ // Store disposables for proper cleanup
405
+ const disposables: IDisposable[] = [];
406
+
407
+ // Pipe output to stdout
408
+ disposables.push(this.activeProcess.onData((data) => {
409
+ process.stdout.write(data);
410
+ }));
411
+
412
+ disposables.push(this.activeProcess.onExit(({ exitCode }) => {
413
+ // Cleanup stdin
414
+ process.stdin.off('data', onData);
415
+ if (process.stdin.isTTY) {
416
+ process.stdin.setRawMode(false);
417
+ }
418
+ process.stdin.pause();
419
+
420
+ // Dispose all event listeners to prevent FD leaks
421
+ for (const disposable of disposables) {
422
+ try {
423
+ disposable.dispose();
424
+ } catch {
425
+ // Ignore disposal errors
426
+ }
427
+ }
428
+
429
+ // Ensure PTY is fully cleaned up
430
+ if (this.activeProcess) {
431
+ try {
432
+ this.activeProcess.kill();
433
+ } catch {
434
+ // Ignore - process may already be dead
435
+ }
436
+ this.activeProcess = null;
437
+ }
438
+
439
+ if (this.killed) {
440
+ resolve(130); // SIGINT exit code
441
+ } else {
442
+ resolve(exitCode);
443
+ }
444
+ }));
445
+ });
446
+ }
447
+
378
448
  /**
379
449
  * Run Claude non-interactively and collect output.
380
450
  * Uses stream-json format internally to capture token usage data.
@@ -442,6 +442,11 @@ export interface SyncMainBranchResult {
442
442
  error?: string;
443
443
  }
444
444
 
445
+ export interface RebaseResult {
446
+ success: boolean;
447
+ error?: string;
448
+ }
449
+
445
450
  /**
446
451
  * Detect the main branch name from the remote.
447
452
  * Uses refs/remotes/origin/HEAD, falling back to main/master.
@@ -531,7 +536,7 @@ export function pullMainBranch(cwd?: string): SyncMainBranchResult {
531
536
  });
532
537
  logger.debug(`Fetched origin/${mainBranch} (local ${mainBranch} diverged, not updated)`);
533
538
  return {
534
- success: true,
539
+ success: false,
535
540
  mainBranch,
536
541
  hadChanges: false,
537
542
  error: `Local ${mainBranch} has diverged from origin, not updated`,
@@ -662,3 +667,34 @@ export function pushMainBranch(cwd?: string): SyncMainBranchResult {
662
667
  };
663
668
  }
664
669
  }
670
+
671
+ /**
672
+ * Rebase the current branch onto the main branch.
673
+ * If the rebase fails with conflicts, aborts the rebase and returns failure.
674
+ *
675
+ * @param mainBranch - The main branch name to rebase onto (e.g., 'main' or 'master')
676
+ * @param cwd - The directory to run git commands in (defaults to current directory)
677
+ */
678
+ export function rebaseOntoMain(mainBranch: string, cwd: string): RebaseResult {
679
+ try {
680
+ execSync(`git rebase ${mainBranch}`, {
681
+ encoding: 'utf-8',
682
+ stdio: 'pipe',
683
+ cwd,
684
+ });
685
+ return { success: true };
686
+ } catch (error) {
687
+ // Abort the failed rebase to restore clean state
688
+ try {
689
+ execSync('git rebase --abort', {
690
+ encoding: 'utf-8',
691
+ stdio: 'pipe',
692
+ cwd,
693
+ });
694
+ } catch {
695
+ // Ignore abort errors
696
+ }
697
+ const msg = error instanceof Error ? error.message : String(error);
698
+ return { success: false, error: msg };
699
+ }
700
+ }
@@ -23,11 +23,13 @@ export interface StreamEvent {
23
23
  cache_read_input_tokens?: number;
24
24
  cache_creation_input_tokens?: number;
25
25
  };
26
+ total_cost_usd?: number;
26
27
  modelUsage?: Record<string, {
27
28
  inputTokens?: number;
28
29
  outputTokens?: number;
29
30
  cacheReadInputTokens?: number;
30
31
  cacheCreationInputTokens?: number;
32
+ costUSD?: number;
31
33
  }>;
32
34
  tool_use_result?: {
33
35
  type?: string;
@@ -175,6 +177,7 @@ function extractUsageData(event: StreamEvent): UsageData | undefined {
175
177
  outputTokens: data.outputTokens ?? 0,
176
178
  cacheReadInputTokens: data.cacheReadInputTokens ?? 0,
177
179
  cacheCreationInputTokens: data.cacheCreationInputTokens ?? 0,
180
+ costUsd: data.costUSD ?? 0,
178
181
  };
179
182
  }
180
183
  }
@@ -185,5 +188,6 @@ function extractUsageData(event: StreamEvent): UsageData | undefined {
185
188
  cacheReadInputTokens: usage?.cache_read_input_tokens ?? 0,
186
189
  cacheCreationInputTokens: usage?.cache_creation_input_tokens ?? 0,
187
190
  modelUsage,
191
+ totalCostUsd: event.total_cost_usd ?? 0,
188
192
  };
189
193
  }
@@ -135,9 +135,10 @@ After interviewing the user about all NEW tasks, create plan files starting from
135
135
  - ${projectPath}/plans/${encodeTaskId(nextTaskNumber + 1)}-task-name.md
136
136
  - etc.
137
137
 
138
- Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter format uses only a closing \`---\` delimiter (no opening delimiter):
138
+ Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter uses standard YAML format with opening and closing \`---\` delimiters:
139
139
 
140
140
  \`\`\`markdown
141
+ ---
141
142
  effort: medium
142
143
  ---
143
144
  # Task: [Task Name]
@@ -186,6 +187,7 @@ The \`effort\` field is REQUIRED in every plan file. It indicates task complexit
186
187
 
187
188
  Optionally, you can add an explicit \`model\` field to override the effort-based model selection:
188
189
  \`\`\`markdown
190
+ ---
189
191
  effort: medium
190
192
  model: opus
191
193
  ---
@@ -135,49 +135,12 @@ Controls the format of git commit messages. Templates use `{placeholder}` syntax
135
135
 
136
136
  Unknown placeholders are left as-is in the output.
137
137
 
138
- ### `pricing` — Token Cost Pricing
139
-
140
- Controls per-model token pricing used for cost estimation. Prices are in dollars per million tokens. Each model category (`opus`, `sonnet`, `haiku`) has four pricing fields:
141
-
142
- | Field | Description |
143
- |-------|-------------|
144
- | `inputPerMTok` | Cost per million input tokens |
145
- | `outputPerMTok` | Cost per million output tokens |
146
- | `cacheReadPerMTok` | Cost per million cache read tokens (discounted) |
147
- | `cacheCreatePerMTok` | Cost per million cache creation tokens |
148
-
149
- **Default values:**
150
-
151
- | Category | Input | Output | Cache Read | Cache Create |
152
- |----------|-------|--------|------------|--------------|
153
- | `opus` | $15 | $75 | $1.50 | $18.75 |
154
- | `sonnet` | $3 | $15 | $0.30 | $3.75 |
155
- | `haiku` | $1 | $5 | $0.10 | $1.25 |
156
-
157
- Full model IDs from CLI output (e.g., `claude-opus-4-6`) are automatically mapped to the corresponding pricing category based on the model family name.
158
-
159
- Example override:
160
-
161
- ```json
162
- {
163
- "pricing": {
164
- "opus": {
165
- "inputPerMTok": 10,
166
- "outputPerMTok": 50
167
- }
168
- }
169
- }
170
- ```
171
-
172
- Only specify the fields you want to change — unset fields keep their defaults.
173
-
174
138
  ### `display` — Token Summary Display Options
175
139
 
176
140
  Controls what information is shown in token usage summaries after tasks and in the grand total.
177
141
 
178
142
  | Key | Default | Description |
179
143
  |-----|---------|-------------|
180
- | `display.showRateLimitEstimate` | `true` | Show estimated 5h rate limit window percentage (e.g., `~42% of 5h window`) |
181
144
  | `display.showCacheTokens` | `true` | Show cache read/create token counts in summaries |
182
145
 
183
146
  Example:
@@ -185,34 +148,11 @@ Example:
185
148
  ```json
186
149
  {
187
150
  "display": {
188
- "showRateLimitEstimate": false,
189
151
  "showCacheTokens": true
190
152
  }
191
153
  }
192
154
  ```
193
155
 
194
- ### `rateLimitWindow` — Rate Limit Configuration
195
-
196
- Controls the rate limit estimation calculation.
197
-
198
- | Key | Default | Description |
199
- |-----|---------|-------------|
200
- | `rateLimitWindow.sonnetTokenCap` | `88000` | The Sonnet-equivalent token cap for the 5-hour window. All token usage is normalized to Sonnet-equivalent tokens using pricing ratios. |
201
-
202
- The 5h window percentage is calculated as: `(estimatedCost / sonnetCostPerToken) / sonnetTokenCap * 100`
203
-
204
- Where `sonnetCostPerToken` is derived from the configured Sonnet pricing. Heavier models (Opus) consume the window faster than lighter ones (Haiku) in proportion to their API pricing ratios.
205
-
206
- Example:
207
-
208
- ```json
209
- {
210
- "rateLimitWindow": {
211
- "sonnetTokenCap": 100000
212
- }
213
- }
214
- ```
215
-
216
156
  ## Validation Rules
217
157
 
218
158
  The config is validated when loaded. Invalid configs cause an error with a descriptive message. The following rules are enforced:
@@ -224,9 +164,7 @@ The config is validated when loaded. Invalid configs cause an error with a descr
224
164
  - **`maxRetries`** must be a non-negative integer.
225
165
  - **`autoCommit`**, **`worktree`**, and **`syncMainBranch`** must be booleans.
226
166
  - **`commitFormat` values** must be strings.
227
- - **`pricing`** categories must be `"opus"`, `"sonnet"`, or `"haiku"`. Each field must be a non-negative number.
228
- - **`display` values** (`showRateLimitEstimate`, `showCacheTokens`) must be booleans.
229
- - **`rateLimitWindow.sonnetTokenCap`** must be a positive number.
167
+ - **`display` values** (`showCacheTokens`) must be booleans.
230
168
  - The config file must be valid JSON containing an object (not an array or primitive).
231
169
 
232
170
  ## CLI Precedence
@@ -294,17 +232,8 @@ Uses Sonnet for planning and caps task execution at Sonnet (tasks with `effort:
294
232
  "amend": "{prefix}[{projectId}] Amend: {projectName}",
295
233
  "prefix": "RAF"
296
234
  },
297
- "pricing": {
298
- "opus": { "inputPerMTok": 15, "outputPerMTok": 75, "cacheReadPerMTok": 1.5, "cacheCreatePerMTok": 18.75 },
299
- "sonnet": { "inputPerMTok": 3, "outputPerMTok": 15, "cacheReadPerMTok": 0.3, "cacheCreatePerMTok": 3.75 },
300
- "haiku": { "inputPerMTok": 1, "outputPerMTok": 5, "cacheReadPerMTok": 0.1, "cacheCreatePerMTok": 1.25 }
301
- },
302
235
  "display": {
303
- "showRateLimitEstimate": true,
304
236
  "showCacheTokens": true
305
- },
306
- "rateLimitWindow": {
307
- "sonnetTokenCap": 88000
308
237
  }
309
238
  }
310
239
  ```
@@ -80,9 +80,10 @@ After interviewing the user about all tasks, create plan files in the plans fold
80
80
  - ${projectPath}/plans/02-task-name.md
81
81
  - etc.
82
82
 
83
- Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter format uses only a closing \`---\` delimiter (no opening delimiter):
83
+ Each plan file MUST have Obsidian-style frontmatter at the top, before the \`# Task:\` heading. The frontmatter uses standard YAML format with opening and closing \`---\` delimiters:
84
84
 
85
85
  \`\`\`markdown
86
+ ---
86
87
  effort: medium
87
88
  ---
88
89
  # Task: [Task Name]
@@ -128,6 +129,7 @@ The \`effort\` field is REQUIRED in every plan file. It indicates task complexit
128
129
 
129
130
  Optionally, you can add an explicit \`model\` field to override the effort-based model selection:
130
131
  \`\`\`markdown
132
+ ---
131
133
  effort: medium
132
134
  model: opus
133
135
  ---