rafcode 2.3.0 → 2.4.1-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 +21 -4
- package/RAF/ahvrih-rate-forge/decisions.md +70 -0
- package/RAF/ahvrih-rate-forge/input.md +44 -0
- package/RAF/ahvrih-rate-forge/outcomes/01-remove-claude-command-config.md +58 -0
- package/RAF/ahvrih-rate-forge/outcomes/02-fix-mixed-attempt-cost.md +46 -0
- package/RAF/ahvrih-rate-forge/outcomes/03-rate-limit-estimation.md +82 -0
- package/RAF/ahvrih-rate-forge/outcomes/04-show-version-in-do-logs.md +45 -0
- package/RAF/ahvrih-rate-forge/outcomes/05-sync-main-before-worktree.md +96 -0
- package/RAF/ahvrih-rate-forge/outcomes/06-sync-readme-with-codebase.md +45 -0
- package/RAF/ahvrih-rate-forge/outcomes/07-no-session-persistence.md +26 -0
- package/RAF/ahvrih-rate-forge/outcomes/08-plan-execution-metadata.md +130 -0
- package/RAF/ahvrih-rate-forge/plans/01-remove-claude-command-config.md +36 -0
- package/RAF/ahvrih-rate-forge/plans/02-fix-mixed-attempt-cost.md +33 -0
- package/RAF/ahvrih-rate-forge/plans/03-rate-limit-estimation.md +82 -0
- package/RAF/ahvrih-rate-forge/plans/04-show-version-in-do-logs.md +32 -0
- package/RAF/ahvrih-rate-forge/plans/05-sync-main-before-worktree.md +40 -0
- package/RAF/ahvrih-rate-forge/plans/06-sync-readme-with-codebase.md +61 -0
- package/RAF/ahvrih-rate-forge/plans/07-no-session-persistence.md +28 -0
- package/RAF/ahvrih-rate-forge/plans/08-plan-execution-metadata.md +123 -0
- 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/README.md +27 -7
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +209 -6
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +140 -21
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +27 -5
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +0 -6
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +4 -9
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +3 -3
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/pull-request.js +3 -3
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/state-derivation.d.ts +5 -0
- package/dist/core/state-derivation.d.ts.map +1 -1
- package/dist/core/state-derivation.js +14 -4
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/core/worktree.d.ts +44 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +247 -0
- package/dist/core/worktree.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +28 -11
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +28 -11
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +30 -13
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +14 -10
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +47 -4
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +176 -30
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +53 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +115 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +9 -19
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/session-parser.d.ts +44 -0
- package/dist/utils/session-parser.d.ts.map +1 -0
- package/dist/utils/session-parser.js +122 -0
- package/dist/utils/session-parser.js.map +1 -0
- package/dist/utils/terminal-symbols.d.ts +22 -3
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +52 -18
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +20 -0
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +57 -2
- package/dist/utils/token-tracker.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +242 -7
- package/src/commands/do.ts +177 -23
- package/src/commands/plan.ts +27 -4
- package/src/core/claude-runner.ts +4 -16
- package/src/core/failure-analyzer.ts +3 -3
- package/src/core/pull-request.ts +3 -3
- package/src/core/state-derivation.ts +20 -4
- package/src/core/worktree.ts +266 -0
- package/src/prompts/amend.ts +28 -11
- package/src/prompts/config-docs.md +91 -29
- package/src/prompts/planning.ts +28 -11
- package/src/types/config.ts +46 -21
- package/src/utils/config.ts +200 -33
- package/src/utils/frontmatter.ts +140 -0
- package/src/utils/name-generator.ts +9 -19
- package/src/utils/terminal-symbols.ts +68 -16
- package/src/utils/token-tracker.ts +65 -2
- package/tests/unit/claude-runner-interactive.test.ts +8 -6
- package/tests/unit/claude-runner.test.ts +5 -66
- 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 +176 -6
- package/tests/unit/config.test.ts +268 -45
- package/tests/unit/frontmatter.test.ts +276 -0
- package/tests/unit/name-generator.test.ts +1 -1
- package/tests/unit/post-execution-picker.test.ts +6 -0
- package/tests/unit/terminal-symbols.test.ts +142 -0
- package/tests/unit/token-tracker.test.ts +304 -1
- package/tests/unit/validation.test.ts +6 -4
- package/tests/unit/worktree.test.ts +309 -0
package/src/commands/do.ts
CHANGED
|
@@ -13,7 +13,9 @@ import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFro
|
|
|
13
13
|
import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects } from '../ui/project-picker.js';
|
|
14
14
|
import type { PendingProjectInfo } from '../ui/project-picker.js';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
|
-
import { getConfig,
|
|
16
|
+
import { getConfig, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowRateLimitEstimate, getShowCacheTokens } from '../utils/config.js';
|
|
17
|
+
import type { PlanFrontmatter } from '../utils/frontmatter.js';
|
|
18
|
+
import { getVersion } from '../utils/version.js';
|
|
17
19
|
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
18
20
|
import { createStatusLine } from '../utils/status-line.js';
|
|
19
21
|
import {
|
|
@@ -49,6 +51,10 @@ import {
|
|
|
49
51
|
mergeWorktreeBranch,
|
|
50
52
|
removeWorktree,
|
|
51
53
|
resolveWorktreeProjectByIdentifier,
|
|
54
|
+
pushMainBranch,
|
|
55
|
+
pullMainBranch,
|
|
56
|
+
detectMainBranch,
|
|
57
|
+
rebaseOntoMain,
|
|
52
58
|
} from '../core/worktree.js';
|
|
53
59
|
import { createPullRequest, prPreflight } from '../core/pull-request.js';
|
|
54
60
|
import type { DoCommandOptions } from '../types/config.js';
|
|
@@ -61,6 +67,74 @@ import type { DoCommandOptions } from '../types/config.js';
|
|
|
61
67
|
*/
|
|
62
68
|
export type PostExecutionAction = 'merge' | 'pr' | 'leave';
|
|
63
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Result of resolving a task's model from frontmatter.
|
|
72
|
+
*/
|
|
73
|
+
interface TaskModelResolution {
|
|
74
|
+
/** The resolved model (after ceiling is applied). */
|
|
75
|
+
model: string;
|
|
76
|
+
/** Whether a warning should be logged about missing frontmatter. */
|
|
77
|
+
missingFrontmatter: boolean;
|
|
78
|
+
/** Frontmatter parsing warnings to log. */
|
|
79
|
+
warnings: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the execution model for a task from its frontmatter metadata.
|
|
84
|
+
*
|
|
85
|
+
* Resolution order:
|
|
86
|
+
* 1. Explicit `model` in frontmatter (subject to ceiling)
|
|
87
|
+
* 2. `effort` in frontmatter resolved via effortMapping (subject to ceiling)
|
|
88
|
+
* 3. Fallback to models.execute (the ceiling, with a warning)
|
|
89
|
+
*
|
|
90
|
+
* @param frontmatter - Parsed frontmatter from the plan file
|
|
91
|
+
* @param frontmatterWarnings - Warnings from frontmatter parsing
|
|
92
|
+
* @param ceilingModel - The ceiling model (usually models.execute from config)
|
|
93
|
+
* @param isRetry - Whether this is a retry attempt (escalates to ceiling)
|
|
94
|
+
*/
|
|
95
|
+
function resolveTaskModel(
|
|
96
|
+
frontmatter: PlanFrontmatter | undefined,
|
|
97
|
+
frontmatterWarnings: string[] | undefined,
|
|
98
|
+
ceilingModel: string,
|
|
99
|
+
isRetry: boolean,
|
|
100
|
+
): TaskModelResolution {
|
|
101
|
+
const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
|
|
102
|
+
|
|
103
|
+
// Retry escalation: always use the ceiling model on retry
|
|
104
|
+
if (isRetry) {
|
|
105
|
+
return { model: ceilingModel, missingFrontmatter: false, warnings };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// No frontmatter - fallback to ceiling with warning
|
|
109
|
+
if (!frontmatter) {
|
|
110
|
+
return {
|
|
111
|
+
model: ceilingModel,
|
|
112
|
+
missingFrontmatter: true,
|
|
113
|
+
warnings,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Explicit model in frontmatter - apply ceiling
|
|
118
|
+
if (frontmatter.model) {
|
|
119
|
+
const model = applyModelCeiling(frontmatter.model, ceilingModel);
|
|
120
|
+
return { model, missingFrontmatter: false, warnings };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Effort-based resolution - apply ceiling
|
|
124
|
+
if (frontmatter.effort) {
|
|
125
|
+
const mappedModel = resolveEffortToModel(frontmatter.effort);
|
|
126
|
+
const model = applyModelCeiling(mappedModel, ceilingModel);
|
|
127
|
+
return { model, missingFrontmatter: false, warnings };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Frontmatter present but no effort or model - fallback to ceiling with warning
|
|
131
|
+
return {
|
|
132
|
+
model: ceilingModel,
|
|
133
|
+
missingFrontmatter: true,
|
|
134
|
+
warnings,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
64
138
|
/**
|
|
65
139
|
* Format failure history for console output.
|
|
66
140
|
* Shows attempts that failed before eventual success or final failure.
|
|
@@ -152,6 +226,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
152
226
|
// Variables for worktree context (set when --worktree is used)
|
|
153
227
|
let worktreeRoot: string | undefined;
|
|
154
228
|
let originalBranch: string | undefined;
|
|
229
|
+
let mainBranchName: string | null = null;
|
|
155
230
|
|
|
156
231
|
if (worktreeMode) {
|
|
157
232
|
// Validate git repo
|
|
@@ -166,6 +241,19 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
166
241
|
// Record original branch before any worktree operations
|
|
167
242
|
originalBranch = getCurrentBranch() ?? undefined;
|
|
168
243
|
|
|
244
|
+
// Sync main branch before worktree operations (if enabled)
|
|
245
|
+
if (getSyncMainBranch()) {
|
|
246
|
+
const syncResult = pullMainBranch();
|
|
247
|
+
mainBranchName = syncResult.mainBranch;
|
|
248
|
+
if (syncResult.success) {
|
|
249
|
+
if (syncResult.hadChanges) {
|
|
250
|
+
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
169
257
|
if (!projectIdentifier) {
|
|
170
258
|
// Auto-discovery flow
|
|
171
259
|
const selected = await discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath);
|
|
@@ -378,6 +466,20 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
378
466
|
}
|
|
379
467
|
throw error;
|
|
380
468
|
}
|
|
469
|
+
|
|
470
|
+
// Rebase worktree branch onto main before execution (if sync is enabled)
|
|
471
|
+
if (getSyncMainBranch()) {
|
|
472
|
+
const mainBranch = mainBranchName ?? detectMainBranch();
|
|
473
|
+
if (mainBranch) {
|
|
474
|
+
const rebaseResult = rebaseOntoMain(mainBranch, worktreeRoot);
|
|
475
|
+
if (rebaseResult.success) {
|
|
476
|
+
logger.info(`Rebased onto ${mainBranch}`);
|
|
477
|
+
} else {
|
|
478
|
+
logger.warn(`Could not rebase onto ${mainBranch}: ${rebaseResult.error}`);
|
|
479
|
+
logger.warn('Continuing with current branch state.');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
381
483
|
}
|
|
382
484
|
|
|
383
485
|
// Execute project
|
|
@@ -394,7 +496,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
394
496
|
force,
|
|
395
497
|
maxRetries,
|
|
396
498
|
autoCommit,
|
|
397
|
-
showModel: true,
|
|
398
499
|
model,
|
|
399
500
|
worktreeCwd: worktreeRoot,
|
|
400
501
|
}
|
|
@@ -500,6 +601,19 @@ async function executePostAction(
|
|
|
500
601
|
|
|
501
602
|
case 'pr': {
|
|
502
603
|
logger.newline();
|
|
604
|
+
|
|
605
|
+
// Push main branch to remote before PR creation (if enabled)
|
|
606
|
+
if (getSyncMainBranch()) {
|
|
607
|
+
const syncResult = pushMainBranch();
|
|
608
|
+
if (syncResult.success) {
|
|
609
|
+
if (syncResult.hadChanges) {
|
|
610
|
+
logger.info(`Pushed ${syncResult.mainBranch} to remote`);
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
logger.warn(`Could not push main branch: ${syncResult.error}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
503
617
|
logger.info(`Creating PR for branch "${worktreeBranch}"...`);
|
|
504
618
|
|
|
505
619
|
const prResult = await createPullRequest(worktreeBranch, projectPath, { cwd: worktreeRoot });
|
|
@@ -658,7 +772,6 @@ interface SingleProjectOptions {
|
|
|
658
772
|
force: boolean;
|
|
659
773
|
maxRetries: number;
|
|
660
774
|
autoCommit: boolean;
|
|
661
|
-
showModel: boolean;
|
|
662
775
|
model: string;
|
|
663
776
|
/** Worktree root directory. When set, Claude runs with cwd in the worktree. */
|
|
664
777
|
worktreeCwd?: string;
|
|
@@ -669,7 +782,7 @@ async function executeSingleProject(
|
|
|
669
782
|
projectName: string,
|
|
670
783
|
options: SingleProjectOptions
|
|
671
784
|
): Promise<ProjectExecutionResult> {
|
|
672
|
-
const { timeout, verbose, debug, force, maxRetries, autoCommit,
|
|
785
|
+
const { timeout, verbose, debug, force, maxRetries, autoCommit, model, worktreeCwd } = options;
|
|
673
786
|
|
|
674
787
|
if (!validatePlansExist(projectPath)) {
|
|
675
788
|
return {
|
|
@@ -709,11 +822,12 @@ async function executeSingleProject(
|
|
|
709
822
|
: state.tasks.filter((t) => t.status !== 'completed').map((t) => t.id)
|
|
710
823
|
);
|
|
711
824
|
|
|
712
|
-
// Set up shutdown handler
|
|
713
|
-
const claudeRunner = new ClaudeRunner({ model });
|
|
825
|
+
// Set up shutdown handler - we'll register runners dynamically per-task
|
|
714
826
|
const projectManager = new ProjectManager();
|
|
715
827
|
shutdownHandler.init();
|
|
716
|
-
|
|
828
|
+
|
|
829
|
+
// The ceiling model for all tasks (can be overridden per-task, subject to this ceiling)
|
|
830
|
+
const ceilingModel = model;
|
|
717
831
|
|
|
718
832
|
// Initialize token tracker for usage reporting
|
|
719
833
|
const tokenTracker = new TokenTracker();
|
|
@@ -725,15 +839,13 @@ async function executeSingleProject(
|
|
|
725
839
|
// Start project timer
|
|
726
840
|
const projectStartTime = Date.now();
|
|
727
841
|
|
|
842
|
+
// Resolve and display version + ceiling model info (before any tasks run)
|
|
843
|
+
const fullCeilingModelId = resolveFullModelId(ceilingModel);
|
|
844
|
+
logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId}`);
|
|
845
|
+
|
|
728
846
|
if (verbose) {
|
|
729
847
|
logger.info(`Executing project: ${projectName}`);
|
|
730
848
|
logger.info(`Tasks: ${state.tasks.length}, Task timeout: ${timeout} minutes`);
|
|
731
|
-
|
|
732
|
-
// Log Claude model name
|
|
733
|
-
if (showModel && model) {
|
|
734
|
-
logger.info(`Using model: ${model}`);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
849
|
logger.newline();
|
|
738
850
|
} else {
|
|
739
851
|
// Minimal mode: show project header
|
|
@@ -923,16 +1035,45 @@ async function executeSingleProject(
|
|
|
923
1035
|
});
|
|
924
1036
|
timer.start();
|
|
925
1037
|
|
|
1038
|
+
// Log frontmatter warnings once before the retry loop
|
|
1039
|
+
if (task.frontmatterWarnings && task.frontmatterWarnings.length > 0) {
|
|
1040
|
+
for (const warning of task.frontmatterWarnings) {
|
|
1041
|
+
logger.warn(` Frontmatter warning: ${warning}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
926
1045
|
while (!success && attempts < maxRetries) {
|
|
927
1046
|
attempts++;
|
|
1047
|
+
const isRetry = attempts > 1;
|
|
1048
|
+
|
|
1049
|
+
// Resolve the model for this attempt (escalates to ceiling on retry)
|
|
1050
|
+
const modelResolution = resolveTaskModel(
|
|
1051
|
+
task.frontmatter,
|
|
1052
|
+
undefined, // warnings already logged above
|
|
1053
|
+
ceilingModel,
|
|
1054
|
+
isRetry,
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
// Log missing frontmatter warning on first attempt only
|
|
1058
|
+
if (!isRetry && modelResolution.missingFrontmatter) {
|
|
1059
|
+
logger.warn(` No effort frontmatter found — using ceiling model`);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Create a runner for this attempt's model
|
|
1063
|
+
const taskRunner = new ClaudeRunner({ model: modelResolution.model });
|
|
1064
|
+
shutdownHandler.registerClaudeRunner(taskRunner);
|
|
928
1065
|
|
|
929
|
-
if (verbose &&
|
|
930
|
-
|
|
1066
|
+
if (verbose && isRetry) {
|
|
1067
|
+
const retryModel = resolveFullModelId(modelResolution.model);
|
|
1068
|
+
logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel} (model: ${retryModel})...`);
|
|
1069
|
+
} else if (verbose && !isRetry) {
|
|
1070
|
+
const taskModel = resolveFullModelId(modelResolution.model);
|
|
1071
|
+
logger.info(` Model: ${taskModel}`);
|
|
931
1072
|
}
|
|
932
1073
|
|
|
933
1074
|
// Build execution prompt (inside loop to include retry context on retries)
|
|
934
1075
|
// Check if previous outcome file exists for retry context
|
|
935
|
-
const previousOutcomeFileForRetry =
|
|
1076
|
+
const previousOutcomeFileForRetry = isRetry && fs.existsSync(outcomeFilePath)
|
|
936
1077
|
? outcomeFilePath
|
|
937
1078
|
: undefined;
|
|
938
1079
|
|
|
@@ -961,18 +1102,16 @@ async function executeSingleProject(
|
|
|
961
1102
|
} : undefined;
|
|
962
1103
|
|
|
963
1104
|
// Run Claude (use worktree root as cwd if in worktree mode)
|
|
964
|
-
const executeEffort = getEffort('execute');
|
|
965
1105
|
const runnerOptions = {
|
|
966
1106
|
timeout,
|
|
967
1107
|
outcomeFilePath,
|
|
968
1108
|
commitContext,
|
|
969
1109
|
cwd: worktreeCwd,
|
|
970
|
-
effortLevel: executeEffort,
|
|
971
1110
|
verboseCheck: () => verboseToggle.isVerbose,
|
|
972
1111
|
};
|
|
973
1112
|
const result = verbose
|
|
974
|
-
? await
|
|
975
|
-
: await
|
|
1113
|
+
? await taskRunner.runVerbose(prompt, runnerOptions)
|
|
1114
|
+
: await taskRunner.run(prompt, runnerOptions);
|
|
976
1115
|
|
|
977
1116
|
lastOutput = result.output;
|
|
978
1117
|
if (result.usageData) {
|
|
@@ -1096,7 +1235,12 @@ Task completed. No detailed report provided.
|
|
|
1096
1235
|
// Track and display token usage for this task
|
|
1097
1236
|
if (attemptUsageData.length > 0) {
|
|
1098
1237
|
const entry = tokenTracker.addTask(task.id, attemptUsageData);
|
|
1099
|
-
|
|
1238
|
+
const taskRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
|
|
1239
|
+
logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
|
|
1240
|
+
showCacheTokens: getShowCacheTokens(),
|
|
1241
|
+
showRateLimitEstimate: getShowRateLimitEstimate(),
|
|
1242
|
+
rateLimitPercentage: taskRateLimitPct,
|
|
1243
|
+
}));
|
|
1100
1244
|
}
|
|
1101
1245
|
|
|
1102
1246
|
completedInSession.add(task.id);
|
|
@@ -1124,7 +1268,12 @@ Task completed. No detailed report provided.
|
|
|
1124
1268
|
// Track token usage even for failed tasks (partial data still useful for totals)
|
|
1125
1269
|
if (attemptUsageData.length > 0) {
|
|
1126
1270
|
const entry = tokenTracker.addTask(task.id, attemptUsageData);
|
|
1127
|
-
|
|
1271
|
+
const taskRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
|
|
1272
|
+
logger.dim(formatTaskTokenSummary(entry, (u) => tokenTracker.calculateCost(u), {
|
|
1273
|
+
showCacheTokens: getShowCacheTokens(),
|
|
1274
|
+
showRateLimitEstimate: getShowRateLimitEstimate(),
|
|
1275
|
+
rateLimitPercentage: taskRateLimitPct,
|
|
1276
|
+
}));
|
|
1128
1277
|
}
|
|
1129
1278
|
|
|
1130
1279
|
// Analyze failure and generate structured report
|
|
@@ -1237,7 +1386,12 @@ ${stashName ? `- Stash: ${stashName}` : ''}
|
|
|
1237
1386
|
if (trackerEntries.length > 0) {
|
|
1238
1387
|
logger.newline();
|
|
1239
1388
|
const totals = tokenTracker.getTotals();
|
|
1240
|
-
|
|
1389
|
+
const totalRateLimitPct = tokenTracker.getCumulativeRateLimitPercentage();
|
|
1390
|
+
logger.dim(formatTokenTotalSummary(totals.usage, totals.cost, {
|
|
1391
|
+
showCacheTokens: getShowCacheTokens(),
|
|
1392
|
+
showRateLimitEstimate: getShowRateLimitEstimate(),
|
|
1393
|
+
rateLimitPercentage: totalRateLimitPct,
|
|
1394
|
+
}));
|
|
1241
1395
|
}
|
|
1242
1396
|
|
|
1243
1397
|
// Show retry history for tasks that had failures (even if eventually successful)
|
package/src/commands/plan.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
resolveModelOption,
|
|
16
16
|
} from '../utils/validation.js';
|
|
17
17
|
import { logger } from '../utils/logger.js';
|
|
18
|
-
import { getWorktreeDefault, getModel, getModelShortName } from '../utils/config.js';
|
|
18
|
+
import { getWorktreeDefault, getModel, getModelShortName, getSyncMainBranch } from '../utils/config.js';
|
|
19
19
|
import { generateProjectNames } from '../utils/name-generator.js';
|
|
20
20
|
import { pickProjectName } from '../ui/name-picker.js';
|
|
21
21
|
import {
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
validateWorktree,
|
|
49
49
|
removeWorktree,
|
|
50
50
|
computeWorktreeBaseDir,
|
|
51
|
+
pullMainBranch,
|
|
51
52
|
} from '../core/worktree.js';
|
|
52
53
|
|
|
53
54
|
interface PlanCommandOptions {
|
|
@@ -185,6 +186,18 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
185
186
|
const repoRoot = getRepoRoot()!;
|
|
186
187
|
const rafDir = getRafDir();
|
|
187
188
|
|
|
189
|
+
// Sync main branch before creating worktree (if enabled)
|
|
190
|
+
if (getSyncMainBranch()) {
|
|
191
|
+
const syncResult = pullMainBranch();
|
|
192
|
+
if (syncResult.success) {
|
|
193
|
+
if (syncResult.hadChanges) {
|
|
194
|
+
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
188
201
|
// Compute project number from main repo's RAF directory
|
|
189
202
|
const projectNumber = getNextProjectNumber(rafDir);
|
|
190
203
|
const sanitizedName = sanitizeProjectName(finalProjectName);
|
|
@@ -412,6 +425,18 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
412
425
|
logger.info(`Recreated worktree from branch: ${folderName}`);
|
|
413
426
|
} else {
|
|
414
427
|
// No branch — create fresh worktree and copy project files
|
|
428
|
+
// Sync main branch before creating worktree (if enabled)
|
|
429
|
+
if (getSyncMainBranch()) {
|
|
430
|
+
const syncResult = pullMainBranch();
|
|
431
|
+
if (syncResult.success) {
|
|
432
|
+
if (syncResult.hadChanges) {
|
|
433
|
+
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
415
440
|
const result = createWorktree(repoBasename, folderName);
|
|
416
441
|
if (!result.success) {
|
|
417
442
|
logger.error(`Failed to create worktree: ${result.error}`);
|
|
@@ -602,11 +627,9 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
602
627
|
logger.info(` - plans/${planFile}`);
|
|
603
628
|
}
|
|
604
629
|
|
|
605
|
-
// Commit planning artifacts (input.md, decisions.md
|
|
606
|
-
const newPlanPaths = newPlanFiles.map(f => path.join(plansDir, f));
|
|
630
|
+
// Commit planning artifacts (input.md, decisions.md only — plan files committed during execution)
|
|
607
631
|
await commitPlanningArtifacts(projectPath, {
|
|
608
632
|
cwd: worktreePath ?? undefined,
|
|
609
|
-
additionalFiles: newPlanPaths,
|
|
610
633
|
isAmend: true,
|
|
611
634
|
});
|
|
612
635
|
|
|
@@ -6,12 +6,11 @@ import { logger } from '../utils/logger.js';
|
|
|
6
6
|
import { renderStreamEvent } from '../parsers/stream-renderer.js';
|
|
7
7
|
import type { UsageData } from '../types/config.js';
|
|
8
8
|
import { getHeadCommitHash, getHeadCommitMessage, isFileCommittedInHead } from './git.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getModel } from '../utils/config.js';
|
|
10
10
|
|
|
11
11
|
function getClaudePath(): string {
|
|
12
|
-
const cmd = getClaudeCommand();
|
|
13
12
|
try {
|
|
14
|
-
return execSync(
|
|
13
|
+
return execSync('which claude', { encoding: 'utf-8' }).trim();
|
|
15
14
|
} catch {
|
|
16
15
|
throw new Error('Claude CLI not found. Please ensure it is installed and in your PATH.');
|
|
17
16
|
}
|
|
@@ -53,12 +52,6 @@ export interface ClaudeRunnerOptions {
|
|
|
53
52
|
/** Path to the outcome file that should be committed. */
|
|
54
53
|
outcomeFilePath: string;
|
|
55
54
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Claude Code reasoning effort level.
|
|
58
|
-
* Sets CLAUDE_CODE_EFFORT_LEVEL env var for the spawned process.
|
|
59
|
-
* Only applied in non-interactive modes (run, runVerbose).
|
|
60
|
-
*/
|
|
61
|
-
effortLevel?: 'low' | 'medium' | 'high';
|
|
62
55
|
/**
|
|
63
56
|
* Dynamic verbose display callback. When provided, called for each stream event
|
|
64
57
|
* to determine whether to write display output to stdout. Overrides the static
|
|
@@ -415,7 +408,7 @@ export class ClaudeRunner {
|
|
|
415
408
|
options: ClaudeRunnerOptions,
|
|
416
409
|
verbose: boolean,
|
|
417
410
|
): Promise<RunResult> {
|
|
418
|
-
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext,
|
|
411
|
+
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, verboseCheck } = options;
|
|
419
412
|
// Ensure timeout is a positive number, fallback to 60 minutes
|
|
420
413
|
const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
|
|
421
414
|
const timeoutMs = validatedTimeout * 60 * 1000;
|
|
@@ -437,11 +430,6 @@ export class ClaudeRunner {
|
|
|
437
430
|
logger.debug(`Prompt length: ${prompt.length}, timeout: ${timeoutMs}ms, cwd: ${cwd}`);
|
|
438
431
|
logger.debug(`Claude path: ${claudePath}`);
|
|
439
432
|
|
|
440
|
-
// Build env, optionally injecting effort level
|
|
441
|
-
const env = effortLevel
|
|
442
|
-
? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
|
|
443
|
-
: process.env;
|
|
444
|
-
|
|
445
433
|
logger.debug('Spawning process...');
|
|
446
434
|
// Use --output-format stream-json --verbose to get real-time streaming events
|
|
447
435
|
// including tool calls, file operations, and token usage in the result event.
|
|
@@ -460,7 +448,7 @@ export class ClaudeRunner {
|
|
|
460
448
|
'Execute the task as described in the system prompt.',
|
|
461
449
|
], {
|
|
462
450
|
cwd,
|
|
463
|
-
env,
|
|
451
|
+
env: process.env,
|
|
464
452
|
stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
|
|
465
453
|
});
|
|
466
454
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
|
-
import { getModel
|
|
3
|
+
import { getModel } from '../utils/config.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Failure types that can be detected programmatically without using the API.
|
|
@@ -213,9 +213,8 @@ function extractRelevantOutput(output: string, maxLines: number): string {
|
|
|
213
213
|
* Get the path to Claude CLI.
|
|
214
214
|
*/
|
|
215
215
|
function getClaudePath(): string {
|
|
216
|
-
const cmd = getClaudeCommand();
|
|
217
216
|
try {
|
|
218
|
-
return execSync(
|
|
217
|
+
return execSync('which claude', { encoding: 'utf-8' }).trim();
|
|
219
218
|
} catch {
|
|
220
219
|
throw new Error('Claude CLI not found. Please ensure it is installed and in your PATH.');
|
|
221
220
|
}
|
|
@@ -312,6 +311,7 @@ Respond with ONLY a markdown report in this exact format:
|
|
|
312
311
|
const failureModel = getModel('failureAnalysis');
|
|
313
312
|
const proc = spawn(claudePath, [
|
|
314
313
|
'--model', failureModel,
|
|
314
|
+
'--no-session-persistence',
|
|
315
315
|
'--dangerously-skip-permissions',
|
|
316
316
|
'-p',
|
|
317
317
|
prompt,
|
package/src/core/pull-request.ts
CHANGED
|
@@ -3,7 +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,
|
|
6
|
+
import { getModel, getModelShortName } from '../utils/config.js';
|
|
7
7
|
import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN } from '../utils/paths.js';
|
|
8
8
|
|
|
9
9
|
export interface PrCreateResult {
|
|
@@ -354,10 +354,9 @@ export function filterClaudeOutput(output: string): string {
|
|
|
354
354
|
* Call Claude to generate a PR body.
|
|
355
355
|
*/
|
|
356
356
|
async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<string> {
|
|
357
|
-
const cmd = getClaudeCommand();
|
|
358
357
|
let claudePath: string;
|
|
359
358
|
try {
|
|
360
|
-
claudePath = execSync(
|
|
359
|
+
claudePath = execSync('which claude', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
361
360
|
} catch {
|
|
362
361
|
throw new Error('Claude CLI not found');
|
|
363
362
|
}
|
|
@@ -369,6 +368,7 @@ async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<s
|
|
|
369
368
|
const prModel = getModel('prGeneration');
|
|
370
369
|
const proc = spawn(claudePath, [
|
|
371
370
|
'--model', prModel,
|
|
371
|
+
'--no-session-persistence',
|
|
372
372
|
'--dangerously-skip-permissions',
|
|
373
373
|
'-p',
|
|
374
374
|
prompt,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { getPlansDir, getOutcomesDir, getInputPath, decodeBase26, TASK_ID_PATTERN } from '../utils/paths.js';
|
|
4
|
+
import { parsePlanFrontmatter, type PlanFrontmatter } from '../utils/frontmatter.js';
|
|
4
5
|
|
|
5
6
|
export type DerivedTaskStatus = 'pending' | 'completed' | 'failed' | 'blocked';
|
|
6
7
|
|
|
@@ -16,6 +17,10 @@ export interface DerivedTask {
|
|
|
16
17
|
planFile: string;
|
|
17
18
|
status: DerivedTaskStatus;
|
|
18
19
|
dependencies: string[];
|
|
20
|
+
/** Frontmatter metadata parsed from the plan file. */
|
|
21
|
+
frontmatter?: PlanFrontmatter;
|
|
22
|
+
/** Warnings from frontmatter parsing. */
|
|
23
|
+
frontmatterWarnings?: string[];
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export interface DerivedProjectState {
|
|
@@ -218,23 +223,34 @@ export function deriveProjectState(projectPath: string): DerivedProjectState {
|
|
|
218
223
|
}
|
|
219
224
|
}
|
|
220
225
|
|
|
221
|
-
// First pass: Match plan files to outcomes and parse dependencies
|
|
226
|
+
// First pass: Match plan files to outcomes and parse dependencies + frontmatter
|
|
222
227
|
for (const planFile of planFiles) {
|
|
223
228
|
const match = planFile.match(new RegExp(`^(${TASK_ID_PATTERN})-(.+)\\.md$`));
|
|
224
229
|
if (match && match[1]) {
|
|
225
230
|
const taskId = match[1];
|
|
226
231
|
const status = outcomeStatuses.get(taskId) ?? 'pending';
|
|
227
232
|
|
|
228
|
-
// Read plan file to extract dependencies
|
|
233
|
+
// Read plan file to extract dependencies and frontmatter
|
|
229
234
|
const planContent = fs.readFileSync(path.join(plansDir, planFile), 'utf-8');
|
|
230
235
|
const dependencies = parseDependencies(planContent);
|
|
236
|
+
const frontmatterResult = parsePlanFrontmatter(planContent);
|
|
231
237
|
|
|
232
|
-
|
|
238
|
+
const task: DerivedTask = {
|
|
233
239
|
id: taskId,
|
|
234
240
|
planFile: path.join('plans', planFile),
|
|
235
241
|
status,
|
|
236
242
|
dependencies,
|
|
237
|
-
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Only add frontmatter if valid metadata was found
|
|
246
|
+
if (frontmatterResult.hasFrontmatter) {
|
|
247
|
+
task.frontmatter = frontmatterResult.frontmatter;
|
|
248
|
+
}
|
|
249
|
+
if (frontmatterResult.warnings.length > 0) {
|
|
250
|
+
task.frontmatterWarnings = frontmatterResult.warnings;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
tasks.push(task);
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
256
|
|