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
@@ -1,3 +1,4 @@
1
+ import { jest } from '@jest/globals';
1
2
  import * as fs from 'node:fs';
2
3
  import * as path from 'node:path';
3
4
  import * as os from 'node:os';
@@ -13,7 +14,25 @@ import {
13
14
  type DerivedTask,
14
15
  } from '../../src/core/state-derivation.js';
15
16
  import { getOutcomeFilePath, extractTaskNameFromPlanFile } from '../../src/utils/paths.js';
16
- import { getExecutionPrompt } from '../../src/prompts/execution.js';
17
+
18
+ const suiteHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-dependency-home-'));
19
+ let mockHomeDir = suiteHomeDir;
20
+
21
+ jest.unstable_mockModule('node:os', () => ({
22
+ homedir: () => mockHomeDir,
23
+ tmpdir: () => os.tmpdir(),
24
+ }));
25
+
26
+ jest.unstable_mockModule('../../src/utils/config.js', () => ({
27
+ getCommitFormat: () => '{prefix}[{projectName}:{taskId}] {description}',
28
+ getCommitPrefix: () => 'RAF',
29
+ renderCommitMessage: (template: string, variables: Record<string, string>) =>
30
+ template.replace(/\{(\w+)\}/g, (match, key: string) => variables[key] ?? match),
31
+ resetConfigCache: jest.fn(),
32
+ }));
33
+
34
+ const { getExecutionPrompt } = await import('../../src/prompts/execution.js');
35
+ const { resetConfigCache } = await import('../../src/utils/config.js');
17
36
 
18
37
  /**
19
38
  * Integration tests for the complete dependency blocking flow.
@@ -25,6 +44,8 @@ describe('Dependency Integration Flow', () => {
25
44
  let projectPath: string;
26
45
 
27
46
  beforeEach(() => {
47
+ fs.rmSync(path.join(mockHomeDir, '.raf'), { recursive: true, force: true });
48
+ resetConfigCache();
28
49
  tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-dep-integration-'));
29
50
  projectPath = path.join(tempDir, '1-test-project');
30
51
  fs.mkdirSync(projectPath, { recursive: true });
@@ -34,9 +55,14 @@ describe('Dependency Integration Flow', () => {
34
55
  });
35
56
 
36
57
  afterEach(() => {
58
+ resetConfigCache();
37
59
  fs.rmSync(tempDir, { recursive: true, force: true });
38
60
  });
39
61
 
62
+ afterAll(() => {
63
+ fs.rmSync(suiteHomeDir, { recursive: true, force: true });
64
+ });
65
+
40
66
  describe('Complete dependency chain simulation', () => {
41
67
  /**
42
68
  * Simulates a real project with the following dependency structure:
@@ -0,0 +1,35 @@
1
+ import { formatResolvedTaskModel } from '../../src/commands/do.js';
2
+ import type { ModelEntry } from '../../src/types/config.js';
3
+
4
+ describe('do model display', () => {
5
+ it('should include effort and fast metadata in verbose model logs', () => {
6
+ const entry: ModelEntry = {
7
+ model: 'sonnet',
8
+ provider: 'claude',
9
+ reasoningEffort: 'low',
10
+ fast: true,
11
+ };
12
+
13
+ expect(formatResolvedTaskModel(entry)).toBe('claude-sonnet-4-5-20250929, low, fast');
14
+ });
15
+
16
+ it('should omit effort when unavailable', () => {
17
+ const entry: ModelEntry = {
18
+ model: 'sonnet',
19
+ provider: 'claude',
20
+ };
21
+
22
+ expect(formatResolvedTaskModel(entry)).toBe('claude-sonnet-4-5-20250929');
23
+ });
24
+
25
+ it('should omit fast when false', () => {
26
+ const entry: ModelEntry = {
27
+ model: 'sonnet',
28
+ provider: 'claude',
29
+ reasoningEffort: 'low',
30
+ fast: false,
31
+ };
32
+
33
+ expect(formatResolvedTaskModel(entry)).toBe('claude-sonnet-4-5-20250929, low');
34
+ });
35
+ });
@@ -1,6 +1,34 @@
1
- import { getExecutionPrompt, ExecutionPromptParams, summarizeOutcome } from '../../src/prompts/execution.js';
1
+ import { jest } from '@jest/globals';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import type { ExecutionPromptParams } from '../../src/prompts/execution.js';
6
+
7
+ const suiteHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'raf-execution-prompt-home-'));
8
+ let mockHomeDir = suiteHomeDir;
9
+
10
+ jest.unstable_mockModule('node:os', () => ({
11
+ homedir: () => mockHomeDir,
12
+ tmpdir: () => os.tmpdir(),
13
+ }));
14
+
15
+ const { getExecutionPrompt, summarizeOutcome } = await import('../../src/prompts/execution.js');
16
+ const { resetConfigCache } = await import('../../src/utils/config.js');
2
17
 
3
18
  describe('Execution Prompt', () => {
19
+ beforeEach(() => {
20
+ fs.rmSync(path.join(mockHomeDir, '.raf'), { recursive: true, force: true });
21
+ resetConfigCache();
22
+ });
23
+
24
+ afterEach(() => {
25
+ resetConfigCache();
26
+ });
27
+
28
+ afterAll(() => {
29
+ fs.rmSync(suiteHomeDir, { recursive: true, force: true });
30
+ });
31
+
4
32
  const baseParams: ExecutionPromptParams = {
5
33
  projectPath: '/Users/test/RAF/42-task-naming-improvements',
6
34
  planPath: '/Users/test/RAF/42-task-naming-improvements/plans/1-enhance-identifier-resolution.md',
@@ -16,7 +44,7 @@ describe('Execution Prompt', () => {
16
44
  describe('Commit Message Format', () => {
17
45
  it('should include RAF commit schema format with description placeholder in prompt', () => {
18
46
  const prompt = getExecutionPrompt(baseParams);
19
- expect(prompt).toContain('RAF[42:1] <description>');
47
+ expect(prompt).toContain('RAF[task-naming-improvements:1] <description>');
20
48
  });
21
49
 
22
50
  it('should instruct to write meaningful description', () => {
@@ -28,25 +56,25 @@ describe('Execution Prompt', () => {
28
56
  it('should use plain numeric task IDs', () => {
29
57
  const params = { ...baseParams, taskNumber: 1 };
30
58
  const prompt = getExecutionPrompt(params);
31
- expect(prompt).toContain('RAF[42:1]');
59
+ expect(prompt).toContain('RAF[task-naming-improvements:1]');
32
60
  });
33
61
 
34
62
  it('should use numeric task IDs for double digits', () => {
35
63
  const params = { ...baseParams, taskNumber: 12 };
36
64
  const prompt = getExecutionPrompt(params);
37
- expect(prompt).toContain('RAF[42:12]');
65
+ expect(prompt).toContain('RAF[task-naming-improvements:12]');
38
66
  });
39
67
 
40
68
  it('should use numeric task IDs for large numbers', () => {
41
69
  const params = { ...baseParams, taskNumber: 123 };
42
70
  const prompt = getExecutionPrompt(params);
43
- expect(prompt).toContain('RAF[42:123]');
71
+ expect(prompt).toContain('RAF[task-naming-improvements:123]');
44
72
  });
45
73
 
46
- it('should include project number from numeric prefix', () => {
47
- const params = { ...baseParams, projectNumber: '7' };
74
+ it('should use project name from folder in commit format', () => {
75
+ const params = { ...baseParams, projectPath: '/Users/test/RAF/7-auth-fix', projectNumber: '7' };
48
76
  const prompt = getExecutionPrompt(params);
49
- expect(prompt).toContain('RAF[7:1]');
77
+ expect(prompt).toContain('RAF[auth-fix:1]');
50
78
  });
51
79
 
52
80
  it('should not include commit instructions when autoCommit is false', () => {
@@ -67,7 +95,9 @@ describe('Execution Prompt', () => {
67
95
  const params = { ...baseParams, autoCommit: false };
68
96
  const prompt = getExecutionPrompt(params);
69
97
  expect(prompt).not.toContain('On Failure');
70
- expect(prompt).not.toContain('do NOT commit');
98
+ // The general outcome section still mentions not committing on failure,
99
+ // but the git-specific instructions block should be absent
100
+ expect(prompt).not.toContain('Git Instructions');
71
101
  });
72
102
 
73
103
  it('should instruct not to add Co-Authored-By or other trailers', () => {
@@ -96,7 +126,7 @@ describe('Execution Prompt', () => {
96
126
  outcomeFilePath: '/Users/test/RAF/42-task-naming-improvements/outcomes/6-update-execution-prompt.md',
97
127
  };
98
128
  const prompt = getExecutionPrompt(params);
99
- expect(prompt).toContain('RAF[42:6] <description>');
129
+ expect(prompt).toContain('RAF[task-naming-improvements:6] <description>');
100
130
  });
101
131
 
102
132
  it('should generate correct commit message format for first task', () => {
@@ -112,7 +142,7 @@ describe('Execution Prompt', () => {
112
142
  outcomeFilePath: '/Users/test/RAF/1-fix-bug/outcomes/1-identify-issue.md',
113
143
  };
114
144
  const prompt = getExecutionPrompt(params);
115
- expect(prompt).toContain('RAF[1:1] <description>');
145
+ expect(prompt).toContain('RAF[fix-bug:1] <description>');
116
146
  });
117
147
 
118
148
  it('should generate correct commit message format for numeric project', () => {
@@ -128,7 +158,7 @@ describe('Execution Prompt', () => {
128
158
  outcomeFilePath: '/Users/test/RAF/7-feature-branch/outcomes/2-implement-feature.md',
129
159
  };
130
160
  const prompt = getExecutionPrompt(params);
131
- expect(prompt).toContain('RAF[7:2] <description>');
161
+ expect(prompt).toContain('RAF[feature-branch:2] <description>');
132
162
  });
133
163
  });
134
164
 
@@ -163,7 +193,7 @@ describe('Execution Prompt', () => {
163
193
 
164
194
  it('should include instructions for writing outcome file', () => {
165
195
  const prompt = getExecutionPrompt(baseParams);
166
- expect(prompt).toContain('You MUST write an outcome file');
196
+ expect(prompt).toContain('Outcome file path');
167
197
  expect(prompt).toContain('summary of what was done');
168
198
  });
169
199
 
@@ -176,25 +206,25 @@ describe('Execution Prompt', () => {
176
206
 
177
207
  it('should distinguish between code tasks and documentation tasks', () => {
178
208
  const prompt = getExecutionPrompt(baseParams);
179
- expect(prompt).toContain('For code tasks');
180
- expect(prompt).toContain('For documentation/report tasks');
209
+ expect(prompt).toContain('documentation/report tasks');
181
210
  });
182
211
 
183
212
  it('should instruct that marker is last line in outcome file', () => {
184
213
  const prompt = getExecutionPrompt(baseParams);
185
- expect(prompt).toContain('completion marker MUST be the LAST line in the outcome file');
214
+ expect(prompt).toContain('completion marker as the LAST line');
186
215
  });
187
216
  });
188
217
 
189
218
  describe('Commit Workflow Rules', () => {
190
- it('should include rule to commit code and outcome together on success', () => {
219
+ it('should include commit instructions when autoCommit is true', () => {
191
220
  const prompt = getExecutionPrompt(baseParams);
192
- expect(prompt).toContain('On SUCCESS: Commit code changes AND outcome file together');
221
+ expect(prompt).toContain('Git Instructions');
222
+ expect(prompt).toContain('git add');
193
223
  });
194
224
 
195
225
  it('should include rule not to commit on failure', () => {
196
226
  const prompt = getExecutionPrompt(baseParams);
197
- expect(prompt).toContain('On FAILURE: Do NOT commit');
227
+ expect(prompt).toContain('do NOT commit');
198
228
  });
199
229
 
200
230
  it('should specify that changes are preserved for debugging on failure', () => {
@@ -1,5 +1,11 @@
1
1
  import { jest } from '@jest/globals';
2
2
  import { EventEmitter } from 'node:events';
3
+ import type { HarnessName } from '../../src/types/config.js';
4
+
5
+ let currentNameGenerationModel = {
6
+ model: 'sonnet',
7
+ harness: 'claude' as HarnessName,
8
+ };
3
9
 
4
10
  // Helper to create a mock spawn that returns a fake ChildProcess
5
11
  function createMockSpawn(stdoutData: string | null, exitCode: number = 0) {
@@ -28,6 +34,11 @@ jest.unstable_mockModule('node:child_process', () => ({
28
34
  execSync: jest.fn(), // keep available for transitive imports
29
35
  }));
30
36
 
37
+ jest.unstable_mockModule('../../src/utils/config.js', () => ({
38
+ getModel: jest.fn(() => currentNameGenerationModel),
39
+ isValidModelName: jest.fn(() => true),
40
+ }));
41
+
31
42
  // Import after mocking
32
43
  const { generateProjectName, generateProjectNames, sanitizeGeneratedName } =
33
44
  await import('../../src/utils/name-generator.js');
@@ -35,10 +46,14 @@ const { generateProjectName, generateProjectNames, sanitizeGeneratedName } =
35
46
  describe('Name Generator', () => {
36
47
  beforeEach(() => {
37
48
  jest.clearAllMocks();
49
+ currentNameGenerationModel = {
50
+ model: 'sonnet',
51
+ harness: 'claude',
52
+ };
38
53
  });
39
54
 
40
55
  describe('generateProjectName', () => {
41
- it('should return sanitized name from Claude response', async () => {
56
+ it('should return sanitized name from CLI response', async () => {
42
57
  mockSpawn.mockReturnValue(createMockSpawn('user-auth-system\n'));
43
58
 
44
59
  const result = await generateProjectName('Build a user authentication system');
@@ -46,8 +61,8 @@ describe('Name Generator', () => {
46
61
  expect(result).toBe('user-auth-system');
47
62
  expect(mockSpawn).toHaveBeenCalledTimes(1);
48
63
  expect(mockSpawn).toHaveBeenCalledWith(
49
- 'claude',
50
- expect.arrayContaining(['--model', 'haiku', '--no-session-persistence', '-p']),
64
+ currentNameGenerationModel.harness,
65
+ expect.arrayContaining(['--model', currentNameGenerationModel.model, '--no-session-persistence', '-p']),
51
66
  expect.any(Object)
52
67
  );
53
68
  });
@@ -77,7 +92,7 @@ describe('Name Generator', () => {
77
92
  expect(result).toBe('some-project-name');
78
93
  });
79
94
 
80
- it('should fall back to word extraction when Claude fails', async () => {
95
+ it('should fall back to word extraction when CLI fails', async () => {
81
96
  mockSpawn.mockReturnValue(createMockSpawn(null, 1));
82
97
 
83
98
  const result = await generateProjectName('Build a user authentication system with OAuth');
@@ -85,7 +100,7 @@ describe('Name Generator', () => {
85
100
  expect(result).toBe('build-user-authentication');
86
101
  });
87
102
 
88
- it('should fall back to word extraction when Claude returns empty', async () => {
103
+ it('should fall back to word extraction when CLI returns empty', async () => {
89
104
  mockSpawn.mockReturnValue(createMockSpawn(''));
90
105
 
91
106
  const result = await generateProjectName('Implement caching layer for database');
@@ -93,7 +108,7 @@ describe('Name Generator', () => {
93
108
  expect(result).toBe('implement-caching-layer');
94
109
  });
95
110
 
96
- it('should fall back to word extraction when Claude returns single char', async () => {
111
+ it('should fall back to word extraction when CLI returns single char', async () => {
97
112
  mockSpawn.mockReturnValue(createMockSpawn('a'));
98
113
 
99
114
  const result = await generateProjectName('Add new logging functionality');
@@ -109,7 +124,7 @@ describe('Name Generator', () => {
109
124
  expect(result).toBe('project');
110
125
  });
111
126
 
112
- it('should truncate long names from Claude', async () => {
127
+ it('should truncate long names from CLI', async () => {
113
128
  const longName =
114
129
  'this-is-a-very-long-project-name-that-exceeds-the-maximum-allowed-length-for-folder-names';
115
130
  mockSpawn.mockReturnValue(createMockSpawn(longName));
@@ -153,7 +168,7 @@ describe('Name Generator', () => {
153
168
  });
154
169
 
155
170
  describe('generateProjectNames', () => {
156
- it('should return multiple sanitized names from Claude response', async () => {
171
+ it('should return multiple sanitized names from CLI response', async () => {
157
172
  mockSpawn.mockReturnValue(
158
173
  createMockSpawn('phoenix-rise\nturbo-boost\nbug-squasher\ncatalyst\nmerlin\n')
159
174
  );
@@ -227,7 +242,7 @@ describe('Name Generator', () => {
227
242
  expect(result.length).toBe(3);
228
243
  });
229
244
 
230
- it('should fall back to single name when Claude fails', async () => {
245
+ it('should fall back to single name when CLI fails', async () => {
231
246
  mockSpawn.mockReturnValue(createMockSpawn(null, 1));
232
247
 
233
248
  const result = await generateProjectNames('Build a user authentication system with OAuth');
@@ -235,13 +250,12 @@ describe('Name Generator', () => {
235
250
  expect(result).toEqual(['build-user-authentication']);
236
251
  });
237
252
 
238
- it('should fall back to single name when too few names returned', async () => {
253
+ it('should keep 1-2 valid names instead of falling back', async () => {
239
254
  mockSpawn.mockReturnValue(createMockSpawn('phoenix\nturbo\n'));
240
255
 
241
256
  const result = await generateProjectNames('Build something awesome');
242
257
 
243
- // Only 2 names returned, so fallback kicks in
244
- expect(result).toEqual(['build-something-awesome']);
258
+ expect(result).toEqual(['phoenix', 'turbo']);
245
259
  });
246
260
 
247
261
  it('should filter out invalid/short names', async () => {
@@ -271,6 +285,62 @@ describe('Name Generator', () => {
271
285
 
272
286
  expect(result).toEqual(['some-project']);
273
287
  });
288
+
289
+ it('should fall back when codex returns no usable suggestions after sanitization', async () => {
290
+ currentNameGenerationModel = {
291
+ model: 'gpt-5.4',
292
+ harness: 'codex',
293
+ };
294
+ mockSpawn.mockReturnValue(createMockSpawn('a\n!\n'));
295
+
296
+ const result = await generateProjectNames('Build something awesome');
297
+
298
+ expect(result).toEqual(['build-something-awesome']);
299
+ });
300
+
301
+ it('should no longer accept a harness argument (config-driven)', async () => {
302
+ // The harness is embedded in the config, not passed as a parameter
303
+ // This test verifies that generateProjectNames has a single-parameter signature
304
+ mockSpawn.mockReturnValue(
305
+ createMockSpawn('phoenix\nturbo-boost\ncatalyst\n')
306
+ );
307
+
308
+ const result = await generateProjectNames('Build something');
309
+
310
+ expect(result).toEqual(['phoenix', 'turbo-boost', 'catalyst']);
311
+ expect(mockSpawn).toHaveBeenCalledWith(
312
+ currentNameGenerationModel.harness,
313
+ expect.arrayContaining(['--model', currentNameGenerationModel.model]),
314
+ expect.any(Object)
315
+ );
316
+ });
317
+
318
+ it('should use codex exec invocation for name generation when configured', async () => {
319
+ currentNameGenerationModel = {
320
+ model: 'gpt-5.4',
321
+ harness: 'codex',
322
+ };
323
+ mockSpawn.mockReturnValue(createMockSpawn('phoenix-rise\nturbo-boost\nbug-squasher\n'));
324
+
325
+ const result = await generateProjectNames('Build something');
326
+
327
+ expect(result).toEqual(['phoenix-rise', 'turbo-boost', 'bug-squasher']);
328
+ expect(mockSpawn).toHaveBeenCalledWith(
329
+ 'codex',
330
+ expect.arrayContaining([
331
+ 'exec',
332
+ '--skip-git-repo-check',
333
+ '--ephemeral',
334
+ '--color',
335
+ 'never',
336
+ '-m',
337
+ 'gpt-5.4',
338
+ ]),
339
+ expect.any(Object)
340
+ );
341
+ const promptArg = (mockSpawn.mock.calls[0][1] as string[]).at(-1);
342
+ expect(promptArg).toContain('Output EXACTLY 5 project names');
343
+ });
274
344
  });
275
345
 
276
346
  describe('sanitizeGeneratedName', () => {
@@ -52,26 +52,23 @@ describe('Plan Command - Auto Flag', () => {
52
52
  expect(amendOption).toBeDefined();
53
53
  });
54
54
 
55
- it('should allow --auto with --model', () => {
55
+ it('should not have --provider flag (removed)', () => {
56
56
  const command = createPlanCommand();
57
57
  const options = command.options;
58
58
 
59
- const autoOption = options.find((opt) => opt.long === '--auto');
60
- const modelOption = options.find((opt) => opt.long === '--model');
61
-
62
- expect(autoOption).toBeDefined();
63
- expect(modelOption).toBeDefined();
59
+ const providerOption = options.find((opt) => opt.long === '--provider');
60
+ expect(providerOption).toBeUndefined();
64
61
  });
65
62
 
66
- it('should allow --auto with --sonnet', () => {
63
+ it('should not have removed --model or --sonnet flags', () => {
67
64
  const command = createPlanCommand();
68
65
  const options = command.options;
69
66
 
70
- const autoOption = options.find((opt) => opt.long === '--auto');
67
+ const modelOption = options.find((opt) => opt.long === '--model');
71
68
  const sonnetOption = options.find((opt) => opt.long === '--sonnet');
72
69
 
73
- expect(autoOption).toBeDefined();
74
- expect(sonnetOption).toBeDefined();
70
+ expect(modelOption).toBeUndefined();
71
+ expect(sonnetOption).toBeUndefined();
75
72
  });
76
73
  });
77
74
 
@@ -344,10 +344,10 @@ describe('Plan Command - Amend Functionality', () => {
344
344
 
345
345
  const { systemPrompt } = getAmendPrompt(params);
346
346
 
347
- expect(systemPrompt).toContain('AMENDMENT MODE');
347
+ expect(systemPrompt).toContain('Amendment Mode');
348
348
  expect(systemPrompt).toContain('Task 1: first [COMPLETED] [PROTECTED]');
349
349
  expect(systemPrompt).toContain('Task 2: second [PENDING] [MODIFIABLE]');
350
- expect(systemPrompt).toContain('starting from number 3');
350
+ expect(systemPrompt).toContain('start from number 3');
351
351
  expect(systemPrompt).toContain('/test/project');
352
352
  });
353
353
 
@@ -377,11 +377,9 @@ describe('Plan Command - Amend Functionality', () => {
377
377
 
378
378
  const { systemPrompt } = getAmendPrompt(params);
379
379
 
380
- expect(systemPrompt).toContain('NEVER modify [PROTECTED] tasks (completed)');
381
- expect(systemPrompt).toContain('DO NOT renumber existing tasks');
382
- expect(systemPrompt).toContain('You MAY modify [MODIFIABLE] tasks (pending/failed)');
383
- expect(systemPrompt).toContain('NEVER modify COMPLETED task plans - they are [PROTECTED]');
384
- expect(systemPrompt).toContain('You MAY modify non-completed task plans (pending/failed)');
380
+ expect(systemPrompt).toContain('[PROTECTED] tasks (completed): NEVER modify');
381
+ expect(systemPrompt).toContain('Do NOT renumber existing tasks');
382
+ expect(systemPrompt).toContain('[MODIFIABLE] tasks (pending/failed): MAY modify');
385
383
  });
386
384
 
387
385
  it('should include failed task status with MODIFIABLE indicator in system prompt', () => {
@@ -413,7 +411,7 @@ describe('Plan Command - Amend Functionality', () => {
413
411
 
414
412
  const { systemPrompt } = getAmendPrompt(params);
415
413
 
416
- expect(systemPrompt).toContain('### Protected Tasks (COMPLETED - cannot be modified)');
414
+ expect(systemPrompt).toContain('### Protected (COMPLETED)');
417
415
  expect(systemPrompt).toContain('- Task 1: first');
418
416
  expect(systemPrompt).toContain('- Task 2: second');
419
417
  });
@@ -432,7 +430,7 @@ describe('Plan Command - Amend Functionality', () => {
432
430
 
433
431
  const { systemPrompt } = getAmendPrompt(params);
434
432
 
435
- expect(systemPrompt).toContain('### Modifiable Tasks (PENDING/FAILED - can be modified if requested)');
433
+ expect(systemPrompt).toContain('### Modifiable (PENDING/FAILED)');
436
434
  expect(systemPrompt).toContain('- Task 2: second');
437
435
  expect(systemPrompt).toContain('- Task 3: third');
438
436
  });
@@ -449,7 +447,7 @@ describe('Plan Command - Amend Functionality', () => {
449
447
 
450
448
  const { systemPrompt } = getAmendPrompt(params);
451
449
 
452
- expect(systemPrompt).toContain('### Protected Tasks (COMPLETED - cannot be modified)\n(none)');
450
+ expect(systemPrompt).toContain('### Protected (COMPLETED)\n(none)');
453
451
  });
454
452
 
455
453
  it('should show (none) when there are no modifiable tasks', () => {
@@ -464,7 +462,7 @@ describe('Plan Command - Amend Functionality', () => {
464
462
 
465
463
  const { systemPrompt } = getAmendPrompt(params);
466
464
 
467
- expect(systemPrompt).toContain('### Modifiable Tasks (PENDING/FAILED - can be modified if requested)\n(none)');
465
+ expect(systemPrompt).toContain('### Modifiable (PENDING/FAILED)\n(none)');
468
466
  });
469
467
 
470
468
  it('should handle mixed task statuses correctly', () => {
@@ -489,8 +487,8 @@ describe('Plan Command - Amend Functionality', () => {
489
487
  expect(systemPrompt).toContain('Task 4: tests [PENDING] [MODIFIABLE]');
490
488
 
491
489
  // Check separate lists
492
- expect(systemPrompt).toContain('### Protected Tasks (COMPLETED - cannot be modified)');
493
- expect(systemPrompt).toContain('### Modifiable Tasks (PENDING/FAILED - can be modified if requested)');
490
+ expect(systemPrompt).toContain('### Protected (COMPLETED)');
491
+ expect(systemPrompt).toContain('### Modifiable (PENDING/FAILED)');
494
492
  });
495
493
 
496
494
  it('should include correct plans directory path', () => {
@@ -506,7 +504,6 @@ describe('Plan Command - Amend Functionality', () => {
506
504
  const { systemPrompt } = getAmendPrompt(params);
507
505
 
508
506
  expect(systemPrompt).toContain('/my/project/path/plans/2-task-name.md');
509
- expect(systemPrompt).toContain('/my/project/path/plans/3-task-name.md');
510
507
  });
511
508
 
512
509
  it('should include new task description in user message', () => {
@@ -532,7 +529,7 @@ describe('Plan Command - Amend Functionality', () => {
532
529
 
533
530
  const { systemPrompt } = getAmendPrompt(params);
534
531
 
535
- expect(systemPrompt).toContain('AMENDMENT MODE');
532
+ expect(systemPrompt).toContain('Amendment Mode');
536
533
  expect(systemPrompt).toContain('AskUserQuestion');
537
534
  expect(systemPrompt).toContain('Interview the User');
538
535
  });
@@ -597,9 +594,9 @@ describe('Plan Command - Amend Functionality', () => {
597
594
 
598
595
  const { systemPrompt } = getAmendPrompt(params);
599
596
 
600
- expect(systemPrompt).toContain('Identifying Follow-up Tasks');
601
597
  expect(systemPrompt).toContain('follow-up to task NN');
602
- expect(systemPrompt).toContain('outcome file paths for completed tasks are listed above');
598
+ expect(systemPrompt).toContain('follow-up');
599
+ expect(systemPrompt).toContain('outcome');
603
600
  });
604
601
  });
605
602
 
@@ -76,8 +76,8 @@ describe('Planning Prompt', () => {
76
76
 
77
77
  const { systemPrompt } = getPlanningPrompt(params);
78
78
 
79
- expect(systemPrompt).toContain('/test/project/plans/1-task-name.md');
80
- expect(systemPrompt).toContain('/test/project/plans/2-task-name.md');
79
+ expect(systemPrompt).toContain('/test/project/plans/');
80
+ expect(systemPrompt).toContain('1-task-name.md');
81
81
  });
82
82
 
83
83
  it('should include task guidelines in system prompt', () => {
@@ -88,7 +88,7 @@ describe('Planning Prompt', () => {
88
88
 
89
89
  const { systemPrompt } = getPlanningPrompt(params);
90
90
 
91
- expect(systemPrompt).toContain('identify distinct');
91
+ expect(systemPrompt).toContain('Identify distinct tasks');
92
92
  expect(systemPrompt).toContain('independently completable');
93
93
  expect(systemPrompt).toContain('10-30 minutes');
94
94
  });
@@ -116,9 +116,9 @@ describe('Planning Prompt', () => {
116
116
 
117
117
  const { systemPrompt } = getPlanningPrompt(params);
118
118
 
119
- expect(systemPrompt).toContain('ALWAYS interview the user before creating plans');
120
- expect(systemPrompt).toContain('numbered order (1, 2, 3');
121
- expect(systemPrompt).toContain('kebab-case names');
119
+ expect(systemPrompt).toContain('Do not skip this step');
120
+ expect(systemPrompt).toContain('execution order');
121
+ expect(systemPrompt).toContain('kebab-case');
122
122
  });
123
123
 
124
124
  it('should show raf do without --worktree when worktreeMode is false', () => {
@@ -146,7 +146,7 @@ describe('Planning Prompt', () => {
146
146
  expect(systemPrompt).not.toContain('--worktree');
147
147
  });
148
148
 
149
- it('should show raf do with --worktree when worktreeMode is true', () => {
149
+ it('should show raf do without --worktree when worktreeMode is true', () => {
150
150
  const params: PlanningPromptParams = {
151
151
  projectPath: '/test/project',
152
152
  inputContent: 'Some project',
@@ -155,7 +155,8 @@ describe('Planning Prompt', () => {
155
155
 
156
156
  const { systemPrompt } = getPlanningPrompt(params);
157
157
 
158
- expect(systemPrompt).toContain('raf do <project> --worktree');
158
+ expect(systemPrompt).toContain('raf do <project>');
159
+ expect(systemPrompt).not.toContain('--worktree');
159
160
  });
160
161
 
161
162
  it('should include project description in user message', () => {