rafcode 3.0.0 → 3.8.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 (235) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CLAUDE.md +0 -1
  3. package/RAF/38-dual-wielder/decisions.md +9 -0
  4. package/RAF/38-dual-wielder/input.md +6 -1
  5. package/RAF/38-dual-wielder/outcomes/8-e2e-test-codex-provider.md +139 -0
  6. package/RAF/38-dual-wielder/plans/8-e2e-test-codex-provider.md +95 -0
  7. package/RAF/39-pathless-rover/decisions.md +16 -0
  8. package/RAF/39-pathless-rover/input.md +2 -0
  9. package/RAF/39-pathless-rover/outcomes/1-fix-codex-stream-renderer.md +21 -0
  10. package/RAF/39-pathless-rover/outcomes/2-wire-provider-flag.md +28 -0
  11. package/RAF/39-pathless-rover/outcomes/3-remove-worktree-flag-do.md +41 -0
  12. package/RAF/39-pathless-rover/outcomes/4-remove-worktree-flag-plan-amend.md +30 -0
  13. package/RAF/39-pathless-rover/outcomes/5-update-prompts-and-docs.md +26 -0
  14. package/RAF/39-pathless-rover/plans/1-fix-codex-stream-renderer.md +43 -0
  15. package/RAF/39-pathless-rover/plans/2-wire-provider-flag.md +48 -0
  16. package/RAF/39-pathless-rover/plans/3-remove-worktree-flag-do.md +41 -0
  17. package/RAF/39-pathless-rover/plans/4-remove-worktree-flag-plan-amend.md +43 -0
  18. package/RAF/39-pathless-rover/plans/5-update-prompts-and-docs.md +31 -0
  19. package/RAF/40-numeric-order-fix/decisions.md +7 -0
  20. package/RAF/40-numeric-order-fix/input.md +19 -0
  21. package/RAF/40-numeric-order-fix/outcomes/1-fix-numeric-sort-order.md +18 -0
  22. package/RAF/40-numeric-order-fix/outcomes/2-add-npm-keywords.md +10 -0
  23. package/RAF/40-numeric-order-fix/plans/1-fix-numeric-sort-order.md +48 -0
  24. package/RAF/40-numeric-order-fix/plans/2-add-npm-keywords.md +23 -0
  25. package/RAF/41-echo-chamber/decisions.md +13 -0
  26. package/RAF/41-echo-chamber/input.md +4 -0
  27. package/RAF/41-echo-chamber/outcomes/1-update-codex-model-defaults.md +24 -0
  28. package/RAF/41-echo-chamber/outcomes/2-e2e-test-codex-provider.md +74 -0
  29. package/RAF/41-echo-chamber/plans/1-update-codex-model-defaults.md +28 -0
  30. package/RAF/41-echo-chamber/plans/2-e2e-test-codex-provider.md +103 -0
  31. package/RAF/42-patch-parade/decisions.md +29 -0
  32. package/RAF/42-patch-parade/input.md +9 -0
  33. package/RAF/42-patch-parade/outcomes/1-fix-codex-model-resolution.md +36 -0
  34. package/RAF/42-patch-parade/outcomes/2-fix-provider-aware-name-generation.md +31 -0
  35. package/RAF/42-patch-parade/outcomes/3-fix-codex-error-event-rendering.md +32 -0
  36. package/RAF/42-patch-parade/outcomes/4-update-cli-help-docs.md +28 -0
  37. package/RAF/42-patch-parade/outcomes/5-update-default-codex-models-to-gpt-5-4.md +33 -0
  38. package/RAF/42-patch-parade/outcomes/6-unify-model-config-schema.md +89 -0
  39. package/RAF/42-patch-parade/plans/1-fix-codex-model-resolution.md +35 -0
  40. package/RAF/42-patch-parade/plans/2-fix-provider-aware-name-generation.md +38 -0
  41. package/RAF/42-patch-parade/plans/3-fix-codex-error-event-rendering.md +32 -0
  42. package/RAF/42-patch-parade/plans/4-update-cli-help-docs.md +31 -0
  43. package/RAF/42-patch-parade/plans/5-update-default-codex-models-to-gpt-5-4.md +35 -0
  44. package/RAF/42-patch-parade/plans/6-unify-model-config-schema.md +46 -0
  45. package/RAF/43-swiss-army/decisions.md +34 -0
  46. package/RAF/43-swiss-army/input.md +7 -0
  47. package/RAF/43-swiss-army/outcomes/1-fix-model-validation.md +21 -0
  48. package/RAF/43-swiss-army/outcomes/2-update-commit-format.md +31 -0
  49. package/RAF/43-swiss-army/outcomes/3-wire-reasoning-effort.md +28 -0
  50. package/RAF/43-swiss-army/outcomes/4-remove-provider-flag.md +27 -0
  51. package/RAF/43-swiss-army/outcomes/5-config-wizard-validation.md +23 -0
  52. package/RAF/43-swiss-army/outcomes/6-add-fast-mode.md +32 -0
  53. package/RAF/43-swiss-army/outcomes/7-config-preset.md +31 -0
  54. package/RAF/43-swiss-army/plans/1-fix-model-validation.md +38 -0
  55. package/RAF/43-swiss-army/plans/2-update-commit-format.md +46 -0
  56. package/RAF/43-swiss-army/plans/3-wire-reasoning-effort.md +39 -0
  57. package/RAF/43-swiss-army/plans/4-remove-provider-flag.md +43 -0
  58. package/RAF/43-swiss-army/plans/5-config-wizard-validation.md +42 -0
  59. package/RAF/43-swiss-army/plans/6-add-fast-mode.md +46 -0
  60. package/RAF/43-swiss-army/plans/7-config-preset.md +51 -0
  61. package/RAF/44-config-api-change/decisions.md +22 -0
  62. package/RAF/44-config-api-change/input.md +5 -0
  63. package/RAF/44-config-api-change/outcomes/1-restructure-config-subcommands.md +19 -0
  64. package/RAF/44-config-api-change/outcomes/2-move-preset-under-config.md +17 -0
  65. package/RAF/44-config-api-change/outcomes/3-update-existing-tests-for-config-api.md +14 -0
  66. package/RAF/44-config-api-change/outcomes/4-update-config-command-docs.md +11 -0
  67. package/RAF/44-config-api-change/outcomes/5-fix-codex-name-generation.md +18 -0
  68. package/RAF/44-config-api-change/plans/1-restructure-config-subcommands.md +37 -0
  69. package/RAF/44-config-api-change/plans/2-move-preset-under-config.md +38 -0
  70. package/RAF/44-config-api-change/plans/3-update-existing-tests-for-config-api.md +38 -0
  71. package/RAF/44-config-api-change/plans/4-update-config-command-docs.md +36 -0
  72. package/RAF/44-config-api-change/plans/5-fix-codex-name-generation.md +49 -0
  73. package/RAF/45-signal-cairn/decisions.md +7 -0
  74. package/RAF/45-signal-cairn/input.md +2 -0
  75. package/RAF/45-signal-cairn/outcomes/1-rename-provider-to-harness.md +19 -0
  76. package/RAF/45-signal-cairn/outcomes/2-normalize-model-display-names.md +18 -0
  77. package/RAF/45-signal-cairn/plans/1-rename-provider-to-harness.md +40 -0
  78. package/RAF/45-signal-cairn/plans/2-normalize-model-display-names.md +41 -0
  79. package/RAF/45-signal-lantern/decisions.md +10 -0
  80. package/RAF/45-signal-lantern/input.md +2 -0
  81. package/RAF/45-signal-lantern/outcomes/1-add-effort-and-fast-to-do-model-display.md +15 -0
  82. package/RAF/45-signal-lantern/outcomes/2-capture-codex-post-run-token-usage.md +15 -0
  83. package/RAF/45-signal-lantern/outcomes/3-show-codex-token-summaries-without-fake-cost.md +14 -0
  84. package/RAF/45-signal-lantern/plans/1-add-effort-and-fast-to-do-model-display.md +38 -0
  85. package/RAF/45-signal-lantern/plans/2-capture-codex-post-run-token-usage.md +37 -0
  86. package/RAF/45-signal-lantern/plans/3-show-codex-token-summaries-without-fake-cost.md +40 -0
  87. package/RAF/46-lantern-arc/decisions.md +19 -0
  88. package/RAF/46-lantern-arc/input.md +6 -0
  89. package/RAF/46-lantern-arc/outcomes/1-remove-spark-alias.md +16 -0
  90. package/RAF/46-lantern-arc/outcomes/2-clean-up-worktree-plan-command.md +30 -0
  91. package/RAF/46-lantern-arc/outcomes/3-fix-token-usage-accumulation.md +32 -0
  92. package/RAF/46-lantern-arc/outcomes/4-display-effort-in-compact-mode.md +22 -0
  93. package/RAF/46-lantern-arc/outcomes/5-codex-fast-mode-research.md +38 -0
  94. package/RAF/46-lantern-arc/outcomes/6-optimize-llm-prompts.md +39 -0
  95. package/RAF/46-lantern-arc/plans/1-remove-spark-alias.md +38 -0
  96. package/RAF/46-lantern-arc/plans/2-clean-up-worktree-plan-command.md +33 -0
  97. package/RAF/46-lantern-arc/plans/3-fix-token-usage-accumulation.md +33 -0
  98. package/RAF/46-lantern-arc/plans/4-display-effort-in-compact-mode.md +28 -0
  99. package/RAF/46-lantern-arc/plans/5-codex-fast-mode-research.md +34 -0
  100. package/RAF/46-lantern-arc/plans/6-optimize-llm-prompts.md +48 -0
  101. package/RAF/47-signal-trim/decisions.md +13 -0
  102. package/RAF/47-signal-trim/input.md +2 -0
  103. package/RAF/47-signal-trim/plans/1-remove-cache-from-status.md +73 -0
  104. package/README.md +50 -63
  105. package/dist/commands/config.d.ts.map +1 -1
  106. package/dist/commands/config.js +47 -49
  107. package/dist/commands/config.js.map +1 -1
  108. package/dist/commands/do.d.ts +2 -0
  109. package/dist/commands/do.d.ts.map +1 -1
  110. package/dist/commands/do.js +91 -230
  111. package/dist/commands/do.js.map +1 -1
  112. package/dist/commands/plan.d.ts.map +1 -1
  113. package/dist/commands/plan.js +54 -259
  114. package/dist/commands/plan.js.map +1 -1
  115. package/dist/commands/preset.d.ts +3 -0
  116. package/dist/commands/preset.d.ts.map +1 -0
  117. package/dist/commands/preset.js +158 -0
  118. package/dist/commands/preset.js.map +1 -0
  119. package/dist/core/claude-runner.d.ts +2 -0
  120. package/dist/core/claude-runner.d.ts.map +1 -1
  121. package/dist/core/claude-runner.js +36 -12
  122. package/dist/core/claude-runner.js.map +1 -1
  123. package/dist/core/codex-runner.d.ts +1 -0
  124. package/dist/core/codex-runner.d.ts.map +1 -1
  125. package/dist/core/codex-runner.js +26 -7
  126. package/dist/core/codex-runner.js.map +1 -1
  127. package/dist/core/failure-analyzer.js +2 -1
  128. package/dist/core/failure-analyzer.js.map +1 -1
  129. package/dist/core/git.d.ts +2 -2
  130. package/dist/core/git.d.ts.map +1 -1
  131. package/dist/core/git.js +53 -3
  132. package/dist/core/git.js.map +1 -1
  133. package/dist/core/project-manager.d.ts.map +1 -1
  134. package/dist/core/project-manager.js +2 -2
  135. package/dist/core/project-manager.js.map +1 -1
  136. package/dist/core/pull-request.js +5 -5
  137. package/dist/core/pull-request.js.map +1 -1
  138. package/dist/core/runner-factory.d.ts +4 -4
  139. package/dist/core/runner-factory.d.ts.map +1 -1
  140. package/dist/core/runner-factory.js +8 -8
  141. package/dist/core/runner-factory.js.map +1 -1
  142. package/dist/core/runner-interface.d.ts +1 -1
  143. package/dist/core/runner-types.d.ts +17 -4
  144. package/dist/core/runner-types.d.ts.map +1 -1
  145. package/dist/core/state-derivation.js +3 -3
  146. package/dist/core/state-derivation.js.map +1 -1
  147. package/dist/parsers/codex-stream-renderer.d.ts +28 -4
  148. package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
  149. package/dist/parsers/codex-stream-renderer.js +110 -0
  150. package/dist/parsers/codex-stream-renderer.js.map +1 -1
  151. package/dist/prompts/amend.d.ts +0 -1
  152. package/dist/prompts/amend.d.ts.map +1 -1
  153. package/dist/prompts/amend.js +31 -104
  154. package/dist/prompts/amend.js.map +1 -1
  155. package/dist/prompts/execution.d.ts.map +1 -1
  156. package/dist/prompts/execution.js +17 -34
  157. package/dist/prompts/execution.js.map +1 -1
  158. package/dist/prompts/planning.d.ts.map +1 -1
  159. package/dist/prompts/planning.js +23 -123
  160. package/dist/prompts/planning.js.map +1 -1
  161. package/dist/types/config.d.ts +33 -32
  162. package/dist/types/config.d.ts.map +1 -1
  163. package/dist/types/config.js +14 -28
  164. package/dist/types/config.js.map +1 -1
  165. package/dist/utils/config.d.ts +36 -16
  166. package/dist/utils/config.d.ts.map +1 -1
  167. package/dist/utils/config.js +209 -104
  168. package/dist/utils/config.js.map +1 -1
  169. package/dist/utils/name-generator.d.ts.map +1 -1
  170. package/dist/utils/name-generator.js +25 -12
  171. package/dist/utils/name-generator.js.map +1 -1
  172. package/dist/utils/paths.d.ts +5 -0
  173. package/dist/utils/paths.d.ts.map +1 -1
  174. package/dist/utils/paths.js +9 -0
  175. package/dist/utils/paths.js.map +1 -1
  176. package/dist/utils/terminal-symbols.d.ts +15 -2
  177. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  178. package/dist/utils/terminal-symbols.js +36 -4
  179. package/dist/utils/terminal-symbols.js.map +1 -1
  180. package/dist/utils/token-tracker.d.ts +6 -1
  181. package/dist/utils/token-tracker.d.ts.map +1 -1
  182. package/dist/utils/token-tracker.js +84 -51
  183. package/dist/utils/token-tracker.js.map +1 -1
  184. package/dist/utils/validation.d.ts +1 -2
  185. package/dist/utils/validation.d.ts.map +1 -1
  186. package/dist/utils/validation.js +4 -25
  187. package/dist/utils/validation.js.map +1 -1
  188. package/package.json +7 -2
  189. package/src/commands/config.ts +60 -63
  190. package/src/commands/do.ts +96 -262
  191. package/src/commands/plan.ts +55 -279
  192. package/src/commands/preset.ts +186 -0
  193. package/src/core/claude-runner.ts +45 -5
  194. package/src/core/codex-runner.ts +32 -7
  195. package/src/core/failure-analyzer.ts +2 -1
  196. package/src/core/git.ts +57 -3
  197. package/src/core/project-manager.ts +2 -1
  198. package/src/core/pull-request.ts +5 -5
  199. package/src/core/runner-factory.ts +9 -9
  200. package/src/core/runner-interface.ts +1 -1
  201. package/src/core/runner-types.ts +17 -4
  202. package/src/core/state-derivation.ts +3 -3
  203. package/src/parsers/codex-stream-renderer.ts +149 -4
  204. package/src/prompts/amend.ts +30 -105
  205. package/src/prompts/config-docs.md +206 -62
  206. package/src/prompts/execution.ts +17 -34
  207. package/src/prompts/planning.ts +23 -124
  208. package/src/types/config.ts +47 -59
  209. package/src/utils/config.ts +248 -115
  210. package/src/utils/name-generator.ts +29 -13
  211. package/src/utils/paths.ts +10 -0
  212. package/src/utils/terminal-symbols.ts +46 -6
  213. package/src/utils/token-tracker.ts +96 -57
  214. package/src/utils/validation.ts +5 -30
  215. package/tests/unit/amend-prompt.test.ts +3 -2
  216. package/tests/unit/claude-runner-interactive.test.ts +21 -3
  217. package/tests/unit/claude-runner.test.ts +39 -0
  218. package/tests/unit/codex-runner.test.ts +163 -0
  219. package/tests/unit/codex-stream-renderer.test.ts +127 -0
  220. package/tests/unit/command-output.test.ts +57 -0
  221. package/tests/unit/commit-planning-artifacts-worktree.test.ts +24 -7
  222. package/tests/unit/commit-planning-artifacts.test.ts +26 -4
  223. package/tests/unit/config-command.test.ts +215 -303
  224. package/tests/unit/config.test.ts +319 -235
  225. package/tests/unit/dependency-integration.test.ts +27 -1
  226. package/tests/unit/do-model-display.test.ts +35 -0
  227. package/tests/unit/execution-prompt.test.ts +49 -19
  228. package/tests/unit/name-generator.test.ts +82 -12
  229. package/tests/unit/plan-command-auto-flag.test.ts +7 -10
  230. package/tests/unit/plan-command.test.ts +14 -17
  231. package/tests/unit/planning-prompt.test.ts +9 -8
  232. package/tests/unit/terminal-symbols.test.ts +94 -3
  233. package/tests/unit/token-tracker.test.ts +180 -1
  234. package/tests/unit/validation.test.ts +9 -41
  235. package/tests/unit/worktree-flag-override.test.ts +0 -186
@@ -8,18 +8,19 @@ import { shutdownHandler } from '../core/shutdown-handler.js';
8
8
  import { stashChanges, hasUncommittedChanges, isGitRepo, getHeadCommitHash } from '../core/git.js';
9
9
  import { getExecutionPrompt } from '../prompts/execution.js';
10
10
  import { parseOutput, isRetryableFailure } from '../parsers/output-parser.js';
11
- import { validatePlansExist, resolveModelOption } from '../utils/validation.js';
11
+ import { validatePlansExist } from '../utils/validation.js';
12
12
  import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFromPlanFile, resolveProjectIdentifierWithDetails, getOutcomeFilePath } from '../utils/paths.js';
13
- import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects, formatProjectChoice } from '../ui/project-picker.js';
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, getWorktreeDefault, getModel, getModelShortName, resolveFullModelId, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens } from '../utils/config.js';
16
+ import { formatModelDisplay, getConfig, getModel, getSyncMainBranch, resolveEffortToModel, applyModelCeiling, getShowCacheTokens, parseModelSpec, resolveFullModelId } 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';
20
20
  import { createStatusLine } from '../utils/status-line.js';
21
21
  import {
22
22
  formatProjectHeader,
23
+ formatModelMetadata,
23
24
  formatSummary,
24
25
  formatTaskProgress,
25
26
  formatTaskTokenSummary,
@@ -29,7 +30,6 @@ import { TokenTracker } from '../utils/token-tracker.js';
29
30
  import { VerboseToggle } from '../utils/verbose-toggle.js';
30
31
  import {
31
32
  deriveProjectState,
32
- discoverProjects,
33
33
  getNextExecutableTask,
34
34
  getDerivedStats,
35
35
  getDerivedStatsForTasks,
@@ -44,10 +44,6 @@ import {
44
44
  getRepoRoot,
45
45
  getRepoBasename,
46
46
  getCurrentBranch,
47
- computeWorktreePath,
48
- computeWorktreeBaseDir,
49
- validateWorktree,
50
- listWorktreeProjects,
51
47
  mergeWorktreeBranch,
52
48
  removeWorktree,
53
49
  resolveWorktreeProjectByIdentifier,
@@ -57,7 +53,7 @@ import {
57
53
  rebaseOntoMain,
58
54
  } from '../core/worktree.js';
59
55
  import { createPullRequest, prPreflight } from '../core/pull-request.js';
60
- import type { DoCommandOptions } from '../types/config.js';
56
+ import type { DoCommandOptions, ModelEntry } from '../types/config.js';
61
57
 
62
58
  /**
63
59
  * Post-execution action chosen by the user before task execution begins.
@@ -71,8 +67,8 @@ export type PostExecutionAction = 'merge' | 'pr' | 'leave';
71
67
  * Result of resolving a task's model from frontmatter.
72
68
  */
73
69
  interface TaskModelResolution {
74
- /** The resolved model (after ceiling is applied). */
75
- model: string;
70
+ /** The resolved model entry (after ceiling is applied). */
71
+ entry: ModelEntry;
76
72
  /** Whether a warning should be logged about missing frontmatter. */
77
73
  missingFrontmatter: boolean;
78
74
  /** Frontmatter parsing warnings to log. */
@@ -89,26 +85,26 @@ interface TaskModelResolution {
89
85
  *
90
86
  * @param frontmatter - Parsed frontmatter from the plan file
91
87
  * @param frontmatterWarnings - Warnings from frontmatter parsing
92
- * @param ceilingModel - The ceiling model (usually models.execute from config)
88
+ * @param ceilingEntry - The ceiling model entry (usually models.execute from config)
93
89
  * @param isRetry - Whether this is a retry attempt (escalates to ceiling)
94
90
  */
95
91
  function resolveTaskModel(
96
92
  frontmatter: PlanFrontmatter | undefined,
97
93
  frontmatterWarnings: string[] | undefined,
98
- ceilingModel: string,
94
+ ceilingEntry: ModelEntry,
99
95
  isRetry: boolean,
100
96
  ): TaskModelResolution {
101
97
  const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
102
98
 
103
99
  // Retry escalation: always use the ceiling model on retry
104
100
  if (isRetry) {
105
- return { model: ceilingModel, missingFrontmatter: false, warnings };
101
+ return { entry: ceilingEntry, missingFrontmatter: false, warnings };
106
102
  }
107
103
 
108
104
  // No frontmatter - fallback to ceiling with warning
109
105
  if (!frontmatter) {
110
106
  return {
111
- model: ceilingModel,
107
+ entry: ceilingEntry,
112
108
  missingFrontmatter: true,
113
109
  warnings,
114
110
  };
@@ -116,20 +112,25 @@ function resolveTaskModel(
116
112
 
117
113
  // Explicit model in frontmatter - apply ceiling
118
114
  if (frontmatter.model) {
119
- const model = applyModelCeiling(frontmatter.model, ceilingModel);
120
- return { model, missingFrontmatter: false, warnings };
115
+ const parsed = parseModelSpec(frontmatter.model);
116
+ const fmEntry: ModelEntry = {
117
+ model: parsed.model,
118
+ harness: parsed.harness,
119
+ };
120
+ const result = applyModelCeiling(fmEntry, ceilingEntry);
121
+ return { entry: result, missingFrontmatter: false, warnings };
121
122
  }
122
123
 
123
124
  // Effort-based resolution - apply ceiling
124
125
  if (frontmatter.effort) {
125
- const mappedModel = resolveEffortToModel(frontmatter.effort);
126
- const model = applyModelCeiling(mappedModel, ceilingModel);
127
- return { model, missingFrontmatter: false, warnings };
126
+ const mappedEntry = resolveEffortToModel(frontmatter.effort);
127
+ const result = applyModelCeiling(mappedEntry, ceilingEntry);
128
+ return { entry: result, missingFrontmatter: false, warnings };
128
129
  }
129
130
 
130
131
  // Frontmatter present but no effort or model - fallback to ceiling with warning
131
132
  return {
132
- model: ceilingModel,
133
+ entry: ceilingEntry,
133
134
  missingFrontmatter: true,
134
135
  warnings,
135
136
  };
@@ -190,6 +191,13 @@ interface ProjectExecutionResult {
190
191
  retryHistory?: TaskRetryHistory[];
191
192
  }
192
193
 
194
+ export function formatResolvedTaskModel(entry: ModelEntry): string {
195
+ return formatModelMetadata(resolveFullModelId(entry.model), {
196
+ effort: entry.reasoningEffort,
197
+ fast: entry.fast === true,
198
+ });
199
+ }
200
+
193
201
  export function createDoCommand(): Command {
194
202
  const command = new Command('do')
195
203
  .description('Execute planned tasks for a project')
@@ -199,11 +207,6 @@ export function createDoCommand(): Command {
199
207
  .option('-v, --verbose', 'Show full LLM output')
200
208
  .option('-d, --debug', 'Save all logs and show debug output')
201
209
  .option('-f, --force', 'Re-run all tasks regardless of status')
202
- .option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
203
- .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
- .option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
207
210
  .action(async (project: string | undefined, options: DoCommandOptions) => {
208
211
  await runDoCommand(project, options);
209
212
  });
@@ -214,59 +217,13 @@ export function createDoCommand(): Command {
214
217
  async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
215
218
  const rafDir = getRafDir();
216
219
  let projectIdentifier = projectIdentifierArg;
217
- let worktreeMode = options.worktree ?? getWorktreeDefault();
218
-
219
- // Validate and resolve model option
220
- let model: string;
221
- try {
222
- model = resolveModelOption(options.model as string | undefined, options.sonnet, 'execute');
223
- } catch (error) {
224
- logger.error((error as Error).message);
225
- process.exit(1);
226
- }
220
+ const executeEntry = getModel('execute');
227
221
 
228
- // Variables for worktree context (set when --worktree is used)
222
+ // Variables for worktree context (derived from where the project is found)
229
223
  let worktreeRoot: string | undefined;
230
224
  let originalBranch: string | undefined;
231
225
  let mainBranchName: string | null = null;
232
226
 
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
227
  // Handle no project identifier (non-worktree mode) - show interactive picker
271
228
  if (!projectIdentifier) {
272
229
  // Discover worktree projects for the current repo (if in a git repo)
@@ -298,9 +255,8 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
298
255
  // Use the selected project
299
256
  projectIdentifier = selectedProject.folder;
300
257
 
301
- // If a worktree project was selected, auto-switch to worktree mode
258
+ // If a worktree project was selected, record worktree context
302
259
  if (selectedProject.source === 'worktree' && selectedProject.worktreeRoot) {
303
- worktreeMode = true;
304
260
  worktreeRoot = selectedProject.worktreeRoot;
305
261
  originalBranch = getCurrentBranch() ?? undefined;
306
262
  }
@@ -316,89 +272,20 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
316
272
  // Resolve project identifier
317
273
  let resolvedProject: { identifier: string; path: string; name: string } | undefined;
318
274
 
319
- if (worktreeMode) {
320
- // Worktree mode: resolve project inside the worktree
275
+ if (worktreeRoot) {
276
+ // Worktree was set by the picker — resolve project inside the worktree
321
277
  const repoRoot = getRepoRoot()!;
322
- const repoBasename = getRepoBasename()!;
323
278
  const rafRelativePath = path.relative(repoRoot, rafDir);
324
-
325
- // If worktreeRoot was set by auto-discovery, use it directly
326
- if (worktreeRoot) {
327
- const wtRafDir = path.join(worktreeRoot, rafRelativePath);
328
- const result = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
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 };
279
+ const wtRafDir = path.join(worktreeRoot, rafRelativePath);
280
+ const result = resolveProjectIdentifierWithDetails(wtRafDir, projectIdentifier);
281
+ if (!result.path) {
282
+ logger.error(`Project not found in worktree: ${projectIdentifier}`);
283
+ process.exit(1);
399
284
  }
285
+ const projectName = extractProjectName(result.path) ?? projectIdentifier;
286
+ resolvedProject = { identifier: projectIdentifier, path: result.path, name: projectName };
400
287
  } else {
401
- // Standard mode: check worktrees first (worktree takes priority), then main repo
288
+ // Auto-detect: check worktrees first (worktree takes priority), then main repo
402
289
  const repoRoot = getRepoRoot();
403
290
  const repoBasename = repoRoot ? getRepoBasename() : null;
404
291
 
@@ -411,8 +298,6 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
411
298
  const wtProjectPath = path.join(wtRafDir, wtResolution.folder);
412
299
 
413
300
  if (fs.existsSync(wtProjectPath)) {
414
- // Auto-switch to worktree mode
415
- worktreeMode = true;
416
301
  worktreeRoot = wtResolution.worktreeRoot;
417
302
  originalBranch = getCurrentBranch() ?? undefined;
418
303
 
@@ -456,9 +341,26 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
456
341
  // Configure logger
457
342
  logger.configure({ verbose, debug });
458
343
 
459
- // Show post-execution picker before task execution (worktree mode only)
344
+ // Worktree setup: sync main branch and show post-execution picker
460
345
  let postAction: PostExecutionAction = 'leave';
461
- if (worktreeMode && worktreeRoot) {
346
+ if (worktreeRoot) {
347
+ if (!originalBranch) {
348
+ originalBranch = getCurrentBranch() ?? undefined;
349
+ }
350
+
351
+ // Sync main branch before worktree operations (if enabled)
352
+ if (getSyncMainBranch()) {
353
+ const syncResult = pullMainBranch();
354
+ mainBranchName = syncResult.mainBranch;
355
+ if (syncResult.success) {
356
+ if (syncResult.hadChanges) {
357
+ logger.info(`Synced ${syncResult.mainBranch} from remote`);
358
+ }
359
+ } else {
360
+ logger.warn(`Could not sync main branch: ${syncResult.error}`);
361
+ }
362
+ }
363
+
462
364
  try {
463
365
  postAction = await pickPostExecutionAction(worktreeRoot);
464
366
  } catch (error) {
@@ -498,7 +400,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
498
400
  force,
499
401
  maxRetries,
500
402
  autoCommit,
501
- model,
403
+ executeEntry,
502
404
  worktreeCwd: worktreeRoot,
503
405
  }
504
406
  );
@@ -509,7 +411,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
509
411
  }
510
412
 
511
413
  // Execute post-execution action based on picker choice
512
- if (worktreeMode && worktreeRoot) {
414
+ if (worktreeRoot) {
513
415
  const worktreeBranch = path.basename(worktreeRoot);
514
416
 
515
417
  if (result.success) {
@@ -650,88 +552,6 @@ async function executePostAction(
650
552
  }
651
553
  }
652
554
 
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
555
  interface SingleProjectOptions {
736
556
  timeout: number;
737
557
  verbose: boolean;
@@ -739,7 +559,8 @@ interface SingleProjectOptions {
739
559
  force: boolean;
740
560
  maxRetries: number;
741
561
  autoCommit: boolean;
742
- model: string;
562
+ /** The resolved execute model entry (acts as ceiling for per-task resolution). */
563
+ executeEntry: ModelEntry;
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, executeEntry, worktreeCwd } = options;
753
574
 
754
575
  if (!validatePlansExist(projectPath)) {
755
576
  return {
@@ -793,8 +614,8 @@ async function executeSingleProject(
793
614
  const projectManager = new ProjectManager();
794
615
  shutdownHandler.init();
795
616
 
796
- // The ceiling model for all tasks (can be overridden per-task, subject to this ceiling)
797
- const ceilingModel = model;
617
+ // The ceiling model entry for all tasks (can be overridden per-task, subject to this ceiling)
618
+ const ceilingEntry = executeEntry;
798
619
 
799
620
  // Initialize token tracker for usage reporting
800
621
  const tokenTracker = new TokenTracker();
@@ -807,8 +628,8 @@ async function executeSingleProject(
807
628
  const projectStartTime = Date.now();
808
629
 
809
630
  // Resolve and display version + ceiling model info (before any tasks run)
810
- const fullCeilingModelId = resolveFullModelId(ceilingModel);
811
- logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId}`);
631
+ const fullCeilingModelId = resolveFullModelId(ceilingEntry.model);
632
+ logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId} (${ceilingEntry.harness})`);
812
633
 
813
634
  if (verbose) {
814
635
  logger.info(`Executing project: ${projectName}`);
@@ -990,6 +811,8 @@ async function executeSingleProject(
990
811
  const failureHistory: Array<{ attempt: number; reason: string }> = [];
991
812
  // Track current model for display in status line (updated in retry loop)
992
813
  let currentModel: string | undefined;
814
+ let currentEffort: string | undefined;
815
+ let currentModelFast = false;
993
816
 
994
817
  // Set up timer for elapsed time tracking
995
818
  const statusLine = createStatusLine();
@@ -1000,8 +823,11 @@ async function executeSingleProject(
1000
823
  return;
1001
824
  }
1002
825
  // Show running status with task name and timer (updates in place)
1003
- const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1004
- statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed, taskId, modelShortName));
826
+ const modelShortName = currentModel ? formatModelDisplay(currentModel) : undefined;
827
+ statusLine.update(formatTaskProgress(taskNumber, totalTasks, 'running', displayName, elapsed, taskId, modelShortName, {
828
+ effort: currentEffort,
829
+ fast: currentModelFast,
830
+ }));
1005
831
  });
1006
832
  timer.start();
1007
833
 
@@ -1020,12 +846,14 @@ async function executeSingleProject(
1020
846
  const modelResolution = resolveTaskModel(
1021
847
  task.frontmatter,
1022
848
  undefined, // warnings already logged above
1023
- ceilingModel,
849
+ ceilingEntry,
1024
850
  isRetry,
1025
851
  );
1026
852
 
1027
853
  // Update current model for timer callback display
1028
- currentModel = modelResolution.model;
854
+ currentModel = modelResolution.entry.model;
855
+ currentEffort = task.frontmatter?.effort;
856
+ currentModelFast = modelResolution.entry.fast === true;
1029
857
 
1030
858
  // Log missing frontmatter warning on first attempt only
1031
859
  if (!isRetry && modelResolution.missingFrontmatter) {
@@ -1033,14 +861,14 @@ async function executeSingleProject(
1033
861
  }
1034
862
 
1035
863
  // Create a runner for this attempt's model
1036
- const taskRunner = createRunner({ model: modelResolution.model });
864
+ const taskRunner = createRunner({ model: modelResolution.entry.model, harness: modelResolution.entry.harness, reasoningEffort: modelResolution.entry.reasoningEffort, fast: modelResolution.entry.fast });
1037
865
  shutdownHandler.registerClaudeRunner(taskRunner);
1038
866
 
1039
867
  if (verbose && isRetry) {
1040
- const retryModel = resolveFullModelId(modelResolution.model);
868
+ const retryModel = formatResolvedTaskModel(modelResolution.entry);
1041
869
  logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel} (model: ${retryModel})...`);
1042
870
  } else if (verbose && !isRetry) {
1043
- const taskModel = resolveFullModelId(modelResolution.model);
871
+ const taskModel = formatResolvedTaskModel(modelResolution.entry);
1044
872
  logger.info(` Model: ${taskModel}`);
1045
873
  }
1046
874
 
@@ -1202,8 +1030,11 @@ Task completed. No detailed report provided.
1202
1030
  logger.success(` Task ${taskLabel} completed (${elapsedFormatted})`);
1203
1031
  } else {
1204
1032
  // Minimal mode: show completed task line
1205
- const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1206
- logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id, modelShortName));
1033
+ const modelShortName = currentModel ? formatModelDisplay(currentModel) : undefined;
1034
+ logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id, modelShortName, {
1035
+ effort: currentEffort,
1036
+ fast: currentModelFast,
1037
+ }));
1207
1038
  }
1208
1039
 
1209
1040
  // Track and display token usage for this task
@@ -1229,12 +1060,16 @@ Task completed. No detailed report provided.
1229
1060
 
1230
1061
  if (verbose) {
1231
1062
  logger.error(` Task ${taskLabel} failed: ${failureReason} (${elapsedFormatted})`);
1232
- const analysisModel = getModelShortName(getModel('failureAnalysis'));
1063
+ const analysisEntry = getModel('failureAnalysis');
1064
+ const analysisModel = formatModelDisplay(analysisEntry.model);
1233
1065
  logger.info(` Analyzing failure with ${analysisModel}...`);
1234
1066
  } else {
1235
1067
  // Minimal mode: show failed task line
1236
- const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1237
- logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id, modelShortName));
1068
+ const modelShortName = currentModel ? formatModelDisplay(currentModel) : undefined;
1069
+ logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id, modelShortName, {
1070
+ effort: currentEffort,
1071
+ fast: currentModelFast,
1072
+ }));
1238
1073
  }
1239
1074
 
1240
1075
  // Track token usage even for failed tasks (partial data still useful for totals)
@@ -1387,4 +1222,3 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1387
1222
  retryHistory: projectRetryHistory,
1388
1223
  };
1389
1224
  }
1390
-