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.
Files changed (60) hide show
  1. package/CLAUDE.md +1 -1
  2. package/RAF/ahwvrz-legacy-sunset/decisions.md +10 -0
  3. package/RAF/ahwvrz-legacy-sunset/input.md +10 -0
  4. package/RAF/ahwvrz-legacy-sunset/outcomes/01-remove-migrate-command.md +30 -0
  5. package/RAF/ahwvrz-legacy-sunset/outcomes/02-fix-resume-worktree-resolution.md +62 -0
  6. package/RAF/ahwvrz-legacy-sunset/plans/01-remove-migrate-command.md +65 -0
  7. package/RAF/ahwvrz-legacy-sunset/plans/02-fix-resume-worktree-resolution.md +72 -0
  8. package/RAF/ahwzmc-echo-forge/decisions.md +15 -0
  9. package/RAF/ahwzmc-echo-forge/input.md +4 -0
  10. package/RAF/ahwzmc-echo-forge/outcomes/01-change-low-effort-default-to-sonnet.md +57 -0
  11. package/RAF/ahwzmc-echo-forge/outcomes/02-add-no-worktree-flag.md +79 -0
  12. package/RAF/ahwzmc-echo-forge/outcomes/03-update-readme.md +75 -0
  13. package/RAF/ahwzmc-echo-forge/plans/01-change-low-effort-default-to-sonnet.md +57 -0
  14. package/RAF/ahwzmc-echo-forge/plans/02-add-no-worktree-flag.md +51 -0
  15. package/RAF/ahwzmc-echo-forge/plans/03-update-readme.md +48 -0
  16. package/RAF/aifqwf-fix-amend-commit-again/decisions.md +7 -0
  17. package/RAF/aifqwf-fix-amend-commit-again/input.md +2 -0
  18. package/RAF/aifqwf-fix-amend-commit-again/outcomes/01-update-effort-mapping-defaults.md +35 -0
  19. package/RAF/aifqwf-fix-amend-commit-again/outcomes/02-fix-amend-worktree-commit.md +50 -0
  20. package/RAF/aifqwf-fix-amend-commit-again/plans/01-update-effort-mapping-defaults.md +37 -0
  21. package/RAF/aifqwf-fix-amend-commit-again/plans/02-fix-amend-worktree-commit.md +55 -0
  22. package/README.md +26 -29
  23. package/dist/commands/do.d.ts.map +1 -1
  24. package/dist/commands/do.js +1 -0
  25. package/dist/commands/do.js.map +1 -1
  26. package/dist/commands/plan.d.ts.map +1 -1
  27. package/dist/commands/plan.js +51 -39
  28. package/dist/commands/plan.js.map +1 -1
  29. package/dist/core/git.d.ts.map +1 -1
  30. package/dist/core/git.js +20 -4
  31. package/dist/core/git.js.map +1 -1
  32. package/dist/index.js +0 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/prompts/amend.d.ts.map +1 -1
  35. package/dist/prompts/amend.js +3 -1
  36. package/dist/prompts/amend.js.map +1 -1
  37. package/dist/prompts/planning.d.ts.map +1 -1
  38. package/dist/prompts/planning.js +4 -1
  39. package/dist/prompts/planning.js.map +1 -1
  40. package/dist/types/config.d.ts +0 -4
  41. package/dist/types/config.d.ts.map +1 -1
  42. package/dist/types/config.js +2 -2
  43. package/dist/types/config.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/commands/do.ts +1 -0
  46. package/src/commands/plan.ts +54 -42
  47. package/src/core/git.ts +23 -4
  48. package/src/index.ts +0 -2
  49. package/src/prompts/amend.ts +3 -1
  50. package/src/prompts/config-docs.md +7 -7
  51. package/src/prompts/planning.ts +4 -1
  52. package/src/types/config.ts +2 -7
  53. package/tests/unit/commit-planning-artifacts-worktree.test.ts +113 -0
  54. package/tests/unit/commit-planning-artifacts.test.ts +1 -1
  55. package/tests/unit/config-command.test.ts +2 -2
  56. package/tests/unit/config.test.ts +14 -14
  57. package/tests/unit/plan-resume-worktree-resolution.test.ts +153 -0
  58. package/tests/unit/worktree-flag-override.test.ts +186 -0
  59. package/src/commands/migrate.ts +0 -269
  60. package/tests/unit/migrate-command.test.ts +0 -197
@@ -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 and decisions.md)
321
- await commitPlanningArtifacts(projectPath, worktreePath ? { cwd: worktreePath } : undefined);
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 only plan files committed during execution)
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
- // First, try to resolve the project from main repo
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
- const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
696
-
697
- if (!mainResolution.path) {
698
- if (mainResolution.error === 'ambiguous' && mainResolution.matches) {
699
- logger.error(`Ambiguous project name: ${identifier}`);
700
- logger.error('Multiple projects match:');
701
- for (const match of mainResolution.matches) {
702
- logger.error(` - ${match.folder}`);
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
- const projectPath = mainResolution.path;
712
- const folderName = path.basename(projectPath);
713
-
714
- // Determine if this is a worktree project by checking if a worktree exists
715
- let resumeCwd = projectPath; // Default to main repo project path
716
- const repoBasename = getRepoBasename();
717
-
718
- if (repoBasename) {
719
- const worktreeBaseDir = computeWorktreeBaseDir(repoBasename);
732
+ // 2. If not found in worktree (or invalid), try main repo
733
+ if (!projectPath) {
734
+ const mainResolution = resolveProjectIdentifierWithDetails(rafDir, identifier);
720
735
 
721
- // Check if a worktree exists for this project
722
- if (fs.existsSync(worktreeBaseDir)) {
723
- const entries = fs.readdirSync(worktreeBaseDir, { withFileTypes: true });
724
- const worktreeEntry = entries.find(
725
- (entry) => entry.isDirectory() && entry.name === folderName
726
- );
727
-
728
- if (worktreeEntry) {
729
- // Worktree exists - use it as the CWD
730
- const worktreePath = path.join(worktreeBaseDir, worktreeEntry.name);
731
- const wtValidation = validateWorktree(worktreePath, '');
732
- if (wtValidation.isValidWorktree) {
733
- resumeCwd = worktreePath;
734
- logger.info(`Resuming session in worktree: ${worktreePath}`);
735
- } else {
736
- logger.warn(`Worktree found but invalid: ${worktreePath}`);
737
- logger.warn('Falling back to main repo path.');
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
- ...(execCwd ? { cwd: execCwd } : {}),
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
- ...(execCwd ? { cwd: execCwd } : {}),
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
- ...(execCwd ? { cwd: execCwd } : {}),
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();
@@ -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` | `"haiku"` | Model for low-complexity tasks |
53
- | `effortMapping.medium` | `"sonnet"` | Model for medium-complexity tasks |
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": "haiku", "medium": "sonnet", "high": "opus" }
64
+ "effortMapping": { "low": "sonnet", "medium": "opus", "high": "opus" }
65
65
  }
66
66
  ```
67
- - Task with `effort: low` → haiku (under ceiling)
68
- - Task with `effort: medium` → sonnet (at ceiling)
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": "haiku",
221
- "medium": "sonnet",
220
+ "low": "sonnet",
221
+ "medium": "opus",
222
222
  "high": "opus"
223
223
  },
224
224
  "timeout": 60,
@@ -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
  \`\`\`
@@ -69,8 +69,8 @@ export const DEFAULT_CONFIG: RafConfig = {
69
69
  config: 'sonnet',
70
70
  },
71
71
  effortMapping: {
72
- low: 'haiku',
73
- medium: 'sonnet',
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: 'haiku', medium: 'sonnet' } };
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('sonnet');
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: 'haiku', medium: 'sonnet', high: 'opus' },
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('haiku'); // default preserved
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('haiku'); // default preserved
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('haiku');
405
- expect(DEFAULT_CONFIG.effortMapping.medium).toBe('sonnet');
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 haiku/sonnet/opus', () => {
487
- expect(DEFAULT_CONFIG.effortMapping.low).toBe('haiku');
488
- expect(DEFAULT_CONFIG.effortMapping.medium).toBe('sonnet');
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('haiku');
600
- expect(config.effortMapping.medium).toBe('sonnet');
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('haiku');
715
- expect(config.effortMapping.medium).toBe('sonnet');
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: 'haiku',
725
- medium: 'sonnet',
724
+ low: 'sonnet',
725
+ medium: 'opus',
726
726
  high: 'opus',
727
727
  },
728
728
  })).not.toThrow();