rafcode 3.2.1 → 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 (200) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CLAUDE.md +0 -1
  3. package/RAF/41-echo-chamber/decisions.md +13 -0
  4. package/RAF/41-echo-chamber/input.md +4 -0
  5. package/RAF/41-echo-chamber/outcomes/1-update-codex-model-defaults.md +24 -0
  6. package/RAF/41-echo-chamber/outcomes/2-e2e-test-codex-provider.md +74 -0
  7. package/RAF/41-echo-chamber/plans/1-update-codex-model-defaults.md +28 -0
  8. package/RAF/41-echo-chamber/plans/2-e2e-test-codex-provider.md +103 -0
  9. package/RAF/42-patch-parade/decisions.md +29 -0
  10. package/RAF/42-patch-parade/input.md +9 -0
  11. package/RAF/42-patch-parade/outcomes/1-fix-codex-model-resolution.md +36 -0
  12. package/RAF/42-patch-parade/outcomes/2-fix-provider-aware-name-generation.md +31 -0
  13. package/RAF/42-patch-parade/outcomes/3-fix-codex-error-event-rendering.md +32 -0
  14. package/RAF/42-patch-parade/outcomes/4-update-cli-help-docs.md +28 -0
  15. package/RAF/42-patch-parade/outcomes/5-update-default-codex-models-to-gpt-5-4.md +33 -0
  16. package/RAF/42-patch-parade/outcomes/6-unify-model-config-schema.md +89 -0
  17. package/RAF/42-patch-parade/plans/1-fix-codex-model-resolution.md +35 -0
  18. package/RAF/42-patch-parade/plans/2-fix-provider-aware-name-generation.md +38 -0
  19. package/RAF/42-patch-parade/plans/3-fix-codex-error-event-rendering.md +32 -0
  20. package/RAF/42-patch-parade/plans/4-update-cli-help-docs.md +31 -0
  21. package/RAF/42-patch-parade/plans/5-update-default-codex-models-to-gpt-5-4.md +35 -0
  22. package/RAF/42-patch-parade/plans/6-unify-model-config-schema.md +46 -0
  23. package/RAF/43-swiss-army/decisions.md +34 -0
  24. package/RAF/43-swiss-army/input.md +7 -0
  25. package/RAF/43-swiss-army/outcomes/1-fix-model-validation.md +21 -0
  26. package/RAF/43-swiss-army/outcomes/2-update-commit-format.md +31 -0
  27. package/RAF/43-swiss-army/outcomes/3-wire-reasoning-effort.md +28 -0
  28. package/RAF/43-swiss-army/outcomes/4-remove-provider-flag.md +27 -0
  29. package/RAF/43-swiss-army/outcomes/5-config-wizard-validation.md +23 -0
  30. package/RAF/43-swiss-army/outcomes/6-add-fast-mode.md +32 -0
  31. package/RAF/43-swiss-army/outcomes/7-config-preset.md +31 -0
  32. package/RAF/43-swiss-army/plans/1-fix-model-validation.md +38 -0
  33. package/RAF/43-swiss-army/plans/2-update-commit-format.md +46 -0
  34. package/RAF/43-swiss-army/plans/3-wire-reasoning-effort.md +39 -0
  35. package/RAF/43-swiss-army/plans/4-remove-provider-flag.md +43 -0
  36. package/RAF/43-swiss-army/plans/5-config-wizard-validation.md +42 -0
  37. package/RAF/43-swiss-army/plans/6-add-fast-mode.md +46 -0
  38. package/RAF/43-swiss-army/plans/7-config-preset.md +51 -0
  39. package/RAF/44-config-api-change/decisions.md +22 -0
  40. package/RAF/44-config-api-change/input.md +5 -0
  41. package/RAF/44-config-api-change/outcomes/1-restructure-config-subcommands.md +19 -0
  42. package/RAF/44-config-api-change/outcomes/2-move-preset-under-config.md +17 -0
  43. package/RAF/44-config-api-change/outcomes/3-update-existing-tests-for-config-api.md +14 -0
  44. package/RAF/44-config-api-change/outcomes/4-update-config-command-docs.md +11 -0
  45. package/RAF/44-config-api-change/outcomes/5-fix-codex-name-generation.md +18 -0
  46. package/RAF/44-config-api-change/plans/1-restructure-config-subcommands.md +37 -0
  47. package/RAF/44-config-api-change/plans/2-move-preset-under-config.md +38 -0
  48. package/RAF/44-config-api-change/plans/3-update-existing-tests-for-config-api.md +38 -0
  49. package/RAF/44-config-api-change/plans/4-update-config-command-docs.md +36 -0
  50. package/RAF/44-config-api-change/plans/5-fix-codex-name-generation.md +49 -0
  51. package/RAF/45-signal-cairn/decisions.md +7 -0
  52. package/RAF/45-signal-cairn/input.md +2 -0
  53. package/RAF/45-signal-cairn/outcomes/1-rename-provider-to-harness.md +19 -0
  54. package/RAF/45-signal-cairn/outcomes/2-normalize-model-display-names.md +18 -0
  55. package/RAF/45-signal-cairn/plans/1-rename-provider-to-harness.md +40 -0
  56. package/RAF/45-signal-cairn/plans/2-normalize-model-display-names.md +41 -0
  57. package/RAF/45-signal-lantern/decisions.md +10 -0
  58. package/RAF/45-signal-lantern/input.md +2 -0
  59. package/RAF/45-signal-lantern/outcomes/1-add-effort-and-fast-to-do-model-display.md +15 -0
  60. package/RAF/45-signal-lantern/outcomes/2-capture-codex-post-run-token-usage.md +15 -0
  61. package/RAF/45-signal-lantern/outcomes/3-show-codex-token-summaries-without-fake-cost.md +14 -0
  62. package/RAF/45-signal-lantern/plans/1-add-effort-and-fast-to-do-model-display.md +38 -0
  63. package/RAF/45-signal-lantern/plans/2-capture-codex-post-run-token-usage.md +37 -0
  64. package/RAF/45-signal-lantern/plans/3-show-codex-token-summaries-without-fake-cost.md +40 -0
  65. package/RAF/46-lantern-arc/decisions.md +19 -0
  66. package/RAF/46-lantern-arc/input.md +6 -0
  67. package/RAF/46-lantern-arc/outcomes/1-remove-spark-alias.md +16 -0
  68. package/RAF/46-lantern-arc/outcomes/2-clean-up-worktree-plan-command.md +30 -0
  69. package/RAF/46-lantern-arc/outcomes/3-fix-token-usage-accumulation.md +32 -0
  70. package/RAF/46-lantern-arc/outcomes/4-display-effort-in-compact-mode.md +22 -0
  71. package/RAF/46-lantern-arc/outcomes/5-codex-fast-mode-research.md +38 -0
  72. package/RAF/46-lantern-arc/outcomes/6-optimize-llm-prompts.md +39 -0
  73. package/RAF/46-lantern-arc/plans/1-remove-spark-alias.md +38 -0
  74. package/RAF/46-lantern-arc/plans/2-clean-up-worktree-plan-command.md +33 -0
  75. package/RAF/46-lantern-arc/plans/3-fix-token-usage-accumulation.md +33 -0
  76. package/RAF/46-lantern-arc/plans/4-display-effort-in-compact-mode.md +28 -0
  77. package/RAF/46-lantern-arc/plans/5-codex-fast-mode-research.md +34 -0
  78. package/RAF/46-lantern-arc/plans/6-optimize-llm-prompts.md +48 -0
  79. package/RAF/47-signal-trim/decisions.md +13 -0
  80. package/RAF/47-signal-trim/input.md +2 -0
  81. package/RAF/47-signal-trim/plans/1-remove-cache-from-status.md +73 -0
  82. package/README.md +47 -57
  83. package/dist/commands/config.d.ts.map +1 -1
  84. package/dist/commands/config.js +47 -49
  85. package/dist/commands/config.js.map +1 -1
  86. package/dist/commands/do.d.ts +2 -0
  87. package/dist/commands/do.d.ts.map +1 -1
  88. package/dist/commands/do.js +57 -44
  89. package/dist/commands/do.js.map +1 -1
  90. package/dist/commands/plan.d.ts.map +1 -1
  91. package/dist/commands/plan.js +36 -153
  92. package/dist/commands/plan.js.map +1 -1
  93. package/dist/commands/preset.d.ts +3 -0
  94. package/dist/commands/preset.d.ts.map +1 -0
  95. package/dist/commands/preset.js +158 -0
  96. package/dist/commands/preset.js.map +1 -0
  97. package/dist/core/claude-runner.d.ts +2 -0
  98. package/dist/core/claude-runner.d.ts.map +1 -1
  99. package/dist/core/claude-runner.js +36 -12
  100. package/dist/core/claude-runner.js.map +1 -1
  101. package/dist/core/codex-runner.d.ts +1 -0
  102. package/dist/core/codex-runner.d.ts.map +1 -1
  103. package/dist/core/codex-runner.js +26 -7
  104. package/dist/core/codex-runner.js.map +1 -1
  105. package/dist/core/failure-analyzer.js +2 -1
  106. package/dist/core/failure-analyzer.js.map +1 -1
  107. package/dist/core/git.d.ts +2 -2
  108. package/dist/core/git.d.ts.map +1 -1
  109. package/dist/core/git.js +53 -3
  110. package/dist/core/git.js.map +1 -1
  111. package/dist/core/pull-request.js +3 -3
  112. package/dist/core/pull-request.js.map +1 -1
  113. package/dist/core/runner-factory.d.ts +4 -4
  114. package/dist/core/runner-factory.d.ts.map +1 -1
  115. package/dist/core/runner-factory.js +8 -8
  116. package/dist/core/runner-factory.js.map +1 -1
  117. package/dist/core/runner-interface.d.ts +1 -1
  118. package/dist/core/runner-types.d.ts +17 -4
  119. package/dist/core/runner-types.d.ts.map +1 -1
  120. package/dist/parsers/codex-stream-renderer.d.ts +7 -0
  121. package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
  122. package/dist/parsers/codex-stream-renderer.js +37 -4
  123. package/dist/parsers/codex-stream-renderer.js.map +1 -1
  124. package/dist/prompts/amend.d.ts.map +1 -1
  125. package/dist/prompts/amend.js +29 -101
  126. package/dist/prompts/amend.js.map +1 -1
  127. package/dist/prompts/execution.d.ts.map +1 -1
  128. package/dist/prompts/execution.js +17 -34
  129. package/dist/prompts/execution.js.map +1 -1
  130. package/dist/prompts/planning.d.ts.map +1 -1
  131. package/dist/prompts/planning.js +21 -120
  132. package/dist/prompts/planning.js.map +1 -1
  133. package/dist/types/config.d.ts +33 -31
  134. package/dist/types/config.d.ts.map +1 -1
  135. package/dist/types/config.js +14 -28
  136. package/dist/types/config.js.map +1 -1
  137. package/dist/utils/config.d.ts +36 -16
  138. package/dist/utils/config.d.ts.map +1 -1
  139. package/dist/utils/config.js +209 -104
  140. package/dist/utils/config.js.map +1 -1
  141. package/dist/utils/name-generator.d.ts.map +1 -1
  142. package/dist/utils/name-generator.js +25 -12
  143. package/dist/utils/name-generator.js.map +1 -1
  144. package/dist/utils/terminal-symbols.d.ts +15 -2
  145. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  146. package/dist/utils/terminal-symbols.js +36 -4
  147. package/dist/utils/terminal-symbols.js.map +1 -1
  148. package/dist/utils/token-tracker.d.ts +6 -1
  149. package/dist/utils/token-tracker.d.ts.map +1 -1
  150. package/dist/utils/token-tracker.js +84 -51
  151. package/dist/utils/token-tracker.js.map +1 -1
  152. package/dist/utils/validation.d.ts +1 -2
  153. package/dist/utils/validation.d.ts.map +1 -1
  154. package/dist/utils/validation.js +4 -25
  155. package/dist/utils/validation.js.map +1 -1
  156. package/package.json +1 -1
  157. package/src/commands/config.ts +60 -63
  158. package/src/commands/do.ts +63 -51
  159. package/src/commands/plan.ts +34 -165
  160. package/src/commands/preset.ts +186 -0
  161. package/src/core/claude-runner.ts +45 -5
  162. package/src/core/codex-runner.ts +32 -7
  163. package/src/core/failure-analyzer.ts +2 -1
  164. package/src/core/git.ts +57 -3
  165. package/src/core/pull-request.ts +3 -3
  166. package/src/core/runner-factory.ts +9 -9
  167. package/src/core/runner-interface.ts +1 -1
  168. package/src/core/runner-types.ts +17 -4
  169. package/src/parsers/codex-stream-renderer.ts +47 -4
  170. package/src/prompts/amend.ts +29 -101
  171. package/src/prompts/config-docs.md +206 -62
  172. package/src/prompts/execution.ts +17 -34
  173. package/src/prompts/planning.ts +21 -120
  174. package/src/types/config.ts +47 -58
  175. package/src/utils/config.ts +248 -115
  176. package/src/utils/name-generator.ts +29 -13
  177. package/src/utils/terminal-symbols.ts +46 -6
  178. package/src/utils/token-tracker.ts +96 -57
  179. package/src/utils/validation.ts +5 -30
  180. package/tests/unit/amend-prompt.test.ts +3 -2
  181. package/tests/unit/claude-runner-interactive.test.ts +21 -3
  182. package/tests/unit/claude-runner.test.ts +39 -0
  183. package/tests/unit/codex-runner.test.ts +163 -0
  184. package/tests/unit/codex-stream-renderer.test.ts +127 -0
  185. package/tests/unit/command-output.test.ts +57 -0
  186. package/tests/unit/commit-planning-artifacts-worktree.test.ts +24 -7
  187. package/tests/unit/commit-planning-artifacts.test.ts +26 -4
  188. package/tests/unit/config-command.test.ts +215 -303
  189. package/tests/unit/config.test.ts +319 -235
  190. package/tests/unit/dependency-integration.test.ts +27 -1
  191. package/tests/unit/do-model-display.test.ts +35 -0
  192. package/tests/unit/execution-prompt.test.ts +49 -19
  193. package/tests/unit/name-generator.test.ts +82 -12
  194. package/tests/unit/plan-command-auto-flag.test.ts +7 -10
  195. package/tests/unit/plan-command.test.ts +14 -17
  196. package/tests/unit/planning-prompt.test.ts +9 -8
  197. package/tests/unit/terminal-symbols.test.ts +94 -3
  198. package/tests/unit/token-tracker.test.ts +180 -1
  199. package/tests/unit/validation.test.ts +9 -41
  200. package/tests/unit/worktree-flag-override.test.ts +0 -186
@@ -8,9 +8,10 @@ import { shutdownHandler } from '../core/shutdown-handler.js';
8
8
  import { logger } from '../utils/logger.js';
9
9
  import {
10
10
  getConfigPath,
11
+ formatModelDisplay,
11
12
  getModel,
12
- getModelShortName,
13
13
  validateConfig,
14
+ collectConfigValidationWarnings,
14
15
  ConfigValidationError,
15
16
  resetConfigCache,
16
17
  resolveConfig,
@@ -18,12 +19,7 @@ import {
18
19
  } from '../utils/config.js';
19
20
  import { DEFAULT_CONFIG } from '../types/config.js';
20
21
  import type { UserConfig } from '../types/config.js';
21
-
22
- interface ConfigCommandOptions {
23
- reset?: boolean;
24
- get?: true | string; // true when --get with no key, string when --get <key>
25
- set?: string[]; // [key, value]
26
- }
22
+ import { createPresetCommand } from './preset.js';
27
23
 
28
24
  /**
29
25
  * Load the config documentation markdown from src/prompts/config-docs.md.
@@ -92,10 +88,10 @@ function postSessionValidation(configPath: string): void {
92
88
  } catch (error) {
93
89
  if (error instanceof ConfigValidationError) {
94
90
  logger.warn(`Config validation warning: ${error.message}`);
95
- logger.warn('The file was not deleted — you can fix it manually or run `raf config` again.');
91
+ logger.warn('The file was not deleted — you can fix it manually or run `raf config wizard` again.');
96
92
  } else if (error instanceof SyntaxError) {
97
93
  logger.warn('Config file contains invalid JSON.');
98
- logger.warn('The file was not deleted — you can fix it manually or run `raf config` again.');
94
+ logger.warn('The file was not deleted — you can fix it manually or run `raf config wizard` again.');
99
95
  } else {
100
96
  logger.warn(`Could not validate config: ${error}`);
101
97
  }
@@ -242,18 +238,16 @@ function formatValue(value: unknown): string {
242
238
  // ---- Config get/set handlers ----
243
239
 
244
240
  /**
245
- * Handle --get flag: print config value(s).
241
+ * Print config value(s).
246
242
  */
247
- function handleGet(key: true | string): void {
243
+ function handleGet(key?: string): void {
248
244
  const config = resolveConfig();
249
245
 
250
- if (key === true) {
251
- // No key specified: print full config
246
+ if (key === undefined) {
252
247
  console.log(JSON.stringify(config, null, 2));
253
248
  return;
254
249
  }
255
250
 
256
- // Specific key requested
257
251
  const value = getNestedValue(config, key);
258
252
 
259
253
  if (value === undefined) {
@@ -265,15 +259,9 @@ function handleGet(key: true | string): void {
265
259
  }
266
260
 
267
261
  /**
268
- * Handle --set flag: update config file with a new value.
262
+ * Update config file with a new value.
269
263
  */
270
- function handleSet(args: string[]): void {
271
- if (args.length !== 2) {
272
- logger.error('--set requires exactly 2 arguments: key and value');
273
- process.exit(1);
274
- }
275
-
276
- const [key, rawValue] = args as [string, string];
264
+ function handleSet(key: string, rawValue: string): void {
277
265
  const value = parseValue(rawValue);
278
266
  const configPath = getConfigPath();
279
267
 
@@ -312,7 +300,10 @@ function handleSet(args: string[]): void {
312
300
 
313
301
  // Validate the resulting config
314
302
  try {
315
- validateConfig(userConfig);
303
+ const validated = validateConfig(userConfig);
304
+ for (const warning of collectConfigValidationWarnings(validated)) {
305
+ logger.warn(`Config validation warning: ${warning}`);
306
+ }
316
307
  } catch (error) {
317
308
  if (error instanceof ConfigValidationError) {
318
309
  logger.error(`Validation error: ${error.message}`);
@@ -336,41 +327,47 @@ function handleSet(args: string[]): void {
336
327
 
337
328
  export function createConfigCommand(): Command {
338
329
  const command = new Command('config')
339
- .description('View and edit RAF configuration interactively')
340
- .argument('[prompt...]', 'Optional initial prompt for the config session')
341
- .option('--reset', 'Delete config file and restore all defaults')
342
- .option('--get [key]', 'Show config value (all config if no key, or specific dot-notation key)')
343
- .option('--set <items...>', 'Set a config value using dot-notation key and value')
344
- .action(async (promptParts: string[], options: ConfigCommandOptions) => {
345
- // --reset takes precedence
346
- if (options.reset) {
347
- await handleReset();
348
- return;
349
- }
350
-
351
- // --get and --set are mutually exclusive
352
- if (options.get !== undefined && options.set !== undefined) {
353
- logger.error('Cannot use --get and --set together');
354
- process.exit(1);
355
- }
356
-
357
- // Handle --get
358
- if (options.get !== undefined) {
359
- handleGet(options.get);
360
- return;
361
- }
362
-
363
- // Handle --set
364
- if (options.set !== undefined) {
365
- handleSet(options.set);
366
- return;
367
- }
368
-
369
- // Default: run interactive session
370
- const initialPrompt = promptParts.length > 0 ? promptParts.join(' ') : undefined;
371
- await runConfigSession(initialPrompt);
330
+ .description('Manage RAF configuration with get, set, reset, wizard, and preset subcommands')
331
+ .action(function(this: Command) {
332
+ this.outputHelp();
372
333
  });
373
334
 
335
+ command
336
+ .addCommand(
337
+ new Command('get')
338
+ .description('Print the resolved config or one resolved dot-notation value')
339
+ .argument('[key]', 'Optional dot-notation key to read')
340
+ .action((key?: string) => {
341
+ handleGet(key);
342
+ })
343
+ )
344
+ .addCommand(
345
+ new Command('set')
346
+ .description('Write a config value using a dot-notation key')
347
+ .argument('<key>', 'Dot-notation key to write')
348
+ .argument('<value>', 'Value to write; parsed as JSON when possible')
349
+ .action((key: string, value: string) => {
350
+ handleSet(key, value);
351
+ })
352
+ )
353
+ .addCommand(
354
+ new Command('reset')
355
+ .description('Delete the config file and restore all defaults')
356
+ .action(async () => {
357
+ await handleReset();
358
+ })
359
+ )
360
+ .addCommand(
361
+ new Command('wizard')
362
+ .description('Launch the interactive config editor session')
363
+ .argument('[prompt...]', 'Optional initial prompt for the config session')
364
+ .action(async (promptParts: string[]) => {
365
+ const initialPrompt = promptParts.length > 0 ? promptParts.join(' ') : undefined;
366
+ await runConfigSession(initialPrompt);
367
+ })
368
+ )
369
+ .addCommand(createPresetCommand());
370
+
374
371
  return command;
375
372
  }
376
373
 
@@ -399,16 +396,16 @@ async function runConfigSession(initialPrompt?: string): Promise<void> {
399
396
  const configPath = getConfigPath();
400
397
 
401
398
  // Try to load config, but fall back to defaults if it's broken
402
- // This allows raf config to be used to fix a broken config file
403
- let model: string;
399
+ // This allows raf config wizard to be used to fix a broken config file
400
+ let modelEntry: import('../types/config.js').ModelEntry;
404
401
  let configError: Error | null = null;
405
402
 
406
403
  try {
407
- model = getModel('config');
404
+ modelEntry = getModel('config');
408
405
  } catch (error) {
409
406
  // Config file has errors - fall back to defaults so the session can launch
410
407
  configError = error instanceof Error ? error : new Error(String(error));
411
- model = DEFAULT_CONFIG.models.config;
408
+ modelEntry = DEFAULT_CONFIG.models.config;
412
409
  // Clear the cached config so subsequent calls don't use the broken cache
413
410
  resetConfigCache();
414
411
  }
@@ -416,7 +413,7 @@ async function runConfigSession(initialPrompt?: string): Promise<void> {
416
413
  // Warn user if config has errors, before starting the session
417
414
  if (configError) {
418
415
  logger.warn(`Config file has errors, using defaults: ${configError.message}`);
419
- logger.warn('Fix the config in this session or run `raf config --reset` to start fresh.');
416
+ logger.warn('Fix the config in this session or run `raf config reset` to start fresh.');
420
417
  logger.newline();
421
418
  }
422
419
 
@@ -438,11 +435,11 @@ async function runConfigSession(initialPrompt?: string): Promise<void> {
438
435
  ?? 'Show me my current config and help me make changes.';
439
436
 
440
437
  // Set up runner
441
- const claudeRunner = createRunner({ model });
438
+ const claudeRunner = createRunner({ model: modelEntry.model, harness: modelEntry.harness, reasoningEffort: modelEntry.reasoningEffort, fast: modelEntry.fast });
442
439
  shutdownHandler.init();
443
440
  shutdownHandler.registerClaudeRunner(claudeRunner);
444
441
 
445
- const configModel = getModelShortName(model);
442
+ const configModel = formatModelDisplay(modelEntry.model);
446
443
  logger.info(`Starting config session with ${configModel}...`);
447
444
  logger.newline();
448
445
 
@@ -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
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, 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,
@@ -52,7 +53,7 @@ import {
52
53
  rebaseOntoMain,
53
54
  } from '../core/worktree.js';
54
55
  import { createPullRequest, prPreflight } from '../core/pull-request.js';
55
- import type { DoCommandOptions } from '../types/config.js';
56
+ import type { DoCommandOptions, ModelEntry } from '../types/config.js';
56
57
 
57
58
  /**
58
59
  * Post-execution action chosen by the user before task execution begins.
@@ -66,8 +67,8 @@ export type PostExecutionAction = 'merge' | 'pr' | 'leave';
66
67
  * Result of resolving a task's model from frontmatter.
67
68
  */
68
69
  interface TaskModelResolution {
69
- /** The resolved model (after ceiling is applied). */
70
- model: string;
70
+ /** The resolved model entry (after ceiling is applied). */
71
+ entry: ModelEntry;
71
72
  /** Whether a warning should be logged about missing frontmatter. */
72
73
  missingFrontmatter: boolean;
73
74
  /** Frontmatter parsing warnings to log. */
@@ -84,27 +85,26 @@ interface TaskModelResolution {
84
85
  *
85
86
  * @param frontmatter - Parsed frontmatter from the plan file
86
87
  * @param frontmatterWarnings - Warnings from frontmatter parsing
87
- * @param ceilingModel - The ceiling model (usually models.execute from config)
88
+ * @param ceilingEntry - The ceiling model entry (usually models.execute from config)
88
89
  * @param isRetry - Whether this is a retry attempt (escalates to ceiling)
89
90
  */
90
91
  function resolveTaskModel(
91
92
  frontmatter: PlanFrontmatter | undefined,
92
93
  frontmatterWarnings: string[] | undefined,
93
- ceilingModel: string,
94
+ ceilingEntry: ModelEntry,
94
95
  isRetry: boolean,
95
- provider?: import('../types/config.js').HarnessProvider,
96
96
  ): TaskModelResolution {
97
97
  const warnings = frontmatterWarnings ? [...frontmatterWarnings] : [];
98
98
 
99
99
  // Retry escalation: always use the ceiling model on retry
100
100
  if (isRetry) {
101
- return { model: ceilingModel, missingFrontmatter: false, warnings };
101
+ return { entry: ceilingEntry, missingFrontmatter: false, warnings };
102
102
  }
103
103
 
104
104
  // No frontmatter - fallback to ceiling with warning
105
105
  if (!frontmatter) {
106
106
  return {
107
- model: ceilingModel,
107
+ entry: ceilingEntry,
108
108
  missingFrontmatter: true,
109
109
  warnings,
110
110
  };
@@ -112,20 +112,25 @@ function resolveTaskModel(
112
112
 
113
113
  // Explicit model in frontmatter - apply ceiling
114
114
  if (frontmatter.model) {
115
- const model = applyModelCeiling(frontmatter.model, ceilingModel);
116
- 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 };
117
122
  }
118
123
 
119
124
  // Effort-based resolution - apply ceiling
120
125
  if (frontmatter.effort) {
121
- const mappedModel = resolveEffortToModel(frontmatter.effort, provider);
122
- const model = applyModelCeiling(mappedModel, ceilingModel);
123
- return { model, missingFrontmatter: false, warnings };
126
+ const mappedEntry = resolveEffortToModel(frontmatter.effort);
127
+ const result = applyModelCeiling(mappedEntry, ceilingEntry);
128
+ return { entry: result, missingFrontmatter: false, warnings };
124
129
  }
125
130
 
126
131
  // Frontmatter present but no effort or model - fallback to ceiling with warning
127
132
  return {
128
- model: ceilingModel,
133
+ entry: ceilingEntry,
129
134
  missingFrontmatter: true,
130
135
  warnings,
131
136
  };
@@ -186,6 +191,13 @@ interface ProjectExecutionResult {
186
191
  retryHistory?: TaskRetryHistory[];
187
192
  }
188
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
+
189
201
  export function createDoCommand(): Command {
190
202
  const command = new Command('do')
191
203
  .description('Execute planned tasks for a project')
@@ -195,9 +207,6 @@ export function createDoCommand(): Command {
195
207
  .option('-v, --verbose', 'Show full LLM output')
196
208
  .option('-d, --debug', 'Save all logs and show debug output')
197
209
  .option('-f, --force', 'Re-run all tasks regardless of status')
198
- .option('-m, --model <name>', 'Model to use (sonnet, haiku, opus)')
199
- .option('--sonnet', 'Use Sonnet model (shorthand for --model sonnet)')
200
- .option('-p, --provider <provider>', 'CLI provider to use (claude, codex)')
201
210
  .action(async (project: string | undefined, options: DoCommandOptions) => {
202
211
  await runDoCommand(project, options);
203
212
  });
@@ -208,14 +217,7 @@ export function createDoCommand(): Command {
208
217
  async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
209
218
  const rafDir = getRafDir();
210
219
  let projectIdentifier = projectIdentifierArg;
211
- // Validate and resolve model option
212
- let model: string;
213
- try {
214
- model = resolveModelOption(options.model as string | undefined, options.sonnet, 'execute');
215
- } catch (error) {
216
- logger.error((error as Error).message);
217
- process.exit(1);
218
- }
220
+ const executeEntry = getModel('execute');
219
221
 
220
222
  // Variables for worktree context (derived from where the project is found)
221
223
  let worktreeRoot: string | undefined;
@@ -398,8 +400,7 @@ async function runDoCommand(projectIdentifierArg: string | undefined, options: D
398
400
  force,
399
401
  maxRetries,
400
402
  autoCommit,
401
- model,
402
- provider: options.provider,
403
+ executeEntry,
403
404
  worktreeCwd: worktreeRoot,
404
405
  }
405
406
  );
@@ -558,9 +559,8 @@ interface SingleProjectOptions {
558
559
  force: boolean;
559
560
  maxRetries: number;
560
561
  autoCommit: boolean;
561
- model: string;
562
- /** CLI provider to use (claude, codex). */
563
- provider?: import('../types/config.js').HarnessProvider;
562
+ /** The resolved execute model entry (acts as ceiling for per-task resolution). */
563
+ executeEntry: ModelEntry;
564
564
  /** Worktree root directory. When set, the runner uses cwd in the worktree. */
565
565
  worktreeCwd?: string;
566
566
  }
@@ -570,7 +570,7 @@ async function executeSingleProject(
570
570
  projectName: string,
571
571
  options: SingleProjectOptions
572
572
  ): Promise<ProjectExecutionResult> {
573
- const { timeout, verbose, debug, force, maxRetries, autoCommit, model, provider, worktreeCwd } = options;
573
+ const { timeout, verbose, debug, force, maxRetries, autoCommit, executeEntry, worktreeCwd } = options;
574
574
 
575
575
  if (!validatePlansExist(projectPath)) {
576
576
  return {
@@ -614,8 +614,8 @@ async function executeSingleProject(
614
614
  const projectManager = new ProjectManager();
615
615
  shutdownHandler.init();
616
616
 
617
- // The ceiling model for all tasks (can be overridden per-task, subject to this ceiling)
618
- const ceilingModel = model;
617
+ // The ceiling model entry for all tasks (can be overridden per-task, subject to this ceiling)
618
+ const ceilingEntry = executeEntry;
619
619
 
620
620
  // Initialize token tracker for usage reporting
621
621
  const tokenTracker = new TokenTracker();
@@ -628,8 +628,8 @@ async function executeSingleProject(
628
628
  const projectStartTime = Date.now();
629
629
 
630
630
  // Resolve and display version + ceiling model info (before any tasks run)
631
- const fullCeilingModelId = resolveFullModelId(ceilingModel);
632
- logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId}`);
631
+ const fullCeilingModelId = resolveFullModelId(ceilingEntry.model);
632
+ logger.dim(`RAF v${getVersion()} | Ceiling: ${fullCeilingModelId} (${ceilingEntry.harness})`);
633
633
 
634
634
  if (verbose) {
635
635
  logger.info(`Executing project: ${projectName}`);
@@ -811,6 +811,8 @@ async function executeSingleProject(
811
811
  const failureHistory: Array<{ attempt: number; reason: string }> = [];
812
812
  // Track current model for display in status line (updated in retry loop)
813
813
  let currentModel: string | undefined;
814
+ let currentEffort: string | undefined;
815
+ let currentModelFast = false;
814
816
 
815
817
  // Set up timer for elapsed time tracking
816
818
  const statusLine = createStatusLine();
@@ -821,8 +823,11 @@ async function executeSingleProject(
821
823
  return;
822
824
  }
823
825
  // Show running status with task name and timer (updates in place)
824
- const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
825
- 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
+ }));
826
831
  });
827
832
  timer.start();
828
833
 
@@ -841,13 +846,14 @@ async function executeSingleProject(
841
846
  const modelResolution = resolveTaskModel(
842
847
  task.frontmatter,
843
848
  undefined, // warnings already logged above
844
- ceilingModel,
849
+ ceilingEntry,
845
850
  isRetry,
846
- provider,
847
851
  );
848
852
 
849
853
  // Update current model for timer callback display
850
- currentModel = modelResolution.model;
854
+ currentModel = modelResolution.entry.model;
855
+ currentEffort = task.frontmatter?.effort;
856
+ currentModelFast = modelResolution.entry.fast === true;
851
857
 
852
858
  // Log missing frontmatter warning on first attempt only
853
859
  if (!isRetry && modelResolution.missingFrontmatter) {
@@ -855,14 +861,14 @@ async function executeSingleProject(
855
861
  }
856
862
 
857
863
  // Create a runner for this attempt's model
858
- const taskRunner = createRunner({ model: modelResolution.model, provider });
864
+ const taskRunner = createRunner({ model: modelResolution.entry.model, harness: modelResolution.entry.harness, reasoningEffort: modelResolution.entry.reasoningEffort, fast: modelResolution.entry.fast });
859
865
  shutdownHandler.registerClaudeRunner(taskRunner);
860
866
 
861
867
  if (verbose && isRetry) {
862
- const retryModel = resolveFullModelId(modelResolution.model);
868
+ const retryModel = formatResolvedTaskModel(modelResolution.entry);
863
869
  logger.info(` Retry ${attempts}/${maxRetries} for task ${taskLabel} (model: ${retryModel})...`);
864
870
  } else if (verbose && !isRetry) {
865
- const taskModel = resolveFullModelId(modelResolution.model);
871
+ const taskModel = formatResolvedTaskModel(modelResolution.entry);
866
872
  logger.info(` Model: ${taskModel}`);
867
873
  }
868
874
 
@@ -1024,8 +1030,11 @@ Task completed. No detailed report provided.
1024
1030
  logger.success(` Task ${taskLabel} completed (${elapsedFormatted})`);
1025
1031
  } else {
1026
1032
  // Minimal mode: show completed task line
1027
- const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1028
- 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
+ }));
1029
1038
  }
1030
1039
 
1031
1040
  // Track and display token usage for this task
@@ -1051,12 +1060,16 @@ Task completed. No detailed report provided.
1051
1060
 
1052
1061
  if (verbose) {
1053
1062
  logger.error(` Task ${taskLabel} failed: ${failureReason} (${elapsedFormatted})`);
1054
- const analysisModel = getModelShortName(getModel('failureAnalysis'));
1063
+ const analysisEntry = getModel('failureAnalysis');
1064
+ const analysisModel = formatModelDisplay(analysisEntry.model);
1055
1065
  logger.info(` Analyzing failure with ${analysisModel}...`);
1056
1066
  } else {
1057
1067
  // Minimal mode: show failed task line
1058
- const modelShortName = currentModel ? getModelShortName(currentModel) : undefined;
1059
- 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
+ }));
1060
1073
  }
1061
1074
 
1062
1075
  // Track token usage even for failed tasks (partial data still useful for totals)
@@ -1209,4 +1222,3 @@ ${stashName ? `- Stash: ${stashName}` : ''}
1209
1222
  retryHistory: projectRetryHistory,
1210
1223
  };
1211
1224
  }
1212
-