rafcode 2.5.0-0 → 2.5.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.md +1 -1
- package/RAF/ahwvrz-legacy-sunset/decisions.md +10 -0
- package/RAF/ahwvrz-legacy-sunset/input.md +10 -0
- package/RAF/ahwvrz-legacy-sunset/outcomes/01-remove-migrate-command.md +30 -0
- package/RAF/ahwvrz-legacy-sunset/outcomes/02-fix-resume-worktree-resolution.md +62 -0
- package/RAF/ahwvrz-legacy-sunset/plans/01-remove-migrate-command.md +65 -0
- package/RAF/ahwvrz-legacy-sunset/plans/02-fix-resume-worktree-resolution.md +72 -0
- package/RAF/ahwzmc-echo-forge/decisions.md +15 -0
- package/RAF/ahwzmc-echo-forge/input.md +4 -0
- package/RAF/ahwzmc-echo-forge/outcomes/01-change-low-effort-default-to-sonnet.md +57 -0
- package/RAF/ahwzmc-echo-forge/outcomes/02-add-no-worktree-flag.md +79 -0
- package/RAF/ahwzmc-echo-forge/outcomes/03-update-readme.md +75 -0
- package/RAF/ahwzmc-echo-forge/plans/01-change-low-effort-default-to-sonnet.md +57 -0
- package/RAF/ahwzmc-echo-forge/plans/02-add-no-worktree-flag.md +51 -0
- package/RAF/ahwzmc-echo-forge/plans/03-update-readme.md +48 -0
- package/RAF/aifqwf-fix-amend-commit-again/decisions.md +7 -0
- package/RAF/aifqwf-fix-amend-commit-again/input.md +2 -0
- package/RAF/aifqwf-fix-amend-commit-again/outcomes/01-update-effort-mapping-defaults.md +35 -0
- package/RAF/aifqwf-fix-amend-commit-again/outcomes/02-fix-amend-worktree-commit.md +50 -0
- package/RAF/aifqwf-fix-amend-commit-again/plans/01-update-effort-mapping-defaults.md +37 -0
- package/RAF/aifqwf-fix-amend-commit-again/plans/02-fix-amend-worktree-commit.md +55 -0
- package/README.md +26 -29
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +1 -0
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +51 -39
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +20 -4
- package/dist/core/git.js.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.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 +4 -1
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +0 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +2 -2
- package/dist/types/config.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/do.ts +1 -0
- package/src/commands/plan.ts +54 -42
- package/src/core/git.ts +23 -4
- package/src/index.ts +0 -2
- package/src/prompts/amend.ts +3 -1
- package/src/prompts/config-docs.md +7 -7
- package/src/prompts/planning.ts +4 -1
- package/src/types/config.ts +2 -7
- package/tests/unit/commit-planning-artifacts-worktree.test.ts +113 -0
- package/tests/unit/commit-planning-artifacts.test.ts +1 -1
- package/tests/unit/config-command.test.ts +2 -2
- package/tests/unit/config.test.ts +14 -14
- package/tests/unit/plan-resume-worktree-resolution.test.ts +153 -0
- package/tests/unit/worktree-flag-override.test.ts +186 -0
- package/src/commands/migrate.ts +0 -269
- package/tests/unit/migrate-command.test.ts +0 -197
package/src/commands/plan.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
removeWorktree,
|
|
50
50
|
computeWorktreeBaseDir,
|
|
51
51
|
pullMainBranch,
|
|
52
|
+
resolveWorktreeProjectByIdentifier,
|
|
52
53
|
} from '../core/worktree.js';
|
|
53
54
|
|
|
54
55
|
interface PlanCommandOptions {
|
|
@@ -72,6 +73,7 @@ export function createPlanCommand(): Command {
|
|
|
72
73
|
.option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
|
|
73
74
|
.option('-y, --auto', "Skip Claude's permission prompts for file operations")
|
|
74
75
|
.option('-w, --worktree', 'Create a git worktree for isolated planning')
|
|
76
|
+
.option('--no-worktree', 'Disable worktree mode (overrides config)')
|
|
75
77
|
.option('-r, --resume <identifier>', 'Resume a planning session for an existing project')
|
|
76
78
|
.action(async (projectName: string | undefined, options: PlanCommandOptions) => {
|
|
77
79
|
// Validate and resolve model option
|
|
@@ -317,8 +319,12 @@ async function runPlanCommand(projectName?: string, model?: string, autoMode: bo
|
|
|
317
319
|
logger.info(` - plans/${planFile}`);
|
|
318
320
|
}
|
|
319
321
|
|
|
320
|
-
// Commit planning artifacts (input.md
|
|
321
|
-
|
|
322
|
+
// Commit planning artifacts (input.md, decisions.md, and plan files)
|
|
323
|
+
const planAbsolutePaths = planFiles.map((f) => path.join(plansDir, f));
|
|
324
|
+
await commitPlanningArtifacts(projectPath, {
|
|
325
|
+
cwd: worktreePath ?? undefined,
|
|
326
|
+
additionalFiles: planAbsolutePaths,
|
|
327
|
+
});
|
|
322
328
|
|
|
323
329
|
logger.newline();
|
|
324
330
|
if (worktreeMode) {
|
|
@@ -631,10 +637,12 @@ async function runAmendCommand(identifier: string, model?: string, autoMode: boo
|
|
|
631
637
|
logger.info(` - plans/${planFile}`);
|
|
632
638
|
}
|
|
633
639
|
|
|
634
|
-
// Commit planning artifacts (input.md, decisions.md
|
|
640
|
+
// Commit planning artifacts (input.md, decisions.md, and new plan files)
|
|
641
|
+
const newPlanAbsolutePaths = newPlanFiles.map((f) => path.join(plansDir, f));
|
|
635
642
|
await commitPlanningArtifacts(projectPath, {
|
|
636
643
|
cwd: worktreePath ?? undefined,
|
|
637
644
|
isAmend: true,
|
|
645
|
+
additionalFiles: newPlanAbsolutePaths,
|
|
638
646
|
});
|
|
639
647
|
|
|
640
648
|
logger.newline();
|
|
@@ -690,54 +698,58 @@ async function runResumeCommand(identifier: string, model?: string): Promise<voi
|
|
|
690
698
|
process.exit(1);
|
|
691
699
|
}
|
|
692
700
|
|
|
693
|
-
//
|
|
701
|
+
// Try to resolve the project from worktrees first, then fall back to main repo
|
|
702
|
+
const repoBasename = getRepoBasename();
|
|
694
703
|
const rafDir = getRafDir();
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
704
|
+
|
|
705
|
+
let projectPath: string | undefined;
|
|
706
|
+
let resumeCwd: string | undefined;
|
|
707
|
+
let folderName: string | undefined;
|
|
708
|
+
|
|
709
|
+
// 1. Try worktree resolution first (if we're in a git repo)
|
|
710
|
+
if (repoBasename) {
|
|
711
|
+
const worktreeResolution = resolveWorktreeProjectByIdentifier(repoBasename, identifier);
|
|
712
|
+
|
|
713
|
+
if (worktreeResolution) {
|
|
714
|
+
// Found in worktree - validate and use it
|
|
715
|
+
const wtValidation = validateWorktree(worktreeResolution.worktreeRoot, '');
|
|
716
|
+
|
|
717
|
+
if (wtValidation.isValidWorktree) {
|
|
718
|
+
folderName = worktreeResolution.folder;
|
|
719
|
+
const repoRoot = getRepoRoot()!;
|
|
720
|
+
const rafRelativePath = path.relative(repoRoot, rafDir);
|
|
721
|
+
projectPath = path.join(worktreeResolution.worktreeRoot, rafRelativePath, folderName);
|
|
722
|
+
resumeCwd = worktreeResolution.worktreeRoot;
|
|
723
|
+
logger.info(`Resuming session in worktree: ${resumeCwd}`);
|
|
724
|
+
} else {
|
|
725
|
+
logger.warn(`Worktree found but invalid: ${worktreeResolution.worktreeRoot}`);
|
|
726
|
+
logger.warn('Falling back to main repo resolution.');
|
|
727
|
+
// Fall through to main repo resolution
|
|
703
728
|
}
|
|
704
|
-
logger.error('Please specify the project ID or full folder name.');
|
|
705
|
-
} else {
|
|
706
|
-
logger.error(`Project not found: ${identifier}`);
|
|
707
729
|
}
|
|
708
|
-
process.exit(1);
|
|
709
730
|
}
|
|
710
731
|
|
|
711
|
-
|
|
712
|
-
|
|
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);
|
|
732
|
+
// 2. If not found in worktree (or invalid), try main repo
|
|
733
|
+
if (!projectPath) {
|
|
734
|
+
const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
|
|
720
735
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
(
|
|
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.');
|
|
736
|
+
if (!mainResolution.path) {
|
|
737
|
+
if (mainResolution.error === 'ambiguous' && mainResolution.matches) {
|
|
738
|
+
logger.error(`Ambiguous project name: ${identifier}`);
|
|
739
|
+
logger.error('Multiple projects match:');
|
|
740
|
+
for (const match of mainResolution.matches) {
|
|
741
|
+
logger.error(` - ${match.folder}`);
|
|
738
742
|
}
|
|
743
|
+
logger.error('Please specify the project ID or full folder name.');
|
|
744
|
+
} else {
|
|
745
|
+
logger.error(`Project not found: ${identifier}`);
|
|
739
746
|
}
|
|
747
|
+
process.exit(1);
|
|
740
748
|
}
|
|
749
|
+
|
|
750
|
+
projectPath = mainResolution.path;
|
|
751
|
+
folderName = path.basename(projectPath);
|
|
752
|
+
resumeCwd = projectPath; // Use main repo project path as CWD
|
|
741
753
|
}
|
|
742
754
|
|
|
743
755
|
logger.info(`Project: ${folderName}`);
|
package/src/core/git.ts
CHANGED
|
@@ -227,6 +227,9 @@ export function isFileCommittedInHead(filePath: string): boolean {
|
|
|
227
227
|
*/
|
|
228
228
|
export async function commitPlanningArtifacts(projectPath: string, options?: { cwd?: string; additionalFiles?: string[]; isAmend?: boolean }): Promise<void> {
|
|
229
229
|
const execCwd = options?.cwd;
|
|
230
|
+
const execOpts = execCwd ? { cwd: execCwd } : {};
|
|
231
|
+
|
|
232
|
+
logger.debug(`commitPlanningArtifacts: projectPath=${projectPath}, cwd=${execCwd ?? 'process.cwd()'}, isAmend=${options?.isAmend ?? false}`);
|
|
230
233
|
|
|
231
234
|
// Check if we're in a git repository
|
|
232
235
|
if (!isGitRepo(execCwd)) {
|
|
@@ -268,6 +271,20 @@ export async function commitPlanningArtifacts(projectPath: string, options?: { c
|
|
|
268
271
|
? absoluteFiles.map(f => path.relative(execCwd, f))
|
|
269
272
|
: absoluteFiles;
|
|
270
273
|
|
|
274
|
+
logger.debug(`commitPlanningArtifacts: staging files: ${filesToStage.join(', ')}`);
|
|
275
|
+
|
|
276
|
+
// Check git status before staging to understand the current state
|
|
277
|
+
try {
|
|
278
|
+
const preStatus = execSync('git status --porcelain', {
|
|
279
|
+
encoding: 'utf-8',
|
|
280
|
+
stdio: 'pipe',
|
|
281
|
+
...execOpts,
|
|
282
|
+
}).trim();
|
|
283
|
+
logger.debug(`commitPlanningArtifacts: pre-stage git status:\n${preStatus || '(clean)'}`);
|
|
284
|
+
} catch {
|
|
285
|
+
logger.debug('commitPlanningArtifacts: could not get pre-stage git status');
|
|
286
|
+
}
|
|
287
|
+
|
|
271
288
|
// Stage each file individually so one missing file doesn't block the others
|
|
272
289
|
let stagedCount = 0;
|
|
273
290
|
for (const file of filesToStage) {
|
|
@@ -275,7 +292,7 @@ export async function commitPlanningArtifacts(projectPath: string, options?: { c
|
|
|
275
292
|
execSync(`git add -- "${file}"`, {
|
|
276
293
|
encoding: 'utf-8',
|
|
277
294
|
stdio: 'pipe',
|
|
278
|
-
...
|
|
295
|
+
...execOpts,
|
|
279
296
|
});
|
|
280
297
|
stagedCount++;
|
|
281
298
|
} catch (error) {
|
|
@@ -294,19 +311,21 @@ export async function commitPlanningArtifacts(projectPath: string, options?: { c
|
|
|
294
311
|
const stagedStatus = execSync('git diff --cached --name-only', {
|
|
295
312
|
encoding: 'utf-8',
|
|
296
313
|
stdio: 'pipe',
|
|
297
|
-
...
|
|
314
|
+
...execOpts,
|
|
298
315
|
}).trim();
|
|
299
316
|
|
|
300
317
|
if (!stagedStatus) {
|
|
301
|
-
logger.debug('No changes to planning artifacts to commit');
|
|
318
|
+
logger.debug('No changes to planning artifacts to commit (git add succeeded but nothing changed in index)');
|
|
302
319
|
return;
|
|
303
320
|
}
|
|
304
321
|
|
|
322
|
+
logger.debug(`commitPlanningArtifacts: staged files: ${stagedStatus}`);
|
|
323
|
+
|
|
305
324
|
// Commit the staged files
|
|
306
325
|
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
307
326
|
encoding: 'utf-8',
|
|
308
327
|
stdio: 'pipe',
|
|
309
|
-
...
|
|
328
|
+
...execOpts,
|
|
310
329
|
});
|
|
311
330
|
|
|
312
331
|
logger.debug(`Committed planning artifacts: ${commitMessage}`);
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { Command } from 'commander';
|
|
|
4
4
|
import { createPlanCommand } from './commands/plan.js';
|
|
5
5
|
import { createDoCommand } from './commands/do.js';
|
|
6
6
|
import { createStatusCommand } from './commands/status.js';
|
|
7
|
-
import { createMigrateCommand } from './commands/migrate.js';
|
|
8
7
|
import { createConfigCommand } from './commands/config.js';
|
|
9
8
|
import { getVersion } from './utils/version.js';
|
|
10
9
|
|
|
@@ -18,7 +17,6 @@ program
|
|
|
18
17
|
program.addCommand(createPlanCommand());
|
|
19
18
|
program.addCommand(createDoCommand());
|
|
20
19
|
program.addCommand(createStatusCommand());
|
|
21
|
-
program.addCommand(createMigrateCommand());
|
|
22
20
|
program.addCommand(createConfigCommand());
|
|
23
21
|
|
|
24
22
|
program.parse();
|
package/src/prompts/amend.ts
CHANGED
|
@@ -200,7 +200,9 @@ This is rarely needed — prefer using the \`effort\` label so the user's config
|
|
|
200
200
|
|
|
201
201
|
After creating all new plan files:
|
|
202
202
|
1. Provide a summary of:
|
|
203
|
-
- The new tasks you've created
|
|
203
|
+
- The new tasks you've created, including the effort level for each task. Example format:
|
|
204
|
+
- Task 02: add-caching (effort: medium)
|
|
205
|
+
- Task 03: update-docs (effort: low)
|
|
204
206
|
- How they relate to existing tasks
|
|
205
207
|
- Total task count in the project
|
|
206
208
|
2. Display this exit message to the user:
|
|
@@ -49,8 +49,8 @@ Maps task complexity labels (in plan frontmatter) to Claude models. When a plan
|
|
|
49
49
|
|
|
50
50
|
| Key | Default | Description |
|
|
51
51
|
|-----|---------|-------------|
|
|
52
|
-
| `effortMapping.low` | `"
|
|
53
|
-
| `effortMapping.medium` | `"
|
|
52
|
+
| `effortMapping.low` | `"sonnet"` | Model for low-complexity tasks |
|
|
53
|
+
| `effortMapping.medium` | `"opus"` | Model for medium-complexity tasks |
|
|
54
54
|
| `effortMapping.high` | `"opus"` | Model for high-complexity tasks |
|
|
55
55
|
|
|
56
56
|
Values must be a short alias (`"sonnet"`, `"haiku"`, `"opus"`) or a full model ID.
|
|
@@ -61,11 +61,11 @@ Example:
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
63
|
"models": { "execute": "sonnet" },
|
|
64
|
-
"effortMapping": { "low": "
|
|
64
|
+
"effortMapping": { "low": "sonnet", "medium": "opus", "high": "opus" }
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
|
-
- Task with `effort: low` →
|
|
68
|
-
- Task with `effort: medium` → sonnet (
|
|
67
|
+
- Task with `effort: low` → sonnet (at ceiling)
|
|
68
|
+
- Task with `effort: medium` → sonnet (capped to ceiling, not opus)
|
|
69
69
|
- Task with `effort: high` → sonnet (capped to ceiling, not opus)
|
|
70
70
|
|
|
71
71
|
### `timeout` — Task Timeout
|
|
@@ -217,8 +217,8 @@ Uses Sonnet for planning and caps task execution at Sonnet (tasks with `effort:
|
|
|
217
217
|
"config": "sonnet"
|
|
218
218
|
},
|
|
219
219
|
"effortMapping": {
|
|
220
|
-
"low": "
|
|
221
|
-
"medium": "
|
|
220
|
+
"low": "sonnet",
|
|
221
|
+
"medium": "opus",
|
|
222
222
|
"high": "opus"
|
|
223
223
|
},
|
|
224
224
|
"timeout": 60,
|
package/src/prompts/planning.ts
CHANGED
|
@@ -168,7 +168,10 @@ or for multiple dependencies:
|
|
|
168
168
|
### Step 5: Confirm Completion
|
|
169
169
|
|
|
170
170
|
After creating all plan files:
|
|
171
|
-
1. Provide a summary of the tasks you've created
|
|
171
|
+
1. Provide a summary of the tasks you've created, including the effort level for each task. Example:
|
|
172
|
+
- Task 01: setup-database (effort: low)
|
|
173
|
+
- Task 02: implement-auth (effort: medium)
|
|
174
|
+
- Task 03: refactor-api (effort: high)
|
|
172
175
|
2. Display this exit message to the user:
|
|
173
176
|
|
|
174
177
|
\`\`\`
|
package/src/types/config.ts
CHANGED
|
@@ -69,8 +69,8 @@ export const DEFAULT_CONFIG: RafConfig = {
|
|
|
69
69
|
config: 'sonnet',
|
|
70
70
|
},
|
|
71
71
|
effortMapping: {
|
|
72
|
-
low: '
|
|
73
|
-
medium: '
|
|
72
|
+
low: 'sonnet',
|
|
73
|
+
medium: 'opus',
|
|
74
74
|
high: 'opus',
|
|
75
75
|
},
|
|
76
76
|
timeout: 60,
|
|
@@ -138,11 +138,6 @@ export interface StatusCommandOptions {
|
|
|
138
138
|
json?: boolean;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export interface MigrateCommandOptions {
|
|
142
|
-
dryRun?: boolean;
|
|
143
|
-
worktree?: boolean;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
141
|
/** Per-model token usage breakdown from stream-json result event. */
|
|
147
142
|
export interface ModelTokenUsage {
|
|
148
143
|
inputTokens: number;
|
|
@@ -179,6 +179,50 @@ describe('commitPlanningArtifacts - worktree integration', () => {
|
|
|
179
179
|
expect(committedFiles).not.toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
+
it('should commit amend artifacts with plan files as additionalFiles in worktree', async () => {
|
|
183
|
+
const projectFolder = 'aatest-my-project';
|
|
184
|
+
|
|
185
|
+
// Create initial project and commit
|
|
186
|
+
createInitialProject(repoDir, projectFolder);
|
|
187
|
+
execSync('git add -A', { cwd: repoDir, stdio: 'pipe' });
|
|
188
|
+
execSync('git commit -m "Initial plan"', { cwd: repoDir, stdio: 'pipe' });
|
|
189
|
+
|
|
190
|
+
// Create worktree
|
|
191
|
+
worktreePath = createWorktreeForProject(repoDir, projectFolder);
|
|
192
|
+
const wtProjectPath = path.join(worktreePath, 'RAF', projectFolder);
|
|
193
|
+
|
|
194
|
+
// Simulate amend: update files and create new plan
|
|
195
|
+
fs.writeFileSync(
|
|
196
|
+
path.join(wtProjectPath, 'input.md'),
|
|
197
|
+
'original input\n\n---\n\nnew task description'
|
|
198
|
+
);
|
|
199
|
+
fs.writeFileSync(
|
|
200
|
+
path.join(wtProjectPath, 'decisions.md'),
|
|
201
|
+
'# Decisions\n\n## Q1?\nA1\n\n## Q2?\nA2'
|
|
202
|
+
);
|
|
203
|
+
fs.writeFileSync(
|
|
204
|
+
path.join(wtProjectPath, 'plans', '02-new-task.md'),
|
|
205
|
+
'# Task: New Task'
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Call commitPlanningArtifacts with plan files as additionalFiles (as plan.ts now does)
|
|
209
|
+
await commitPlanningArtifacts(wtProjectPath, {
|
|
210
|
+
cwd: worktreePath,
|
|
211
|
+
isAmend: true,
|
|
212
|
+
additionalFiles: [path.join(wtProjectPath, 'plans', '02-new-task.md')],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Verify commit was made with Amend prefix
|
|
216
|
+
const lastMsg = getLastCommitMessage(worktreePath);
|
|
217
|
+
expect(lastMsg).toMatch(/RAF\[aatest\] Amend: my-project/);
|
|
218
|
+
|
|
219
|
+
// Verify all files are in the commit (input, decisions, AND plan files)
|
|
220
|
+
const committedFiles = getLastCommitFiles(worktreePath);
|
|
221
|
+
expect(committedFiles).toContain(`RAF/${projectFolder}/input.md`);
|
|
222
|
+
expect(committedFiles).toContain(`RAF/${projectFolder}/decisions.md`);
|
|
223
|
+
expect(committedFiles).toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
224
|
+
});
|
|
225
|
+
|
|
182
226
|
it('should commit after worktree recreation from branch', async () => {
|
|
183
227
|
const projectFolder = 'aatest-my-project';
|
|
184
228
|
|
|
@@ -247,6 +291,75 @@ describe('commitPlanningArtifacts - worktree integration', () => {
|
|
|
247
291
|
expect(committedFiles).not.toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
248
292
|
});
|
|
249
293
|
|
|
294
|
+
it('should commit all artifacts after worktree recreation with additionalFiles', async () => {
|
|
295
|
+
const projectFolder = 'aatest-my-project';
|
|
296
|
+
|
|
297
|
+
// Create initial project, commit, and create initial worktree
|
|
298
|
+
createInitialProject(repoDir, projectFolder);
|
|
299
|
+
execSync('git add -A', { cwd: repoDir, stdio: 'pipe' });
|
|
300
|
+
execSync('git commit -m "Initial plan"', { cwd: repoDir, stdio: 'pipe' });
|
|
301
|
+
|
|
302
|
+
// Create worktree
|
|
303
|
+
const initialWtPath = createWorktreeForProject(repoDir, projectFolder);
|
|
304
|
+
const initialWtProjectPath = path.join(initialWtPath, 'RAF', projectFolder);
|
|
305
|
+
|
|
306
|
+
// Commit something on the worktree branch (simulating initial plan commit + task execution)
|
|
307
|
+
fs.writeFileSync(
|
|
308
|
+
path.join(initialWtProjectPath, 'decisions.md'),
|
|
309
|
+
'# Decisions\n\n## Q1?\nA1\n\n## Q2?\nA2'
|
|
310
|
+
);
|
|
311
|
+
execSync('git add -A', { cwd: initialWtPath, stdio: 'pipe' });
|
|
312
|
+
execSync('git commit -m "Plan commit on branch"', { cwd: initialWtPath, stdio: 'pipe' });
|
|
313
|
+
|
|
314
|
+
// Remove the worktree (simulating cleanup after execution)
|
|
315
|
+
execSync(`git worktree remove "${initialWtPath}" --force`, {
|
|
316
|
+
cwd: repoDir,
|
|
317
|
+
stdio: 'pipe',
|
|
318
|
+
});
|
|
319
|
+
worktreePaths.splice(worktreePaths.indexOf(initialWtPath), 1);
|
|
320
|
+
|
|
321
|
+
// Recreate worktree from existing branch (simulating amend --worktree)
|
|
322
|
+
const recreatedWtPath = path.join(makeTempDir('raf-wt-recreated-'), projectFolder);
|
|
323
|
+
execSync(`git worktree add "${recreatedWtPath}" "${projectFolder}"`, {
|
|
324
|
+
cwd: repoDir,
|
|
325
|
+
stdio: 'pipe',
|
|
326
|
+
});
|
|
327
|
+
worktreePaths.push(recreatedWtPath);
|
|
328
|
+
|
|
329
|
+
const recreatedProjectPath = path.join(recreatedWtPath, 'RAF', projectFolder);
|
|
330
|
+
|
|
331
|
+
// Simulate amend: update input.md, update decisions.md, create new plan
|
|
332
|
+
fs.writeFileSync(
|
|
333
|
+
path.join(recreatedProjectPath, 'input.md'),
|
|
334
|
+
'original input\n\n---\n\namend task description'
|
|
335
|
+
);
|
|
336
|
+
fs.writeFileSync(
|
|
337
|
+
path.join(recreatedProjectPath, 'decisions.md'),
|
|
338
|
+
'# Decisions\n\n## Q1?\nA1\n\n## Q2?\nA2\n\n## Q3?\nA3'
|
|
339
|
+
);
|
|
340
|
+
fs.writeFileSync(
|
|
341
|
+
path.join(recreatedProjectPath, 'plans', '02-new-task.md'),
|
|
342
|
+
'# Task: New Task'
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Call commitPlanningArtifacts with plan files as additionalFiles (as plan.ts now does)
|
|
346
|
+
await commitPlanningArtifacts(recreatedProjectPath, {
|
|
347
|
+
cwd: recreatedWtPath,
|
|
348
|
+
isAmend: true,
|
|
349
|
+
additionalFiles: [path.join(recreatedProjectPath, 'plans', '02-new-task.md')],
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Verify commit was made
|
|
353
|
+
const lastMsg = getLastCommitMessage(recreatedWtPath);
|
|
354
|
+
expect(lastMsg).toMatch(/RAF\[aatest\] Amend: my-project/);
|
|
355
|
+
|
|
356
|
+
// Verify all files are in the commit (including plan files)
|
|
357
|
+
const committedFiles = getLastCommitFiles(recreatedWtPath);
|
|
358
|
+
expect(committedFiles).toContain(`RAF/${projectFolder}/input.md`);
|
|
359
|
+
expect(committedFiles).toContain(`RAF/${projectFolder}/decisions.md`);
|
|
360
|
+
expect(committedFiles).toContain(`RAF/${projectFolder}/plans/02-new-task.md`);
|
|
361
|
+
});
|
|
362
|
+
|
|
250
363
|
it('should work when only some files have changed', async () => {
|
|
251
364
|
const projectFolder = 'aatest-my-project';
|
|
252
365
|
|
|
@@ -130,7 +130,7 @@ describe('commitPlanningArtifacts', () => {
|
|
|
130
130
|
|
|
131
131
|
// Should log debug message and not throw
|
|
132
132
|
expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
133
|
-
'No changes to planning artifacts to commit'
|
|
133
|
+
'No changes to planning artifacts to commit (git add succeeded but nothing changed in index)'
|
|
134
134
|
);
|
|
135
135
|
expect(mockLogger.warn).not.toHaveBeenCalled();
|
|
136
136
|
});
|
|
@@ -77,7 +77,7 @@ describe('Config Command', () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('should accept valid config with effortMapping override', () => {
|
|
80
|
-
const config = { effortMapping: { low: '
|
|
80
|
+
const config = { effortMapping: { low: 'sonnet', medium: 'opus' } };
|
|
81
81
|
expect(() => validateConfig(config)).not.toThrow();
|
|
82
82
|
});
|
|
83
83
|
|
|
@@ -204,7 +204,7 @@ describe('Config Command', () => {
|
|
|
204
204
|
// These are the values that runConfigSession uses when config loading fails
|
|
205
205
|
expect(DEFAULT_CONFIG.models.config).toBe('sonnet');
|
|
206
206
|
// effortMapping defaults used for per-task model resolution
|
|
207
|
-
expect(DEFAULT_CONFIG.effortMapping.medium).toBe('
|
|
207
|
+
expect(DEFAULT_CONFIG.effortMapping.medium).toBe('opus');
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
it('should be able to read raw file contents even when config is invalid JSON', () => {
|
|
@@ -91,7 +91,7 @@ describe('Config', () => {
|
|
|
91
91
|
it('should accept a full valid config', () => {
|
|
92
92
|
const config = {
|
|
93
93
|
models: { plan: 'opus', execute: 'haiku' },
|
|
94
|
-
effortMapping: { low: '
|
|
94
|
+
effortMapping: { low: 'sonnet', medium: 'opus', high: 'opus' },
|
|
95
95
|
timeout: 30,
|
|
96
96
|
maxRetries: 5,
|
|
97
97
|
autoCommit: false,
|
|
@@ -274,7 +274,7 @@ describe('Config', () => {
|
|
|
274
274
|
|
|
275
275
|
const config = resolveConfig(configPath);
|
|
276
276
|
expect(config.effortMapping.medium).toBe('opus');
|
|
277
|
-
expect(config.effortMapping.low).toBe('
|
|
277
|
+
expect(config.effortMapping.low).toBe('sonnet'); // default preserved
|
|
278
278
|
expect(config.effortMapping.high).toBe('opus'); // default preserved
|
|
279
279
|
});
|
|
280
280
|
|
|
@@ -368,7 +368,7 @@ describe('Config', () => {
|
|
|
368
368
|
fs.writeFileSync(configPath, JSON.stringify({ effortMapping: { high: 'sonnet' } }));
|
|
369
369
|
const config = resolveConfig(configPath);
|
|
370
370
|
expect(config.effortMapping.high).toBe('sonnet');
|
|
371
|
-
expect(config.effortMapping.low).toBe('
|
|
371
|
+
expect(config.effortMapping.low).toBe('sonnet'); // default preserved
|
|
372
372
|
});
|
|
373
373
|
|
|
374
374
|
it('getCommitFormat returns correct format', () => {
|
|
@@ -401,8 +401,8 @@ describe('Config', () => {
|
|
|
401
401
|
});
|
|
402
402
|
|
|
403
403
|
it('should have all effortMapping levels defined', () => {
|
|
404
|
-
expect(DEFAULT_CONFIG.effortMapping.low).toBe('
|
|
405
|
-
expect(DEFAULT_CONFIG.effortMapping.medium).toBe('
|
|
404
|
+
expect(DEFAULT_CONFIG.effortMapping.low).toBe('sonnet');
|
|
405
|
+
expect(DEFAULT_CONFIG.effortMapping.medium).toBe('opus');
|
|
406
406
|
expect(DEFAULT_CONFIG.effortMapping.high).toBe('opus');
|
|
407
407
|
});
|
|
408
408
|
|
|
@@ -483,9 +483,9 @@ describe('Config', () => {
|
|
|
483
483
|
expect(DEFAULT_CONFIG.models.prGeneration).toBe('sonnet');
|
|
484
484
|
});
|
|
485
485
|
|
|
486
|
-
it('should default effortMapping to
|
|
487
|
-
expect(DEFAULT_CONFIG.effortMapping.low).toBe('
|
|
488
|
-
expect(DEFAULT_CONFIG.effortMapping.medium).toBe('
|
|
486
|
+
it('should default effortMapping to sonnet/opus/opus', () => {
|
|
487
|
+
expect(DEFAULT_CONFIG.effortMapping.low).toBe('sonnet');
|
|
488
|
+
expect(DEFAULT_CONFIG.effortMapping.medium).toBe('opus');
|
|
489
489
|
expect(DEFAULT_CONFIG.effortMapping.high).toBe('opus');
|
|
490
490
|
});
|
|
491
491
|
|
|
@@ -596,8 +596,8 @@ describe('Config', () => {
|
|
|
596
596
|
const config = resolveConfig(configPath);
|
|
597
597
|
expect(config.effortMapping.high).toBe('sonnet');
|
|
598
598
|
// Others should remain at defaults
|
|
599
|
-
expect(config.effortMapping.low).toBe('
|
|
600
|
-
expect(config.effortMapping.medium).toBe('
|
|
599
|
+
expect(config.effortMapping.low).toBe('sonnet');
|
|
600
|
+
expect(config.effortMapping.medium).toBe('opus');
|
|
601
601
|
});
|
|
602
602
|
|
|
603
603
|
it('should use custom commit format when configured', () => {
|
|
@@ -711,8 +711,8 @@ describe('Config', () => {
|
|
|
711
711
|
const configPath = path.join(tempDir, 'default.json');
|
|
712
712
|
// Use default config
|
|
713
713
|
const config = resolveConfig(path.join(tempDir, 'nonexistent.json'));
|
|
714
|
-
expect(config.effortMapping.low).toBe('
|
|
715
|
-
expect(config.effortMapping.medium).toBe('
|
|
714
|
+
expect(config.effortMapping.low).toBe('sonnet');
|
|
715
|
+
expect(config.effortMapping.medium).toBe('opus');
|
|
716
716
|
expect(config.effortMapping.high).toBe('opus');
|
|
717
717
|
});
|
|
718
718
|
});
|
|
@@ -721,8 +721,8 @@ describe('Config', () => {
|
|
|
721
721
|
it('should accept valid effortMapping config', () => {
|
|
722
722
|
expect(() => validateConfig({
|
|
723
723
|
effortMapping: {
|
|
724
|
-
low: '
|
|
725
|
-
medium: '
|
|
724
|
+
low: 'sonnet',
|
|
725
|
+
medium: 'opus',
|
|
726
726
|
high: 'opus',
|
|
727
727
|
},
|
|
728
728
|
})).not.toThrow();
|