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
@@ -0,0 +1,186 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import { Command } from 'commander';
5
+ import { logger } from '../utils/logger.js';
6
+ import {
7
+ getConfigPath,
8
+ validateConfig,
9
+ ConfigValidationError,
10
+ } from '../utils/config.js';
11
+
12
+ const PRESETS_DIR = path.join(os.homedir(), '.raf', 'presets');
13
+
14
+ function ensurePresetsDir(): void {
15
+ if (!fs.existsSync(PRESETS_DIR)) {
16
+ fs.mkdirSync(PRESETS_DIR, { recursive: true });
17
+ }
18
+ }
19
+
20
+ function getPresetPath(name: string): string {
21
+ return path.join(PRESETS_DIR, `${name}.json`);
22
+ }
23
+
24
+ function validatePresetName(name: string): void {
25
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
26
+ logger.error('Preset name must contain only letters, numbers, hyphens, and underscores.');
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ function savePreset(name: string): void {
32
+ validatePresetName(name);
33
+ ensurePresetsDir();
34
+
35
+ const configPath = getConfigPath();
36
+ if (!fs.existsSync(configPath)) {
37
+ logger.error('No config file found. Run `raf config wizard` or `raf config set <key> <value>` to create one first.');
38
+ process.exit(1);
39
+ }
40
+
41
+ const presetPath = getPresetPath(name);
42
+ const configContent = fs.readFileSync(configPath, 'utf-8');
43
+
44
+ // Validate the config before saving
45
+ try {
46
+ const parsed = JSON.parse(configContent);
47
+ validateConfig(parsed);
48
+ } catch (err) {
49
+ if (err instanceof ConfigValidationError) {
50
+ logger.error(`Current config is invalid: ${err.message}`);
51
+ process.exit(1);
52
+ }
53
+ throw err;
54
+ }
55
+
56
+ const existed = fs.existsSync(presetPath);
57
+ fs.writeFileSync(presetPath, configContent);
58
+ logger.info(`${existed ? 'Updated' : 'Saved'} preset "${name}" at ${presetPath}`);
59
+ }
60
+
61
+ function loadPreset(name: string): void {
62
+ validatePresetName(name);
63
+ const presetPath = getPresetPath(name);
64
+
65
+ if (!fs.existsSync(presetPath)) {
66
+ logger.error(`Preset "${name}" not found. Run \`raf config preset list\` to see available presets.`);
67
+ process.exit(1);
68
+ }
69
+
70
+ const content = fs.readFileSync(presetPath, 'utf-8');
71
+
72
+ // Validate preset before applying
73
+ let parsed: unknown;
74
+ try {
75
+ parsed = JSON.parse(content);
76
+ } catch {
77
+ logger.error(`Preset "${name}" contains invalid JSON.`);
78
+ process.exit(1);
79
+ }
80
+
81
+ try {
82
+ validateConfig(parsed);
83
+ } catch (err) {
84
+ if (err instanceof ConfigValidationError) {
85
+ logger.error(`Preset "${name}" is invalid: ${err.message}`);
86
+ process.exit(1);
87
+ }
88
+ throw err;
89
+ }
90
+
91
+ const configPath = getConfigPath();
92
+ const configDir = path.dirname(configPath);
93
+ if (!fs.existsSync(configDir)) {
94
+ fs.mkdirSync(configDir, { recursive: true });
95
+ }
96
+ fs.writeFileSync(configPath, content);
97
+ logger.info(`Loaded preset "${name}" into ${configPath}`);
98
+
99
+ // Show a brief summary of what was loaded
100
+ const config = parsed as Record<string, unknown>;
101
+ const keys = Object.keys(config);
102
+ if (keys.length > 0) {
103
+ logger.info(` Keys: ${keys.join(', ')}`);
104
+ }
105
+ }
106
+
107
+ function listPresets(): void {
108
+ ensurePresetsDir();
109
+
110
+ const files = fs.readdirSync(PRESETS_DIR).filter(f => f.endsWith('.json'));
111
+ if (files.length === 0) {
112
+ logger.info('No presets saved. Use `raf config preset save <name>` to create one.');
113
+ return;
114
+ }
115
+
116
+ logger.info(`Found ${files.length} preset(s):\n`);
117
+ for (const file of files) {
118
+ const name = path.basename(file, '.json');
119
+ const filePath = path.join(PRESETS_DIR, file);
120
+ try {
121
+ const content = fs.readFileSync(filePath, 'utf-8');
122
+ const config = JSON.parse(content) as Record<string, unknown>;
123
+
124
+ // Build a brief summary from models
125
+ const summary: string[] = [];
126
+ if (config.models && typeof config.models === 'object') {
127
+ const models = config.models as Record<string, { model?: string; harness?: string }>;
128
+ const harnesses = new Set<string>();
129
+ for (const entry of Object.values(models)) {
130
+ if (entry?.harness) harnesses.add(entry.harness);
131
+ }
132
+ if (harnesses.size > 0) {
133
+ summary.push(`harnesses: ${[...harnesses].join(', ')}`);
134
+ }
135
+ if (models.execute?.model) {
136
+ summary.push(`execute: ${models.execute.model}`);
137
+ }
138
+ }
139
+
140
+ const info = summary.length > 0 ? ` (${summary.join('; ')})` : '';
141
+ logger.info(` ${name}${info}`);
142
+ } catch {
143
+ logger.info(` ${name} (invalid JSON)`);
144
+ }
145
+ }
146
+ }
147
+
148
+ function deletePreset(name: string): void {
149
+ validatePresetName(name);
150
+ const presetPath = getPresetPath(name);
151
+
152
+ if (!fs.existsSync(presetPath)) {
153
+ logger.error(`Preset "${name}" not found.`);
154
+ process.exit(1);
155
+ }
156
+
157
+ fs.unlinkSync(presetPath);
158
+ logger.info(`Deleted preset "${name}".`);
159
+ }
160
+
161
+ export function createPresetCommand(): Command {
162
+ const preset = new Command('preset')
163
+ .description('Manage named config presets under `raf config preset`');
164
+
165
+ preset
166
+ .command('save <name>')
167
+ .description('Save current config as a named preset')
168
+ .action((name: string) => savePreset(name));
169
+
170
+ preset
171
+ .command('load <name>')
172
+ .description('Load a preset into the active config (overwrites current config)')
173
+ .action((name: string) => loadPreset(name));
174
+
175
+ preset
176
+ .command('list')
177
+ .description('List all saved presets')
178
+ .action(() => listPresets());
179
+
180
+ preset
181
+ .command('delete <name>')
182
+ .description('Delete a saved preset')
183
+ .action((name: string) => deletePreset(name));
184
+
185
+ return preset;
186
+ }
@@ -4,6 +4,7 @@ import { execSync, spawn } from 'node:child_process';
4
4
  import { logger } from '../utils/logger.js';
5
5
  import { renderStreamEvent } from '../parsers/stream-renderer.js';
6
6
  import { getModel } from '../utils/config.js';
7
+ import { mergeUsageData } from '../utils/token-tracker.js';
7
8
  import type { ICliRunner } from './runner-interface.js';
8
9
  import type { RunnerOptions, RunnerConfig, RunResult } from './runner-types.js';
9
10
  import { createCompletionDetector } from './completion-detector.js';
@@ -32,9 +33,13 @@ export class ClaudeRunner implements ICliRunner {
32
33
  private activeProcess: pty.IPty | null = null;
33
34
  private killed = false;
34
35
  private model: string;
36
+ private reasoningEffort?: string;
37
+ private fast?: boolean;
35
38
 
36
39
  constructor(config: RunnerConfig = {}) {
37
- this.model = config.model ?? getModel('execute');
40
+ this.model = config.model ?? getModel('execute').model;
41
+ this.reasoningEffort = config.reasoningEffort;
42
+ this.fast = config.fast;
38
43
  }
39
44
 
40
45
  /**
@@ -55,6 +60,16 @@ export class ClaudeRunner implements ICliRunner {
55
60
  return new Promise((resolve) => {
56
61
  const args = ['--model', this.model];
57
62
 
63
+ // Add reasoning effort flag when configured
64
+ if (this.reasoningEffort) {
65
+ args.push('--effort', this.reasoningEffort);
66
+ }
67
+
68
+ // Add fast mode when configured
69
+ if (this.fast) {
70
+ args.push('--settings', '{"fastMode": true}');
71
+ }
72
+
58
73
  // Add --dangerously-skip-permissions if requested (for --auto mode)
59
74
  if (dangerouslySkipPermissions) {
60
75
  args.push('--dangerously-skip-permissions');
@@ -147,6 +162,16 @@ export class ClaudeRunner implements ICliRunner {
147
162
  return new Promise((resolve) => {
148
163
  const args = ['--resume', '--model', this.model];
149
164
 
165
+ // Add reasoning effort flag when configured
166
+ if (this.reasoningEffort) {
167
+ args.push('--effort', this.reasoningEffort);
168
+ }
169
+
170
+ // Add fast mode when configured
171
+ if (this.fast) {
172
+ args.push('--settings', '{"fastMode": true}');
173
+ }
174
+
150
175
  logger.debug(`Starting session resume picker with model: ${this.model}`);
151
176
 
152
177
  this.activeProcess = pty.spawn(getClaudePath(), args, {
@@ -272,10 +297,23 @@ export class ClaudeRunner implements ICliRunner {
272
297
  // including tool calls, file operations, and token usage in the result event.
273
298
  // --dangerously-skip-permissions bypasses interactive prompts
274
299
  // -p enables print mode (non-interactive)
275
- const proc = spawn(claudePath, [
300
+ const execArgs = [
276
301
  '--dangerously-skip-permissions',
277
302
  '--model',
278
303
  this.model,
304
+ ];
305
+
306
+ // Add reasoning effort flag when configured
307
+ if (this.reasoningEffort) {
308
+ execArgs.push('--effort', this.reasoningEffort);
309
+ }
310
+
311
+ // Add fast mode when configured
312
+ if (this.fast) {
313
+ execArgs.push('--settings', '{"fastMode": true}');
314
+ }
315
+
316
+ execArgs.push(
279
317
  '--append-system-prompt',
280
318
  prompt,
281
319
  '--output-format',
@@ -283,7 +321,9 @@ export class ClaudeRunner implements ICliRunner {
283
321
  '--verbose',
284
322
  '-p',
285
323
  'Execute the task as described in the system prompt.',
286
- ], {
324
+ );
325
+
326
+ const proc = spawn(claudePath, execArgs, {
287
327
  cwd,
288
328
  env: process.env,
289
329
  stdio: ['ignore', 'pipe', 'pipe'], // no stdin needed
@@ -346,7 +386,7 @@ export class ClaudeRunner implements ICliRunner {
346
386
 
347
387
  // Capture usage data from result events
348
388
  if (rendered.usageData) {
349
- usageData = rendered.usageData;
389
+ usageData = mergeUsageData(usageData, rendered.usageData);
350
390
  }
351
391
 
352
392
  if (shouldDisplay() && rendered.display) {
@@ -368,7 +408,7 @@ export class ClaudeRunner implements ICliRunner {
368
408
  output += rendered.textContent;
369
409
  }
370
410
  if (rendered.usageData) {
371
- usageData = rendered.usageData;
411
+ usageData = mergeUsageData(usageData, rendered.usageData);
372
412
  }
373
413
  if (shouldDisplay() && rendered.display) {
374
414
  process.stdout.write(rendered.display);
@@ -4,6 +4,7 @@ import { execSync, spawn } from 'node:child_process';
4
4
  import { logger } from '../utils/logger.js';
5
5
  import { renderCodexStreamEvent } from '../parsers/codex-stream-renderer.js';
6
6
  import { getModel } from '../utils/config.js';
7
+ import { mergeUsageData } from '../utils/token-tracker.js';
7
8
  import type { ICliRunner } from './runner-interface.js';
8
9
  import type { RunnerOptions, RunnerConfig, RunResult } from './runner-types.js';
9
10
  import { createCompletionDetector } from './completion-detector.js';
@@ -36,9 +37,11 @@ export class CodexRunner implements ICliRunner {
36
37
  private activeProcess: pty.IPty | null = null;
37
38
  private killed = false;
38
39
  private model: string;
40
+ private reasoningEffort?: string;
39
41
 
40
42
  constructor(config: RunnerConfig = {}) {
41
- this.model = config.model ?? getModel('execute', 'codex');
43
+ this.model = config.model ?? getModel('execute').model;
44
+ this.reasoningEffort = config.reasoningEffort;
42
45
  }
43
46
 
44
47
  /**
@@ -54,7 +57,14 @@ export class CodexRunner implements ICliRunner {
54
57
 
55
58
  return new Promise((resolve) => {
56
59
  const combinedPrompt = buildCombinedPrompt(systemPrompt, userMessage);
57
- const args = ['-m', this.model, combinedPrompt];
60
+ const args = ['-m', this.model];
61
+
62
+ // Add reasoning effort via config override when configured
63
+ if (this.reasoningEffort) {
64
+ args.push('-c', `model_reasoning_effort="${this.reasoningEffort}"`);
65
+ }
66
+
67
+ args.push(combinedPrompt);
58
68
 
59
69
  logger.debug(`Starting interactive Codex session with model: ${this.model}`);
60
70
 
@@ -167,6 +177,7 @@ export class CodexRunner implements ICliRunner {
167
177
  let stderr = '';
168
178
  let timedOut = false;
169
179
  let contextOverflow = false;
180
+ let usageData: import('../types/config.js').UsageData | undefined;
170
181
 
171
182
  const codexPath = getCodexPath();
172
183
 
@@ -175,15 +186,23 @@ export class CodexRunner implements ICliRunner {
175
186
  logger.debug(`Codex path: ${codexPath}`);
176
187
 
177
188
  logger.debug('Spawning process...');
178
- const proc = spawn(codexPath, [
189
+ const execArgs = [
179
190
  'exec',
180
191
  '--full-auto',
181
192
  '--json',
182
193
  '--ephemeral',
183
194
  '-m',
184
195
  this.model,
185
- prompt,
186
- ], {
196
+ ];
197
+
198
+ // Add reasoning effort via config override when configured
199
+ if (this.reasoningEffort) {
200
+ execArgs.push('-c', `model_reasoning_effort="${this.reasoningEffort}"`);
201
+ }
202
+
203
+ execArgs.push(prompt);
204
+
205
+ const proc = spawn(codexPath, execArgs, {
187
206
  cwd,
188
207
  env: process.env,
189
208
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -244,6 +263,10 @@ export class CodexRunner implements ICliRunner {
244
263
  }
245
264
  }
246
265
 
266
+ if (rendered.usageData) {
267
+ usageData = mergeUsageData(usageData, rendered.usageData);
268
+ }
269
+
247
270
  if (shouldDisplay() && rendered.display) {
248
271
  process.stdout.write(rendered.display);
249
272
  }
@@ -262,6 +285,9 @@ export class CodexRunner implements ICliRunner {
262
285
  if (rendered.textContent) {
263
286
  output += rendered.textContent;
264
287
  }
288
+ if (rendered.usageData) {
289
+ usageData = mergeUsageData(usageData, rendered.usageData);
290
+ }
265
291
  if (shouldDisplay() && rendered.display) {
266
292
  process.stdout.write(rendered.display);
267
293
  }
@@ -281,8 +307,7 @@ export class CodexRunner implements ICliRunner {
281
307
  exitCode: exitCode ?? (this.killed ? 130 : 1),
282
308
  timedOut,
283
309
  contextOverflow,
284
- // Codex does not provide usage data in the same format — omit gracefully
285
- usageData: undefined,
310
+ usageData,
286
311
  });
287
312
  });
288
313
  });
@@ -308,7 +308,8 @@ Respond with ONLY a markdown report in this exact format:
308
308
  const claudePath = getClaudePath();
309
309
 
310
310
  // Use configured model for failure analysis
311
- const failureModel = getModel('failureAnalysis');
311
+ const failureEntry = getModel('failureAnalysis');
312
+ const failureModel = failureEntry.model;
312
313
  const proc = spawn(claudePath, [
313
314
  '--model', failureModel,
314
315
  '--no-session-persistence',
package/src/core/git.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { execSync } from 'node:child_process';
2
+ import * as fs from 'node:fs';
2
3
  import * as path from 'node:path';
3
4
  import { logger } from '../utils/logger.js';
4
5
  import { extractProjectNumber, extractProjectName } from '../utils/paths.js';
@@ -213,10 +214,56 @@ export function isFileCommittedInHead(filePath: string): boolean {
213
214
  }
214
215
  }
215
216
 
217
+ /**
218
+ * Extract a concise description from input.md for use in plan commit messages.
219
+ * Reads the first non-empty line and truncates to ~70 chars.
220
+ * Falls back to the project name if input.md can't be read.
221
+ */
222
+ function extractPlanDescription(projectPath: string, fallback: string): string {
223
+ const inputPath = path.join(projectPath, 'input.md');
224
+ try {
225
+ const content = fs.readFileSync(inputPath, 'utf-8');
226
+ const firstLine = content.split('\n').find(line => line.trim().length > 0);
227
+ if (firstLine) {
228
+ // Strip leading markdown markers (-, *, [ ], [x], #, etc.)
229
+ let cleaned = firstLine.trim().replace(/^[-*#>\s]*(\[.\]\s*)?/, '').trim();
230
+ if (cleaned.length > 70) {
231
+ cleaned = cleaned.substring(0, 67) + '...';
232
+ }
233
+ if (cleaned.length > 0) {
234
+ return cleaned;
235
+ }
236
+ }
237
+ } catch {
238
+ // Can't read input.md, use fallback
239
+ }
240
+ return fallback;
241
+ }
242
+
243
+ /**
244
+ * Generate a description for amend commit messages.
245
+ * Uses the count and names of additional plan files if provided.
246
+ */
247
+ function extractAmendDescription(additionalFiles?: string[]): string {
248
+ if (!additionalFiles || additionalFiles.length === 0) {
249
+ return 'updated plans';
250
+ }
251
+ // Extract task names from plan file paths (e.g., "plans/3-fix-bug.md" → "fix-bug")
252
+ const taskNames = additionalFiles
253
+ .map(f => path.basename(f, '.md'))
254
+ .map(name => name.replace(/^\d+-/, ''))
255
+ .filter(name => name.length > 0);
256
+
257
+ if (taskNames.length <= 3) {
258
+ return taskNames.join(', ');
259
+ }
260
+ return `${taskNames.length} tasks`;
261
+ }
262
+
216
263
  /**
217
264
  * Commit planning artifacts (input.md and decisions.md) for a project.
218
- * Uses commit message format: RAF[NNN] Plan: project-name
219
- * For amendments: RAF[NNN] Amend: project-name
265
+ * Uses commit message format: RAF[project-name] Plan: description
266
+ * For amendments: RAF[project-name] Amend: description
220
267
  *
221
268
  * @param projectPath - Full path to the project folder (e.g., /path/to/RAF/017-decision-vault)
222
269
  * @param options - Optional settings
@@ -254,10 +301,17 @@ export async function commitPlanningArtifacts(projectPath: string, options?: { c
254
301
  const formatType = options?.isAmend ? 'amend' as const : 'plan' as const;
255
302
  const template = getCommitFormat(formatType);
256
303
  const commitPrefix = getCommitPrefix();
304
+
305
+ // Auto-generate description based on commit type
306
+ const description = options?.isAmend
307
+ ? extractAmendDescription(options?.additionalFiles)
308
+ : extractPlanDescription(projectPath, projectName);
309
+
257
310
  const commitMessage = renderCommitMessage(template, {
258
311
  prefix: commitPrefix,
259
- projectId: projectNumber,
312
+ projectId: projectName, // backwards compat: {projectId} resolves to projectName
260
313
  projectName,
314
+ description,
261
315
  });
262
316
 
263
317
  // Build list of files to stage (absolute paths)
@@ -12,6 +12,7 @@ import {
12
12
  getInputPath,
13
13
  listProjects,
14
14
  TASK_ID_PATTERN,
15
+ numericFileSort,
15
16
  } from '../utils/paths.js';
16
17
  import { sanitizeProjectName } from '../utils/validation.js';
17
18
  import { logger } from '../utils/logger.js';
@@ -143,7 +144,7 @@ export class ProjectManager {
143
144
  }
144
145
 
145
146
  const outcomes: Array<{ taskId: string; content: string }> = [];
146
- const files = fs.readdirSync(outcomesDir).filter((f) => f.endsWith('.md')).sort();
147
+ const files = fs.readdirSync(outcomesDir).filter((f) => f.endsWith('.md')).sort(numericFileSort);
147
148
 
148
149
  for (const file of files) {
149
150
  const match = file.match(new RegExp(`^(${TASK_ID_PATTERN})-`));
@@ -3,8 +3,8 @@ import * as fs from 'node:fs';
3
3
  import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import { logger } from '../utils/logger.js';
6
- import { getModel, getModelShortName } from '../utils/config.js';
7
- import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN } from '../utils/paths.js';
6
+ import { formatModelDisplay, getModel } from '../utils/config.js';
7
+ import { extractProjectName, getInputPath, getDecisionsPath, getOutcomesDir, TASK_ID_PATTERN, numericFileSort } from '../utils/paths.js';
8
8
 
9
9
  export interface PrCreateResult {
10
10
  success: boolean;
@@ -208,7 +208,7 @@ export function readProjectContext(projectPath: string): {
208
208
 
209
209
  const outcomesDir = getOutcomesDir(projectPath);
210
210
  if (fs.existsSync(outcomesDir)) {
211
- const files = fs.readdirSync(outcomesDir).filter(f => f.endsWith('.md')).sort();
211
+ const files = fs.readdirSync(outcomesDir).filter(f => f.endsWith('.md')).sort(numericFileSort);
212
212
  const taskIdPattern = new RegExp(`^(${TASK_ID_PATTERN})-`);
213
213
  for (const file of files) {
214
214
  const match = file.match(taskIdPattern);
@@ -279,7 +279,7 @@ Respond with ONLY the PR body in this exact format (no extra text, no code fence
279
279
  [1-3 bullet points describing how to verify these changes work correctly]`;
280
280
 
281
281
  try {
282
- const prModel = getModelShortName(getModel('prGeneration'));
282
+ const prModel = formatModelDisplay(getModel('prGeneration').model);
283
283
  logger.info(`Generating PR with ${prModel}...`);
284
284
  const body = await callClaudeForPrBody(prompt, timeoutMs);
285
285
  return body;
@@ -365,7 +365,7 @@ async function callClaudeForPrBody(prompt: string, timeoutMs: number): Promise<s
365
365
  let output = '';
366
366
  let stderr = '';
367
367
 
368
- const prModel = getModel('prGeneration');
368
+ const prModel = getModel('prGeneration').model;
369
369
  const proc = spawn(claudePath, [
370
370
  '--model', prModel,
371
371
  '--no-session-persistence',
@@ -1,35 +1,35 @@
1
- import type { HarnessProvider } from '../types/config.js';
1
+ import type { HarnessName } from '../types/config.js';
2
2
  import type { ICliRunner } from './runner-interface.js';
3
3
  import type { RunnerConfig } from './runner-types.js';
4
4
  import { ClaudeRunner } from './claude-runner.js';
5
5
  import { CodexRunner } from './codex-runner.js';
6
6
 
7
7
  /**
8
- * Create a CLI runner for the given provider configuration.
8
+ * Create a CLI runner for the given harness configuration.
9
9
  */
10
10
  export function createRunner(config: RunnerConfig = {}): ICliRunner {
11
- const provider = config.provider ?? 'claude';
11
+ const harness = config.harness ?? 'claude';
12
12
 
13
- switch (provider) {
13
+ switch (harness) {
14
14
  case 'claude':
15
15
  return new ClaudeRunner(config);
16
16
  case 'codex':
17
17
  return new CodexRunner(config);
18
18
  default:
19
- throw new Error(`Unknown provider: ${provider}`);
19
+ throw new Error(`Unknown harness: ${harness}`);
20
20
  }
21
21
  }
22
22
 
23
23
  /**
24
- * Get the binary name for a given provider.
24
+ * Get the binary name for a given harness.
25
25
  */
26
- export function getProviderBinaryName(provider: HarnessProvider): string {
27
- switch (provider) {
26
+ export function getHarnessBinaryName(harness: HarnessName): string {
27
+ switch (harness) {
28
28
  case 'claude':
29
29
  return 'claude';
30
30
  case 'codex':
31
31
  return 'codex';
32
32
  default:
33
- throw new Error(`Unknown provider: ${provider}`);
33
+ throw new Error(`Unknown harness: ${harness}`);
34
34
  }
35
35
  }
@@ -1,7 +1,7 @@
1
1
  import type { RunnerOptions, RunResult } from './runner-types.js';
2
2
 
3
3
  /**
4
- * Provider-agnostic CLI runner interface.
4
+ * Harness-agnostic CLI runner interface.
5
5
  * Both ClaudeRunner and CodexRunner implement this interface.
6
6
  */
7
7
  export interface ICliRunner {
@@ -1,4 +1,4 @@
1
- import type { UsageData, HarnessProvider } from '../types/config.js';
1
+ import type { UsageData, HarnessName } from '../types/config.js';
2
2
 
3
3
  /**
4
4
  * Options for a single runner execution (run, runVerbose, runInteractive, etc.).
@@ -52,14 +52,27 @@ export interface RunnerOptions {
52
52
  export interface RunnerConfig {
53
53
  /**
54
54
  * Model to use (e.g., opus, sonnet, haiku, gpt-5.4).
55
- * Default: provider-specific default.
55
+ * Default: harness-specific default.
56
56
  */
57
57
  model?: string;
58
58
  /**
59
- * CLI provider to use.
59
+ * CLI harness to use.
60
60
  * Default: 'claude'.
61
61
  */
62
- provider?: HarnessProvider;
62
+ harness?: HarnessName;
63
+ /**
64
+ * Reasoning effort level to pass to the CLI.
65
+ * Claude CLI: --effort <level>
66
+ * Codex CLI: -c model_reasoning_effort="<level>"
67
+ * Only included when explicitly set.
68
+ */
69
+ reasoningEffort?: string;
70
+ /**
71
+ * Enable fast mode for faster output.
72
+ * Claude CLI: --settings '{"fastMode": true}'
73
+ * Only included when explicitly set to true.
74
+ */
75
+ fast?: boolean;
63
76
  }
64
77
 
65
78
  /**
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import { getPlansDir, getOutcomesDir, getInputPath, TASK_ID_PATTERN } from '../utils/paths.js';
3
+ import { getPlansDir, getOutcomesDir, getInputPath, TASK_ID_PATTERN, numericFileSort } from '../utils/paths.js';
4
4
  import { parsePlanFrontmatter, type PlanFrontmatter } from '../utils/frontmatter.js';
5
5
 
6
6
  export type DerivedTaskStatus = 'pending' | 'completed' | 'failed' | 'blocked';
@@ -198,14 +198,14 @@ export function deriveProjectState(projectPath: string): DerivedProjectState {
198
198
 
199
199
  const planFiles = fs.readdirSync(plansDir)
200
200
  .filter((f) => f.endsWith('.md'))
201
- .sort();
201
+ .sort(numericFileSort);
202
202
 
203
203
  // Build a map of outcome statuses
204
204
  const outcomeStatuses = new Map<string, DerivedTaskStatus>();
205
205
  if (fs.existsSync(outcomesDir)) {
206
206
  const outcomeFiles = fs.readdirSync(outcomesDir)
207
207
  .filter((f) => f.endsWith('.md'))
208
- .sort();
208
+ .sort(numericFileSort);
209
209
 
210
210
  for (const outcomeFile of outcomeFiles) {
211
211
  const match = outcomeFile.match(new RegExp(`^(${TASK_ID_PATTERN})-`));