rafcode 3.0.0 → 3.2.1
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/RAF/38-dual-wielder/decisions.md +9 -0
- package/RAF/38-dual-wielder/input.md +6 -1
- package/RAF/38-dual-wielder/outcomes/8-e2e-test-codex-provider.md +139 -0
- package/RAF/38-dual-wielder/plans/8-e2e-test-codex-provider.md +95 -0
- package/RAF/39-pathless-rover/decisions.md +16 -0
- package/RAF/39-pathless-rover/input.md +2 -0
- package/RAF/39-pathless-rover/outcomes/1-fix-codex-stream-renderer.md +21 -0
- package/RAF/39-pathless-rover/outcomes/2-wire-provider-flag.md +28 -0
- package/RAF/39-pathless-rover/outcomes/3-remove-worktree-flag-do.md +41 -0
- package/RAF/39-pathless-rover/outcomes/4-remove-worktree-flag-plan-amend.md +30 -0
- package/RAF/39-pathless-rover/outcomes/5-update-prompts-and-docs.md +26 -0
- package/RAF/39-pathless-rover/plans/1-fix-codex-stream-renderer.md +43 -0
- package/RAF/39-pathless-rover/plans/2-wire-provider-flag.md +48 -0
- package/RAF/39-pathless-rover/plans/3-remove-worktree-flag-do.md +41 -0
- package/RAF/39-pathless-rover/plans/4-remove-worktree-flag-plan-amend.md +43 -0
- package/RAF/39-pathless-rover/plans/5-update-prompts-and-docs.md +31 -0
- package/RAF/40-numeric-order-fix/decisions.md +7 -0
- package/RAF/40-numeric-order-fix/input.md +19 -0
- package/RAF/40-numeric-order-fix/outcomes/1-fix-numeric-sort-order.md +18 -0
- package/RAF/40-numeric-order-fix/outcomes/2-add-npm-keywords.md +10 -0
- package/RAF/40-numeric-order-fix/plans/1-fix-numeric-sort-order.md +48 -0
- package/RAF/40-numeric-order-fix/plans/2-add-npm-keywords.md +23 -0
- package/README.md +5 -8
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +41 -193
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +32 -120
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/project-manager.d.ts.map +1 -1
- package/dist/core/project-manager.js +2 -2
- package/dist/core/project-manager.js.map +1 -1
- package/dist/core/pull-request.js +2 -2
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/state-derivation.js +3 -3
- package/dist/core/state-derivation.js.map +1 -1
- package/dist/parsers/codex-stream-renderer.d.ts +21 -4
- package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.js +77 -0
- package/dist/parsers/codex-stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts +0 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +2 -3
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +2 -3
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +0 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +9 -0
- package/dist/utils/paths.js.map +1 -1
- package/package.json +7 -2
- package/src/commands/do.ts +42 -220
- package/src/commands/plan.ts +34 -127
- package/src/core/project-manager.ts +2 -1
- package/src/core/pull-request.ts +2 -2
- package/src/core/state-derivation.ts +3 -3
- package/src/parsers/codex-stream-renderer.ts +106 -4
- package/src/prompts/amend.ts +1 -4
- package/src/prompts/config-docs.md +1 -1
- package/src/prompts/planning.ts +2 -4
- package/src/types/config.ts +0 -1
- package/src/utils/paths.ts +10 -0
package/src/commands/do.ts
CHANGED
|
@@ -10,10 +10,10 @@ import { getExecutionPrompt } from '../prompts/execution.js';
|
|
|
10
10
|
import { parseOutput, isRetryableFailure } from '../parsers/output-parser.js';
|
|
11
11
|
import { validatePlansExist, resolveModelOption } from '../utils/validation.js';
|
|
12
12
|
import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFromPlanFile, resolveProjectIdentifierWithDetails, getOutcomeFilePath } from '../utils/paths.js';
|
|
13
|
-
import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects
|
|
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, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
|
|
17
17
|
import type { PlanFrontmatter } from '../utils/frontmatter.js';
|
|
18
18
|
import { getVersion } from '../utils/version.js';
|
|
19
19
|
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
@@ -29,7 +29,6 @@ import { TokenTracker } from '../utils/token-tracker.js';
|
|
|
29
29
|
import { VerboseToggle } from '../utils/verbose-toggle.js';
|
|
30
30
|
import {
|
|
31
31
|
deriveProjectState,
|
|
32
|
-
discoverProjects,
|
|
33
32
|
getNextExecutableTask,
|
|
34
33
|
getDerivedStats,
|
|
35
34
|
getDerivedStatsForTasks,
|
|
@@ -44,10 +43,6 @@ import {
|
|
|
44
43
|
getRepoRoot,
|
|
45
44
|
getRepoBasename,
|
|
46
45
|
getCurrentBranch,
|
|
47
|
-
computeWorktreePath,
|
|
48
|
-
computeWorktreeBaseDir,
|
|
49
|
-
validateWorktree,
|
|
50
|
-
listWorktreeProjects,
|
|
51
46
|
mergeWorktreeBranch,
|
|
52
47
|
removeWorktree,
|
|
53
48
|
resolveWorktreeProjectByIdentifier,
|
|
@@ -97,6 +92,7 @@ function resolveTaskModel(
|
|
|
97
92
|
frontmatterWarnings: string[] | undefined,
|
|
98
93
|
ceilingModel: string,
|
|
99
94
|
isRetry: boolean,
|
|
95
|
+
provider?: import('../types/config.js').HarnessProvider,
|
|
100
96
|
): TaskModelResolution {
|
|
101
97
|
const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
|
|
102
98
|
|
|
@@ -122,7 +118,7 @@ function resolveTaskModel(
|
|
|
122
118
|
|
|
123
119
|
// Effort-based resolution - apply ceiling
|
|
124
120
|
if (frontmatter.effort) {
|
|
125
|
-
const mappedModel = resolveEffortToModel(frontmatter.effort);
|
|
121
|
+
const mappedModel = resolveEffortToModel(frontmatter.effort, provider);
|
|
126
122
|
const model = applyModelCeiling(mappedModel, ceilingModel);
|
|
127
123
|
return { model, missingFrontmatter: false, warnings };
|
|
128
124
|
}
|
|
@@ -201,8 +197,6 @@ export function createDoCommand(): Command {
|
|
|
201
197
|
.option('-f, --force', 'Re-run all tasks regardless of status')
|
|
202
198
|
.option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
|
|
203
199
|
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
204
|
-
.option('-w, --worktree', 'Execute tasks in a git worktree')
|
|
205
|
-
.option('--no-worktree', 'Disable worktree mode (overrides config)')
|
|
206
200
|
.option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
|
|
207
201
|
.action(async (project: string | undefined, options: DoCommandOptions) => {
|
|
208
202
|
await runDoCommand(project, options);
|
|
@@ -214,8 +208,6 @@ export function createDoCommand(): Command {
|
|
|
214
208
|
async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
|
|
215
209
|
const rafDir = getRafDir();
|
|
216
210
|
let projectIdentifier = projectIdentifierArg;
|
|
217
|
-
let worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
218
|
-
|
|
219
211
|
// Validate and resolve model option
|
|
220
212
|
let model: string;
|
|
221
213
|
try {
|
|
@@ -225,48 +217,11 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
225
217
|
process.exit(1);
|
|
226
218
|
}
|
|
227
219
|
|
|
228
|
-
// Variables for worktree context (
|
|
220
|
+
// Variables for worktree context (derived from where the project is found)
|
|
229
221
|
let worktreeRoot: string | undefined;
|
|
230
222
|
let originalBranch: string | undefined;
|
|
231
223
|
let mainBranchName: string | null = null;
|
|
232
224
|
|
|
233
|
-
if (worktreeMode) {
|
|
234
|
-
// Validate git repo
|
|
235
|
-
const repoRoot = getRepoRoot();
|
|
236
|
-
if (!repoRoot) {
|
|
237
|
-
logger.error('--worktree requires a git repository');
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
const repoBasename = getRepoBasename()!;
|
|
241
|
-
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
242
|
-
|
|
243
|
-
// Record original branch before any worktree operations
|
|
244
|
-
originalBranch = getCurrentBranch() ?? undefined;
|
|
245
|
-
|
|
246
|
-
// Sync main branch before worktree operations (if enabled)
|
|
247
|
-
if (getSyncMainBranch()) {
|
|
248
|
-
const syncResult = pullMainBranch();
|
|
249
|
-
mainBranchName = syncResult.mainBranch;
|
|
250
|
-
if (syncResult.success) {
|
|
251
|
-
if (syncResult.hadChanges) {
|
|
252
|
-
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!projectIdentifier) {
|
|
260
|
-
// Auto-discovery flow
|
|
261
|
-
const selected = await discoverAndPickWorktreeProject(repoBasename, rafDir, rafRelativePath);
|
|
262
|
-
if (!selected) {
|
|
263
|
-
process.exit(0);
|
|
264
|
-
}
|
|
265
|
-
worktreeRoot = selected.worktreeRoot;
|
|
266
|
-
projectIdentifier = selected.projectFolder;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
225
|
// Handle no project identifier (non-worktree mode) - show interactive picker
|
|
271
226
|
if (!projectIdentifier) {
|
|
272
227
|
// Discover worktree projects for the current repo (if in a git repo)
|
|
@@ -298,9 +253,8 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
298
253
|
// Use the selected project
|
|
299
254
|
projectIdentifier = selectedProject.folder;
|
|
300
255
|
|
|
301
|
-
// If a worktree project was selected,
|
|
256
|
+
// If a worktree project was selected, record worktree context
|
|
302
257
|
if (selectedProject.source === 'worktree' && selectedProject.worktreeRoot) {
|
|
303
|
-
worktreeMode = true;
|
|
304
258
|
worktreeRoot = selectedProject.worktreeRoot;
|
|
305
259
|
originalBranch = getCurrentBranch() ?? undefined;
|
|
306
260
|
}
|
|
@@ -316,89 +270,20 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
316
270
|
// Resolve project identifier
|
|
317
271
|
let resolvedProject: { identifier: string; path: string; name: string } | undefined;
|
|
318
272
|
|
|
319
|
-
if (
|
|
320
|
-
// Worktree
|
|
273
|
+
if (worktreeRoot) {
|
|
274
|
+
// Worktree was set by the picker — resolve project inside the worktree
|
|
321
275
|
const repoRoot = getRepoRoot()!;
|
|
322
|
-
const repoBasename = getRepoBasename()!;
|
|
323
276
|
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (!result.path) {
|
|
330
|
-
logger.error(`Project not found in worktree: ${projectIdentifier}`);
|
|
331
|
-
process.exit(1);
|
|
332
|
-
}
|
|
333
|
-
const projectName = extractProjectName(result.path) ?? projectIdentifier;
|
|
334
|
-
resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
|
|
335
|
-
} else {
|
|
336
|
-
// Explicit identifier: resolve from main repo to get folder name, then validate worktree
|
|
337
|
-
const mainResult = resolveProjectIdentifierWithDetails(rafDir, projectIdentifier);
|
|
338
|
-
|
|
339
|
-
let projectFolderName: string;
|
|
340
|
-
if (mainResult.path) {
|
|
341
|
-
// Found in main repo - use its folder name
|
|
342
|
-
projectFolderName = path.basename(mainResult.path);
|
|
343
|
-
} else {
|
|
344
|
-
// Not found in main repo - try to find it in worktrees directly
|
|
345
|
-
// This handles projects that only exist in worktrees
|
|
346
|
-
const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
|
|
347
|
-
if (!fs.existsSync(worktreeBaseDir)) {
|
|
348
|
-
logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
|
|
349
|
-
process.exit(1);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Search worktrees for the project
|
|
353
|
-
const wtProjects = listWorktreeProjects(repoBasename);
|
|
354
|
-
let found = false;
|
|
355
|
-
for (const wtProjectDir of wtProjects) {
|
|
356
|
-
const wtPath = computeWorktreePath(repoBasename, wtProjectDir);
|
|
357
|
-
const wtRafDir = path.join(wtPath, rafRelativePath);
|
|
358
|
-
if (!fs.existsSync(wtRafDir)) continue;
|
|
359
|
-
|
|
360
|
-
const resolution = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
|
|
361
|
-
if (resolution.path) {
|
|
362
|
-
projectFolderName = path.basename(resolution.path);
|
|
363
|
-
worktreeRoot = wtPath;
|
|
364
|
-
found = true;
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!found) {
|
|
370
|
-
logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Compute worktree path if not already set
|
|
376
|
-
if (!worktreeRoot) {
|
|
377
|
-
worktreeRoot = computeWorktreePath(repoBasename, projectFolderName!);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Validate the worktree
|
|
381
|
-
const wtProjectRelPath = path.join(rafRelativePath, projectFolderName!);
|
|
382
|
-
const validation = validateWorktree(worktreeRoot, wtProjectRelPath);
|
|
383
|
-
|
|
384
|
-
if (!validation.exists || !validation.isValidWorktree) {
|
|
385
|
-
logger.error(`No worktree found for project "${projectIdentifier}". Did you plan with --worktree?`);
|
|
386
|
-
logger.error(`Expected worktree at: ${worktreeRoot}`);
|
|
387
|
-
process.exit(1);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (!validation.hasProjectFolder || !validation.hasPlans) {
|
|
391
|
-
logger.error(`Worktree exists but project content is missing.`);
|
|
392
|
-
logger.error(`Expected project folder at: ${validation.projectPath ?? path.join(worktreeRoot, wtProjectRelPath)}`);
|
|
393
|
-
process.exit(1);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const projectPath = validation.projectPath!;
|
|
397
|
-
const projectName = extractProjectName(projectPath) ?? projectIdentifier;
|
|
398
|
-
resolvedProject = { identifier: projectIdentifier, path: projectPath, name: projectName };
|
|
277
|
+
const wtRafDir = path.join(worktreeRoot, rafRelativePath);
|
|
278
|
+
const result = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
|
|
279
|
+
if (!result.path) {
|
|
280
|
+
logger.error(`Project not found in worktree: ${projectIdentifier}`);
|
|
281
|
+
process.exit(1);
|
|
399
282
|
}
|
|
283
|
+
const projectName = extractProjectName(result.path) ?? projectIdentifier;
|
|
284
|
+
resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
|
|
400
285
|
} else {
|
|
401
|
-
//
|
|
286
|
+
// Auto-detect: check worktrees first (worktree takes priority), then main repo
|
|
402
287
|
const repoRoot = getRepoRoot();
|
|
403
288
|
const repoBasename = repoRoot ? getRepoBasename() : null;
|
|
404
289
|
|
|
@@ -411,8 +296,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
411
296
|
const wtProjectPath = path.join(wtRafDir, wtResolution.folder);
|
|
412
297
|
|
|
413
298
|
if (fs.existsSync(wtProjectPath)) {
|
|
414
|
-
// Auto-switch to worktree mode
|
|
415
|
-
worktreeMode = true;
|
|
416
299
|
worktreeRoot = wtResolution.worktreeRoot;
|
|
417
300
|
originalBranch = getCurrentBranch() ?? undefined;
|
|
418
301
|
|
|
@@ -456,9 +339,26 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
456
339
|
// Configure logger
|
|
457
340
|
logger.configure({ verbose, debug });
|
|
458
341
|
|
|
459
|
-
//
|
|
342
|
+
// Worktree setup: sync main branch and show post-execution picker
|
|
460
343
|
let postAction: PostExecutionAction = 'leave';
|
|
461
|
-
if (
|
|
344
|
+
if (worktreeRoot) {
|
|
345
|
+
if (!originalBranch) {
|
|
346
|
+
originalBranch = getCurrentBranch() ?? undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Sync main branch before worktree operations (if enabled)
|
|
350
|
+
if (getSyncMainBranch()) {
|
|
351
|
+
const syncResult = pullMainBranch();
|
|
352
|
+
mainBranchName = syncResult.mainBranch;
|
|
353
|
+
if (syncResult.success) {
|
|
354
|
+
if (syncResult.hadChanges) {
|
|
355
|
+
logger.info(`Synced ${syncResult.mainBranch} from remote`);
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
logger.warn(`Could not sync main branch: ${syncResult.error}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
462
362
|
try {
|
|
463
363
|
postAction = await pickPostExecutionAction(worktreeRoot);
|
|
464
364
|
} catch (error) {
|
|
@@ -499,6 +399,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
499
399
|
maxRetries,
|
|
500
400
|
autoCommit,
|
|
501
401
|
model,
|
|
402
|
+
provider: options.provider,
|
|
502
403
|
worktreeCwd: worktreeRoot,
|
|
503
404
|
}
|
|
504
405
|
);
|
|
@@ -509,7 +410,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
509
410
|
}
|
|
510
411
|
|
|
511
412
|
// Execute post-execution action based on picker choice
|
|
512
|
-
if (
|
|
413
|
+
if (worktreeRoot) {
|
|
513
414
|
const worktreeBranch = path.basename(worktreeRoot);
|
|
514
415
|
|
|
515
416
|
if (result.success) {
|
|
@@ -650,88 +551,6 @@ async function executePostAction(
|
|
|
650
551
|
}
|
|
651
552
|
}
|
|
652
553
|
|
|
653
|
-
/**
|
|
654
|
-
* Auto-discovery flow for `raf do --worktree` without a project identifier.
|
|
655
|
-
* Shows both worktree AND main-repo pending projects in an interactive picker.
|
|
656
|
-
* Worktree projects above a threshold filter are included; main-repo projects
|
|
657
|
-
* are always included. Duplicates are deduped with worktree taking precedence.
|
|
658
|
-
*
|
|
659
|
-
* @returns Selected project info or null if cancelled/no projects
|
|
660
|
-
*/
|
|
661
|
-
async function discoverAndPickWorktreeProject(
|
|
662
|
-
repoBasename: string,
|
|
663
|
-
rafDir: string,
|
|
664
|
-
rafRelativePath: string,
|
|
665
|
-
): Promise<{ worktreeRoot?: string; projectFolder: string } | null> {
|
|
666
|
-
// Get worktree pending projects (uses shared utility from project-picker)
|
|
667
|
-
const wtPendingProjects = getPendingWorktreeProjects(repoBasename, rafRelativePath);
|
|
668
|
-
|
|
669
|
-
// Find the highest-numbered completed project in the MAIN tree for threshold filter
|
|
670
|
-
const mainProjects = discoverProjects(rafDir);
|
|
671
|
-
let highestCompletedNumber = 0;
|
|
672
|
-
|
|
673
|
-
for (const project of mainProjects) {
|
|
674
|
-
const state = deriveProjectState(project.path);
|
|
675
|
-
if (isProjectComplete(state) && state.tasks.length > 0) {
|
|
676
|
-
if (project.number > highestCompletedNumber) {
|
|
677
|
-
highestCompletedNumber = project.number;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Filter threshold: highest completed - 3 (or 0 if none completed)
|
|
683
|
-
const threshold = highestCompletedNumber > 3 ? highestCompletedNumber - 3 : 0;
|
|
684
|
-
|
|
685
|
-
// Apply threshold filter to worktree projects only
|
|
686
|
-
const filteredWtProjects = wtPendingProjects.filter((p) => p.number >= threshold);
|
|
687
|
-
|
|
688
|
-
// Get main-repo pending projects (no threshold filter)
|
|
689
|
-
const mainPendingProjects = getPendingProjects(rafDir);
|
|
690
|
-
|
|
691
|
-
// Merge and deduplicate: worktree versions take precedence
|
|
692
|
-
const allProjects = [...mainPendingProjects, ...filteredWtProjects];
|
|
693
|
-
const seen = new Map<string, PendingProjectInfo>();
|
|
694
|
-
for (const project of allProjects) {
|
|
695
|
-
const existing = seen.get(project.folder);
|
|
696
|
-
if (!existing || project.source === 'worktree') {
|
|
697
|
-
seen.set(project.folder, project);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const deduped = Array.from(seen.values());
|
|
701
|
-
|
|
702
|
-
// Sort by project number
|
|
703
|
-
deduped.sort((a, b) => a.number - b.number);
|
|
704
|
-
|
|
705
|
-
if (deduped.length === 0) {
|
|
706
|
-
logger.info('No pending projects found.');
|
|
707
|
-
return null;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// Show interactive picker
|
|
711
|
-
const choices = deduped.map((p) => ({
|
|
712
|
-
name: formatProjectChoice(p),
|
|
713
|
-
value: p,
|
|
714
|
-
}));
|
|
715
|
-
|
|
716
|
-
try {
|
|
717
|
-
const selected = await select({
|
|
718
|
-
message: 'Select a project to execute:',
|
|
719
|
-
choices,
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
return {
|
|
723
|
-
worktreeRoot: selected.worktreeRoot,
|
|
724
|
-
projectFolder: selected.folder,
|
|
725
|
-
};
|
|
726
|
-
} catch (error) {
|
|
727
|
-
// Handle Ctrl+C (user cancellation)
|
|
728
|
-
if (error instanceof Error && error.message.includes('User force closed')) {
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
throw error;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
554
|
interface SingleProjectOptions {
|
|
736
555
|
timeout: number;
|
|
737
556
|
verbose: boolean;
|
|
@@ -740,6 +559,8 @@ interface SingleProjectOptions {
|
|
|
740
559
|
maxRetries: number;
|
|
741
560
|
autoCommit: boolean;
|
|
742
561
|
model: string;
|
|
562
|
+
/** CLI provider to use (claude, codex). */
|
|
563
|
+
provider?: import('../types/config.js').HarnessProvider;
|
|
743
564
|
/** Worktree root directory. When set, the runner uses cwd in the worktree. */
|
|
744
565
|
worktreeCwd?: string;
|
|
745
566
|
}
|
|
@@ -749,7 +570,7 @@ async function executeSingleProject(
|
|
|
749
570
|
projectName: string,
|
|
750
571
|
options: SingleProjectOptions
|
|
751
572
|
): Promise<ProjectExecutionResult> {
|
|
752
|
-
const { timeout, verbose, debug, force, maxRetries, autoCommit, model, worktreeCwd } = options;
|
|
573
|
+
const { timeout, verbose, debug, force, maxRetries, autoCommit, model, provider, worktreeCwd } = options;
|
|
753
574
|
|
|
754
575
|
if (!validatePlansExist(projectPath)) {
|
|
755
576
|
return {
|
|
@@ -1022,6 +843,7 @@ async function executeSingleProject(
|
|
|
1022
843
|
undefined, // warnings already logged above
|
|
1023
844
|
ceilingModel,
|
|
1024
845
|
isRetry,
|
|
846
|
+
provider,
|
|
1025
847
|
);
|
|
1026
848
|
|
|
1027
849
|
// Update current model for timer callback display
|
|
@@ -1033,7 +855,7 @@ async function executeSingleProject(
|
|
|
1033
855
|
}
|
|
1034
856
|
|
|
1035
857
|
// Create a runner for this attempt's model
|
|
1036
|
-
const taskRunner = createRunner({ model: modelResolution.model });
|
|
858
|
+
const taskRunner = createRunner({ model: modelResolution.model, provider });
|
|
1037
859
|
shutdownHandler.registerClaudeRunner(taskRunner);
|
|
1038
860
|
|
|
1039
861
|
if (verbose && isRetry) {
|