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.
- package/.claude/settings.local.json +3 -1
- package/CLAUDE.md +7 -5
- package/RAF/ahwidh-quick-fix-gremlin/decisions.md +37 -0
- package/RAF/ahwidh-quick-fix-gremlin/input.md +35 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/01-fix-name-generation-prompt.md +33 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/02-fix-amend-commit-scope.md +43 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/03-fix-diverged-main-branch-sync.md +32 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/04-wire-rate-limit-to-do-command.md +61 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/05-add-config-get-set-flags.md +125 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/06-sync-worktree-branch-before-execution.md +96 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/07-update-frontmatter-format.md +107 -0
- package/RAF/ahwidh-quick-fix-gremlin/outcomes/08-remove-plan-token-report.md +76 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/01-fix-name-generation-prompt.md +52 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/02-fix-amend-commit-scope.md +48 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/03-fix-diverged-main-branch-sync.md +49 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/04-wire-rate-limit-to-do-command.md +78 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/05-add-config-get-set-flags.md +101 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/06-sync-worktree-branch-before-execution.md +92 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/07-update-frontmatter-format.md +105 -0
- package/RAF/ahwidh-quick-fix-gremlin/plans/08-remove-plan-token-report.md +50 -0
- package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
- package/RAF/ahwqwq-model-whisperer/input.md +5 -0
- package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
- package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
- package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
- package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
- package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
- package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +209 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +37 -8
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +92 -54
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +8 -6
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +73 -5
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/worktree.d.ts +12 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +33 -1
- package/dist/core/worktree.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +2 -0
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +2 -0
- package/dist/parsers/stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +3 -1
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +3 -1
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +4 -24
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +0 -24
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +1 -26
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +2 -98
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +13 -3
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +40 -10
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +7 -16
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +7 -16
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +16 -42
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +4 -30
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +17 -98
- package/dist/utils/token-tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +242 -0
- package/src/commands/do.ts +39 -7
- package/src/commands/plan.ts +101 -58
- package/src/core/claude-runner.ts +82 -12
- package/src/core/worktree.ts +37 -1
- package/src/parsers/stream-renderer.ts +4 -0
- package/src/prompts/amend.ts +3 -1
- package/src/prompts/config-docs.md +1 -72
- package/src/prompts/planning.ts +3 -1
- package/src/types/config.ts +4 -52
- package/src/utils/config.ts +2 -112
- package/src/utils/frontmatter.ts +41 -11
- package/src/utils/name-generator.ts +7 -16
- package/src/utils/terminal-symbols.ts +16 -46
- package/src/utils/token-tracker.ts +19 -113
- package/tests/unit/claude-runner.test.ts +1 -0
- package/tests/unit/commit-planning-artifacts-worktree.test.ts +6 -14
- package/tests/unit/commit-planning-artifacts.test.ts +4 -12
- package/tests/unit/config-command.test.ts +161 -0
- package/tests/unit/config.test.ts +6 -148
- package/tests/unit/frontmatter.test.ts +95 -1
- package/tests/unit/name-generator.test.ts +1 -1
- package/tests/unit/post-execution-picker.test.ts +1 -0
- package/tests/unit/stream-renderer.test.ts +82 -0
- package/tests/unit/terminal-symbols.test.ts +86 -124
- package/tests/unit/token-tracker.test.ts +159 -679
- package/tests/unit/worktree.test.ts +68 -1
- package/src/utils/session-parser.ts +0 -161
- package/tests/unit/session-parser.test.ts +0 -301
package/src/commands/plan.ts
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
|
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
|
-
|
|
704
|
-
|
|
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 (!
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
//
|
|
723
|
-
|
|
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
|
-
|
|
740
|
-
|
|
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
|
|
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.
|
package/src/core/worktree.ts
CHANGED
|
@@ -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:
|
|
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
|
}
|
package/src/prompts/amend.ts
CHANGED
|
@@ -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
|
|
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
|
-
- **`
|
|
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
|
```
|
package/src/prompts/planning.ts
CHANGED
|
@@ -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
|
|
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
|
---
|