rafcode 2.0.0 → 2.1.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/.claude/settings.local.json +3 -1
- package/RAF/ahrren-turbo-finder/decisions.md +19 -0
- package/RAF/ahrren-turbo-finder/input.md +2 -0
- package/RAF/ahrren-turbo-finder/outcomes/01-worktree-auto-detect.md +40 -0
- package/RAF/ahrren-turbo-finder/outcomes/02-medium-effort-do.md +34 -0
- package/RAF/ahrren-turbo-finder/plans/01-worktree-auto-detect.md +44 -0
- package/RAF/ahrren-turbo-finder/plans/02-medium-effort-do.md +39 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +39 -17
- package/dist/commands/do.js.map +1 -1
- package/dist/core/claude-runner.d.ts +6 -0
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +12 -4
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/worktree.d.ts +18 -0
- package/dist/core/worktree.d.ts.map +1 -1
- package/dist/core/worktree.js +61 -0
- package/dist/core/worktree.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/do.ts +45 -18
- package/src/core/claude-runner.ts +20 -4
- package/src/core/worktree.ts +77 -0
- package/tests/unit/claude-runner-interactive.test.ts +24 -0
- package/tests/unit/claude-runner.test.ts +103 -0
- package/tests/unit/post-execution-picker.test.ts +1 -0
- package/tests/unit/worktree.test.ts +102 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/core/worktree.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/core/worktree.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAM3C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAM/C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAOhD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,CAgC5F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,kBAAkB,CA6CtG;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,mBAAmB,CAyC/F;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAWzF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAOxD;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,oBAAoB,CA0CtG;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBnE;AAED,MAAM,WAAW,yBAAyB;IACxC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kCAAkC,CAChD,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,yBAAyB,GAAG,IAAI,CAqDlC"}
|
package/dist/core/worktree.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { extractProjectNumber, extractProjectName, isBase26Prefix, decodeBase26 } from '../utils/paths.js';
|
|
6
7
|
/**
|
|
7
8
|
* Get the git toplevel directory (repo root).
|
|
8
9
|
* Returns null if not in a git repo.
|
|
@@ -319,4 +320,64 @@ export function listWorktreeProjects(repoBasename) {
|
|
|
319
320
|
return [];
|
|
320
321
|
}
|
|
321
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Resolve a project identifier against worktree folder names.
|
|
325
|
+
* Uses the same matching strategy as `resolveProjectIdentifierWithDetails`:
|
|
326
|
+
* 1. Full folder name match (exact, case-insensitive)
|
|
327
|
+
* 2. Base26 prefix match (6-char ID)
|
|
328
|
+
* 3. Project name match (the portion after the prefix)
|
|
329
|
+
*
|
|
330
|
+
* @param repoBasename - The basename of the current git repo
|
|
331
|
+
* @param identifier - The project identifier to resolve
|
|
332
|
+
* @returns The matched worktree project info or null if not found
|
|
333
|
+
*/
|
|
334
|
+
export function resolveWorktreeProjectByIdentifier(repoBasename, identifier) {
|
|
335
|
+
const wtProjectDirs = listWorktreeProjects(repoBasename);
|
|
336
|
+
if (wtProjectDirs.length === 0)
|
|
337
|
+
return null;
|
|
338
|
+
const lowerIdentifier = identifier.toLowerCase();
|
|
339
|
+
// 1. Full folder name match (exact, case-insensitive)
|
|
340
|
+
for (const dir of wtProjectDirs) {
|
|
341
|
+
if (dir.toLowerCase() === lowerIdentifier) {
|
|
342
|
+
return {
|
|
343
|
+
folder: dir,
|
|
344
|
+
worktreeRoot: computeWorktreePath(repoBasename, dir),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// 2. Base26 prefix match
|
|
349
|
+
if (isBase26Prefix(identifier)) {
|
|
350
|
+
const targetNumber = decodeBase26(identifier);
|
|
351
|
+
if (targetNumber !== null) {
|
|
352
|
+
for (const dir of wtProjectDirs) {
|
|
353
|
+
const prefix = extractProjectNumber(dir);
|
|
354
|
+
if (prefix) {
|
|
355
|
+
const dirNumber = decodeBase26(prefix);
|
|
356
|
+
if (dirNumber === targetNumber) {
|
|
357
|
+
return {
|
|
358
|
+
folder: dir,
|
|
359
|
+
worktreeRoot: computeWorktreePath(repoBasename, dir),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// 3. Project name match (case-insensitive)
|
|
367
|
+
const nameMatches = [];
|
|
368
|
+
for (const dir of wtProjectDirs) {
|
|
369
|
+
const name = extractProjectName(dir);
|
|
370
|
+
if (name && name.toLowerCase() === lowerIdentifier) {
|
|
371
|
+
nameMatches.push(dir);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (nameMatches.length === 1) {
|
|
375
|
+
return {
|
|
376
|
+
folder: nameMatches[0],
|
|
377
|
+
worktreeRoot: computeWorktreePath(repoBasename, nameMatches[0]),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
// Ambiguous or no match
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
322
383
|
//# sourceMappingURL=worktree.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/core/worktree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/core/worktree.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/commands/do.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
listWorktreeProjects,
|
|
45
45
|
mergeWorktreeBranch,
|
|
46
46
|
removeWorktree,
|
|
47
|
+
resolveWorktreeProjectByIdentifier,
|
|
47
48
|
} from '../core/worktree.js';
|
|
48
49
|
import { createPullRequest, prPreflight } from '../core/pull-request.js';
|
|
49
50
|
import type { DoCommandOptions } from '../types/config.js';
|
|
@@ -219,7 +220,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
// Resolve project identifier
|
|
222
|
-
let resolvedProject: { identifier: string; path: string; name: string };
|
|
223
|
+
let resolvedProject: { identifier: string; path: string; name: string } | undefined;
|
|
223
224
|
|
|
224
225
|
if (worktreeMode) {
|
|
225
226
|
// Worktree mode: resolve project inside the worktree
|
|
@@ -303,24 +304,50 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
|
|
|
303
304
|
resolvedProject = { identifier: projectIdentifier, path: projectPath, name: projectName };
|
|
304
305
|
}
|
|
305
306
|
} else {
|
|
306
|
-
// Standard mode:
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
307
|
+
// Standard mode: check worktrees first (worktree takes priority), then main repo
|
|
308
|
+
const repoRoot = getRepoRoot();
|
|
309
|
+
const repoBasename = repoRoot ? getRepoBasename() : null;
|
|
310
|
+
|
|
311
|
+
// Try worktree resolution first (preferred when project exists in both)
|
|
312
|
+
if (repoBasename) {
|
|
313
|
+
const wtResolution = resolveWorktreeProjectByIdentifier(repoBasename, projectIdentifier);
|
|
314
|
+
if (wtResolution) {
|
|
315
|
+
const rafRelativePath = path.relative(repoRoot!, rafDir);
|
|
316
|
+
const wtRafDir = path.join(wtResolution.worktreeRoot, rafRelativePath);
|
|
317
|
+
const wtProjectPath = path.join(wtRafDir, wtResolution.folder);
|
|
318
|
+
|
|
319
|
+
if (fs.existsSync(wtProjectPath)) {
|
|
320
|
+
// Auto-switch to worktree mode
|
|
321
|
+
worktreeMode = true;
|
|
322
|
+
worktreeRoot = wtResolution.worktreeRoot;
|
|
323
|
+
originalBranch = getCurrentBranch() ?? undefined;
|
|
324
|
+
|
|
325
|
+
const projectName = extractProjectName(wtResolution.folder) ?? projectIdentifier;
|
|
326
|
+
resolvedProject = { identifier: projectIdentifier, path: wtProjectPath, name: projectName };
|
|
327
|
+
}
|
|
317
328
|
}
|
|
318
|
-
logger.info("Run 'raf status' to see available projects.");
|
|
319
|
-
process.exit(1);
|
|
320
329
|
}
|
|
321
330
|
|
|
322
|
-
|
|
323
|
-
resolvedProject
|
|
331
|
+
// Fall back to main repo if worktree didn't match
|
|
332
|
+
if (!resolvedProject) {
|
|
333
|
+
const result = resolveProjectIdentifierWithDetails(rafDir, projectIdentifier);
|
|
334
|
+
|
|
335
|
+
if (!result.path) {
|
|
336
|
+
if (result.error === 'ambiguous' && result.matches) {
|
|
337
|
+
const matchList = result.matches
|
|
338
|
+
.map((m) => ` - ${m.folder}`)
|
|
339
|
+
.join('\n');
|
|
340
|
+
logger.error(`${projectIdentifier}: Ambiguous project name. Multiple projects match:\n${matchList}\nPlease specify the project ID or full folder name.`);
|
|
341
|
+
} else {
|
|
342
|
+
logger.error(`${projectIdentifier}: Project not found`);
|
|
343
|
+
}
|
|
344
|
+
logger.info("Run 'raf status' to see available projects.");
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const projectName = extractProjectName(result.path) ?? projectIdentifier;
|
|
349
|
+
resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
|
|
350
|
+
}
|
|
324
351
|
}
|
|
325
352
|
|
|
326
353
|
// Get configuration
|
|
@@ -914,8 +941,8 @@ async function executeSingleProject(
|
|
|
914
941
|
|
|
915
942
|
// Run Claude (use worktree root as cwd if in worktree mode)
|
|
916
943
|
const result = verbose
|
|
917
|
-
? await claudeRunner.runVerbose(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd })
|
|
918
|
-
: await claudeRunner.run(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd });
|
|
944
|
+
? await claudeRunner.runVerbose(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' })
|
|
945
|
+
: await claudeRunner.run(prompt, { timeout, outcomeFilePath, commitContext, cwd: worktreeCwd, effortLevel: 'medium' });
|
|
919
946
|
|
|
920
947
|
lastOutput = result.output;
|
|
921
948
|
|
|
@@ -50,6 +50,12 @@ export interface ClaudeRunnerOptions {
|
|
|
50
50
|
/** Path to the outcome file that should be committed. */
|
|
51
51
|
outcomeFilePath: string;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Claude Code reasoning effort level.
|
|
55
|
+
* Sets CLAUDE_CODE_EFFORT_LEVEL env var for the spawned process.
|
|
56
|
+
* Only applied in non-interactive modes (run, runVerbose).
|
|
57
|
+
*/
|
|
58
|
+
effortLevel?: 'low' | 'medium' | 'high';
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
export interface ClaudeRunnerConfig {
|
|
@@ -367,7 +373,7 @@ export class ClaudeRunner {
|
|
|
367
373
|
* - Default timeout is 60 minutes if not specified
|
|
368
374
|
*/
|
|
369
375
|
async run(prompt: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
|
|
370
|
-
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext } = options;
|
|
376
|
+
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel } = options;
|
|
371
377
|
// Ensure timeout is a positive number, fallback to 60 minutes
|
|
372
378
|
const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
|
|
373
379
|
const timeoutMs = validatedTimeout * 60 * 1000;
|
|
@@ -383,6 +389,11 @@ export class ClaudeRunner {
|
|
|
383
389
|
logger.debug(`Starting Claude execution session with model: ${this.model}`);
|
|
384
390
|
logger.debug(`Claude path: ${claudePath}`);
|
|
385
391
|
|
|
392
|
+
// Build env, optionally injecting effort level
|
|
393
|
+
const env = effortLevel
|
|
394
|
+
? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
|
|
395
|
+
: process.env;
|
|
396
|
+
|
|
386
397
|
// Use --append-system-prompt to add RAF instructions to system prompt
|
|
387
398
|
// This gives RAF instructions stronger precedence than passing as user message
|
|
388
399
|
// --dangerously-skip-permissions bypasses interactive prompts
|
|
@@ -397,7 +408,7 @@ export class ClaudeRunner {
|
|
|
397
408
|
'Execute the task as described in the system prompt.',
|
|
398
409
|
], {
|
|
399
410
|
cwd,
|
|
400
|
-
env
|
|
411
|
+
env,
|
|
401
412
|
stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
|
|
402
413
|
});
|
|
403
414
|
|
|
@@ -474,7 +485,7 @@ export class ClaudeRunner {
|
|
|
474
485
|
* - Default timeout is 60 minutes if not specified
|
|
475
486
|
*/
|
|
476
487
|
async runVerbose(prompt: string, options: ClaudeRunnerOptions = {}): Promise<RunResult> {
|
|
477
|
-
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext } = options;
|
|
488
|
+
const { timeout = 60, cwd = process.cwd(), outcomeFilePath, commitContext, effortLevel } = options;
|
|
478
489
|
// Ensure timeout is a positive number, fallback to 60 minutes
|
|
479
490
|
const validatedTimeout = Number(timeout) > 0 ? Number(timeout) : 60;
|
|
480
491
|
const timeoutMs = validatedTimeout * 60 * 1000;
|
|
@@ -491,6 +502,11 @@ export class ClaudeRunner {
|
|
|
491
502
|
logger.debug(`Prompt length: ${prompt.length}, timeout: ${timeoutMs}ms, cwd: ${cwd}`);
|
|
492
503
|
logger.debug(`Claude path: ${claudePath}`);
|
|
493
504
|
|
|
505
|
+
// Build env, optionally injecting effort level
|
|
506
|
+
const env = effortLevel
|
|
507
|
+
? { ...process.env, CLAUDE_CODE_EFFORT_LEVEL: effortLevel }
|
|
508
|
+
: process.env;
|
|
509
|
+
|
|
494
510
|
logger.debug('Spawning process...');
|
|
495
511
|
// Use --output-format stream-json --verbose to get real-time streaming events
|
|
496
512
|
// including tool calls, file operations, and intermediate output.
|
|
@@ -509,7 +525,7 @@ export class ClaudeRunner {
|
|
|
509
525
|
'Execute the task as described in the system prompt.',
|
|
510
526
|
], {
|
|
511
527
|
cwd,
|
|
512
|
-
env
|
|
528
|
+
env,
|
|
513
529
|
stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
|
|
514
530
|
});
|
|
515
531
|
|
package/src/core/worktree.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
|
+
import { extractProjectNumber, extractProjectName, isBase26Prefix, decodeBase26 } from '../utils/paths.js';
|
|
6
7
|
|
|
7
8
|
export interface WorktreeCreateResult {
|
|
8
9
|
success: boolean;
|
|
@@ -355,3 +356,79 @@ export function listWorktreeProjects(repoBasename: string): string[] {
|
|
|
355
356
|
return [];
|
|
356
357
|
}
|
|
357
358
|
}
|
|
359
|
+
|
|
360
|
+
export interface WorktreeProjectResolution {
|
|
361
|
+
/** The worktree project folder name (e.g., "ahrren-turbo-finder") */
|
|
362
|
+
folder: string;
|
|
363
|
+
/** The worktree root path (e.g., ~/.raf/worktrees/RAF/ahrren-turbo-finder) */
|
|
364
|
+
worktreeRoot: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Resolve a project identifier against worktree folder names.
|
|
369
|
+
* Uses the same matching strategy as `resolveProjectIdentifierWithDetails`:
|
|
370
|
+
* 1. Full folder name match (exact, case-insensitive)
|
|
371
|
+
* 2. Base26 prefix match (6-char ID)
|
|
372
|
+
* 3. Project name match (the portion after the prefix)
|
|
373
|
+
*
|
|
374
|
+
* @param repoBasename - The basename of the current git repo
|
|
375
|
+
* @param identifier - The project identifier to resolve
|
|
376
|
+
* @returns The matched worktree project info or null if not found
|
|
377
|
+
*/
|
|
378
|
+
export function resolveWorktreeProjectByIdentifier(
|
|
379
|
+
repoBasename: string,
|
|
380
|
+
identifier: string,
|
|
381
|
+
): WorktreeProjectResolution | null {
|
|
382
|
+
const wtProjectDirs = listWorktreeProjects(repoBasename);
|
|
383
|
+
if (wtProjectDirs.length === 0) return null;
|
|
384
|
+
|
|
385
|
+
const lowerIdentifier = identifier.toLowerCase();
|
|
386
|
+
|
|
387
|
+
// 1. Full folder name match (exact, case-insensitive)
|
|
388
|
+
for (const dir of wtProjectDirs) {
|
|
389
|
+
if (dir.toLowerCase() === lowerIdentifier) {
|
|
390
|
+
return {
|
|
391
|
+
folder: dir,
|
|
392
|
+
worktreeRoot: computeWorktreePath(repoBasename, dir),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 2. Base26 prefix match
|
|
398
|
+
if (isBase26Prefix(identifier)) {
|
|
399
|
+
const targetNumber = decodeBase26(identifier);
|
|
400
|
+
if (targetNumber !== null) {
|
|
401
|
+
for (const dir of wtProjectDirs) {
|
|
402
|
+
const prefix = extractProjectNumber(dir);
|
|
403
|
+
if (prefix) {
|
|
404
|
+
const dirNumber = decodeBase26(prefix);
|
|
405
|
+
if (dirNumber === targetNumber) {
|
|
406
|
+
return {
|
|
407
|
+
folder: dir,
|
|
408
|
+
worktreeRoot: computeWorktreePath(repoBasename, dir),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 3. Project name match (case-insensitive)
|
|
417
|
+
const nameMatches: string[] = [];
|
|
418
|
+
for (const dir of wtProjectDirs) {
|
|
419
|
+
const name = extractProjectName(dir);
|
|
420
|
+
if (name && name.toLowerCase() === lowerIdentifier) {
|
|
421
|
+
nameMatches.push(dir);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (nameMatches.length === 1) {
|
|
426
|
+
return {
|
|
427
|
+
folder: nameMatches[0]!,
|
|
428
|
+
worktreeRoot: computeWorktreePath(repoBasename, nameMatches[0]!),
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Ambiguous or no match
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
@@ -244,6 +244,30 @@ describe('ClaudeRunner - runInteractive', () => {
|
|
|
244
244
|
});
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
+
describe('effort level (not applied in interactive mode)', () => {
|
|
248
|
+
it('should NOT set CLAUDE_CODE_EFFORT_LEVEL in runInteractive env', async () => {
|
|
249
|
+
const mockProc = createMockPtyProcess();
|
|
250
|
+
const mockStdin = createMockStdin();
|
|
251
|
+
const mockStdout = createMockStdout();
|
|
252
|
+
|
|
253
|
+
Object.defineProperty(process, 'stdin', { value: mockStdin, configurable: true });
|
|
254
|
+
Object.defineProperty(process, 'stdout', { value: mockStdout, configurable: true });
|
|
255
|
+
|
|
256
|
+
mockPtySpawn.mockReturnValue(mockProc);
|
|
257
|
+
|
|
258
|
+
const runner = new ClaudeRunner();
|
|
259
|
+
// Even if effortLevel were somehow passed, interactive mode should use process.env as-is
|
|
260
|
+
const runPromise = runner.runInteractive('system', 'user');
|
|
261
|
+
|
|
262
|
+
const spawnOptions = mockPtySpawn.mock.calls[0][2];
|
|
263
|
+
// Interactive mode passes process.env directly, no effort level override
|
|
264
|
+
expect(spawnOptions.env).not.toHaveProperty('CLAUDE_CODE_EFFORT_LEVEL');
|
|
265
|
+
|
|
266
|
+
mockProc._exitCallback({ exitCode: 0 });
|
|
267
|
+
await runPromise;
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
247
271
|
describe('--dangerously-skip-permissions flag', () => {
|
|
248
272
|
it('should NOT include --dangerously-skip-permissions by default', async () => {
|
|
249
273
|
const mockProc = createMockPtyProcess();
|
|
@@ -472,6 +472,109 @@ describe('ClaudeRunner', () => {
|
|
|
472
472
|
});
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
+
describe('effort level', () => {
|
|
476
|
+
function createMockProcess() {
|
|
477
|
+
const stdout = new EventEmitter();
|
|
478
|
+
const stderr = new EventEmitter();
|
|
479
|
+
const proc = new EventEmitter() as any;
|
|
480
|
+
proc.stdout = stdout;
|
|
481
|
+
proc.stderr = stderr;
|
|
482
|
+
proc.kill = jest.fn();
|
|
483
|
+
return proc;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
it('should set CLAUDE_CODE_EFFORT_LEVEL env var in run() when effortLevel is provided', async () => {
|
|
487
|
+
const mockProc = createMockProcess();
|
|
488
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
489
|
+
|
|
490
|
+
const runner = new ClaudeRunner();
|
|
491
|
+
const runPromise = runner.run('test prompt', { timeout: 60, effortLevel: 'medium' });
|
|
492
|
+
|
|
493
|
+
mockProc.emit('close', 0);
|
|
494
|
+
await runPromise;
|
|
495
|
+
|
|
496
|
+
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
497
|
+
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe('medium');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('should set CLAUDE_CODE_EFFORT_LEVEL env var in runVerbose() when effortLevel is provided', async () => {
|
|
501
|
+
const mockProc = createMockProcess();
|
|
502
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
503
|
+
|
|
504
|
+
const runner = new ClaudeRunner();
|
|
505
|
+
const runPromise = runner.runVerbose('test prompt', { timeout: 60, effortLevel: 'medium' });
|
|
506
|
+
|
|
507
|
+
mockProc.emit('close', 0);
|
|
508
|
+
await runPromise;
|
|
509
|
+
|
|
510
|
+
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
511
|
+
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe('medium');
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should NOT set CLAUDE_CODE_EFFORT_LEVEL when effortLevel is not provided in run()', async () => {
|
|
515
|
+
const mockProc = createMockProcess();
|
|
516
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
517
|
+
|
|
518
|
+
const runner = new ClaudeRunner();
|
|
519
|
+
const runPromise = runner.run('test prompt', { timeout: 60 });
|
|
520
|
+
|
|
521
|
+
mockProc.emit('close', 0);
|
|
522
|
+
await runPromise;
|
|
523
|
+
|
|
524
|
+
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
525
|
+
// env should be process.env directly (no CLAUDE_CODE_EFFORT_LEVEL override)
|
|
526
|
+
expect(spawnOptions.env).toBe(process.env);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('should NOT set CLAUDE_CODE_EFFORT_LEVEL when effortLevel is not provided in runVerbose()', async () => {
|
|
530
|
+
const mockProc = createMockProcess();
|
|
531
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
532
|
+
|
|
533
|
+
const runner = new ClaudeRunner();
|
|
534
|
+
const runPromise = runner.runVerbose('test prompt', { timeout: 60 });
|
|
535
|
+
|
|
536
|
+
mockProc.emit('close', 0);
|
|
537
|
+
await runPromise;
|
|
538
|
+
|
|
539
|
+
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
540
|
+
// env should be process.env directly (no CLAUDE_CODE_EFFORT_LEVEL override)
|
|
541
|
+
expect(spawnOptions.env).toBe(process.env);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should support different effort levels', async () => {
|
|
545
|
+
for (const level of ['low', 'medium', 'high'] as const) {
|
|
546
|
+
const mockProc = createMockProcess();
|
|
547
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
548
|
+
|
|
549
|
+
const runner = new ClaudeRunner();
|
|
550
|
+
const runPromise = runner.run('test prompt', { timeout: 60, effortLevel: level });
|
|
551
|
+
|
|
552
|
+
mockProc.emit('close', 0);
|
|
553
|
+
await runPromise;
|
|
554
|
+
|
|
555
|
+
const spawnOptions = mockSpawn.mock.calls[mockSpawn.mock.calls.length - 1][2];
|
|
556
|
+
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe(level);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('should preserve other env vars when effortLevel is set', async () => {
|
|
561
|
+
const mockProc = createMockProcess();
|
|
562
|
+
mockSpawn.mockReturnValue(mockProc);
|
|
563
|
+
|
|
564
|
+
const runner = new ClaudeRunner();
|
|
565
|
+
const runPromise = runner.run('test prompt', { timeout: 60, effortLevel: 'medium' });
|
|
566
|
+
|
|
567
|
+
mockProc.emit('close', 0);
|
|
568
|
+
await runPromise;
|
|
569
|
+
|
|
570
|
+
const spawnOptions = mockSpawn.mock.calls[0][2];
|
|
571
|
+
// Should have PATH from process.env
|
|
572
|
+
expect(spawnOptions.env.PATH).toBe(process.env.PATH);
|
|
573
|
+
// And the injected effort level
|
|
574
|
+
expect(spawnOptions.env.CLAUDE_CODE_EFFORT_LEVEL).toBe('medium');
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
475
578
|
describe('system prompt append flag', () => {
|
|
476
579
|
function createMockProcess() {
|
|
477
580
|
const stdout = new EventEmitter();
|
|
@@ -42,6 +42,7 @@ const {
|
|
|
42
42
|
mergeWorktreeBranch,
|
|
43
43
|
removeWorktree,
|
|
44
44
|
listWorktreeProjects,
|
|
45
|
+
resolveWorktreeProjectByIdentifier,
|
|
45
46
|
} = await import('../../src/core/worktree.js');
|
|
46
47
|
|
|
47
48
|
const HOME = os.homedir();
|
|
@@ -520,4 +521,105 @@ describe('worktree utilities', () => {
|
|
|
520
521
|
expect(result).toEqual([]);
|
|
521
522
|
});
|
|
522
523
|
});
|
|
524
|
+
|
|
525
|
+
describe('resolveWorktreeProjectByIdentifier', () => {
|
|
526
|
+
const worktreeDirs = [
|
|
527
|
+
{ name: 'ahrren-turbo-finder', isDirectory: () => true },
|
|
528
|
+
{ name: 'abcdef-cool-feature', isDirectory: () => true },
|
|
529
|
+
{ name: 'ghijkl-another-thing', isDirectory: () => true },
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
beforeEach(() => {
|
|
533
|
+
mockExistsSync.mockReturnValue(true);
|
|
534
|
+
mockReaddirSync.mockReturnValue(worktreeDirs);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should resolve by full folder name (exact match)', () => {
|
|
538
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'ahrren-turbo-finder');
|
|
539
|
+
|
|
540
|
+
expect(result).not.toBeNull();
|
|
541
|
+
expect(result!.folder).toBe('ahrren-turbo-finder');
|
|
542
|
+
expect(result!.worktreeRoot).toBe(
|
|
543
|
+
path.join(HOME, '.raf', 'worktrees', 'myapp', 'ahrren-turbo-finder')
|
|
544
|
+
);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('should resolve by full folder name case-insensitively', () => {
|
|
548
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'Ahrren-Turbo-Finder');
|
|
549
|
+
|
|
550
|
+
expect(result).not.toBeNull();
|
|
551
|
+
expect(result!.folder).toBe('ahrren-turbo-finder');
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should resolve by base26 prefix (6-char ID)', () => {
|
|
555
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'ahrren');
|
|
556
|
+
|
|
557
|
+
expect(result).not.toBeNull();
|
|
558
|
+
expect(result!.folder).toBe('ahrren-turbo-finder');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('should resolve by base26 prefix for different project', () => {
|
|
562
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'abcdef');
|
|
563
|
+
|
|
564
|
+
expect(result).not.toBeNull();
|
|
565
|
+
expect(result!.folder).toBe('abcdef-cool-feature');
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should resolve by project name', () => {
|
|
569
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'turbo-finder');
|
|
570
|
+
|
|
571
|
+
expect(result).not.toBeNull();
|
|
572
|
+
expect(result!.folder).toBe('ahrren-turbo-finder');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should resolve by project name case-insensitively', () => {
|
|
576
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'Turbo-Finder');
|
|
577
|
+
|
|
578
|
+
expect(result).not.toBeNull();
|
|
579
|
+
expect(result!.folder).toBe('ahrren-turbo-finder');
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('should return null when no match found', () => {
|
|
583
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'nonexistent');
|
|
584
|
+
|
|
585
|
+
expect(result).toBeNull();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('should return null when no worktree projects exist', () => {
|
|
589
|
+
mockExistsSync.mockReturnValue(false);
|
|
590
|
+
|
|
591
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'turbo-finder');
|
|
592
|
+
|
|
593
|
+
expect(result).toBeNull();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should return null for ambiguous name match (multiple projects with same name)', () => {
|
|
597
|
+
mockReaddirSync.mockReturnValue([
|
|
598
|
+
{ name: 'ahrren-my-feature', isDirectory: () => true },
|
|
599
|
+
{ name: 'abcdef-my-feature', isDirectory: () => true },
|
|
600
|
+
]);
|
|
601
|
+
|
|
602
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'my-feature');
|
|
603
|
+
|
|
604
|
+
// Ambiguous: two projects named "my-feature"
|
|
605
|
+
expect(result).toBeNull();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should prefer full folder name match over name match', () => {
|
|
609
|
+
// "abcdef-cool-feature" could match as full folder name
|
|
610
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'abcdef-cool-feature');
|
|
611
|
+
|
|
612
|
+
expect(result).not.toBeNull();
|
|
613
|
+
expect(result!.folder).toBe('abcdef-cool-feature');
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('should return correct worktreeRoot path', () => {
|
|
617
|
+
const result = resolveWorktreeProjectByIdentifier('myapp', 'another-thing');
|
|
618
|
+
|
|
619
|
+
expect(result).not.toBeNull();
|
|
620
|
+
expect(result!.worktreeRoot).toBe(
|
|
621
|
+
path.join(HOME, '.raf', 'worktrees', 'myapp', 'ghijkl-another-thing')
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
523
625
|
});
|