anvil-dev-framework 0.1.6

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 (190) hide show
  1. package/README.md +719 -0
  2. package/VERSION +1 -0
  3. package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
  4. package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
  5. package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
  6. package/docs/INSTALLATION.md +984 -0
  7. package/docs/anvil-hud.md +469 -0
  8. package/docs/anvil-init.md +255 -0
  9. package/docs/anvil-state.md +210 -0
  10. package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
  11. package/docs/command-reference.md +2022 -0
  12. package/docs/hooks-tts.md +368 -0
  13. package/docs/implementation-guide.md +810 -0
  14. package/docs/linear-github-integration.md +247 -0
  15. package/docs/local-issues.md +677 -0
  16. package/docs/patterns/README.md +419 -0
  17. package/docs/planning-responsibilities.md +139 -0
  18. package/docs/session-workflow.md +573 -0
  19. package/docs/simplification-plan-template.md +297 -0
  20. package/docs/simplification-principles.md +129 -0
  21. package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
  22. package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
  23. package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
  24. package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
  25. package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
  26. package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
  27. package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
  28. package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
  29. package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
  30. package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
  31. package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
  32. package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
  33. package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
  34. package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
  35. package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
  36. package/docs/sync.md +122 -0
  37. package/global/CLAUDE.md +140 -0
  38. package/global/agents/verify-app.md +164 -0
  39. package/global/commands/anvil-settings.md +527 -0
  40. package/global/commands/anvil-sync.md +121 -0
  41. package/global/commands/change.md +197 -0
  42. package/global/commands/clarify.md +252 -0
  43. package/global/commands/cleanup.md +292 -0
  44. package/global/commands/commit-push-pr.md +207 -0
  45. package/global/commands/decay-review.md +127 -0
  46. package/global/commands/discover.md +158 -0
  47. package/global/commands/doc-coverage.md +122 -0
  48. package/global/commands/evidence.md +307 -0
  49. package/global/commands/explore.md +121 -0
  50. package/global/commands/force-exit.md +135 -0
  51. package/global/commands/handoff.md +191 -0
  52. package/global/commands/healthcheck.md +302 -0
  53. package/global/commands/hud.md +84 -0
  54. package/global/commands/insights.md +319 -0
  55. package/global/commands/linear-setup.md +184 -0
  56. package/global/commands/lint-fix.md +198 -0
  57. package/global/commands/orient.md +510 -0
  58. package/global/commands/plan.md +228 -0
  59. package/global/commands/ralph.md +346 -0
  60. package/global/commands/ready.md +182 -0
  61. package/global/commands/release.md +305 -0
  62. package/global/commands/retro.md +96 -0
  63. package/global/commands/shard.md +166 -0
  64. package/global/commands/spec.md +227 -0
  65. package/global/commands/sprint.md +184 -0
  66. package/global/commands/tasks.md +228 -0
  67. package/global/commands/test-and-commit.md +151 -0
  68. package/global/commands/validate.md +132 -0
  69. package/global/commands/verify.md +251 -0
  70. package/global/commands/weekly-review.md +156 -0
  71. package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
  72. package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
  73. package/global/hooks/anvil_memory_observe.ts +322 -0
  74. package/global/hooks/anvil_memory_session.ts +166 -0
  75. package/global/hooks/anvil_memory_stop.ts +187 -0
  76. package/global/hooks/parse_transcript.py +116 -0
  77. package/global/hooks/post_merge_cleanup.sh +132 -0
  78. package/global/hooks/post_tool_format.sh +215 -0
  79. package/global/hooks/ralph_context_monitor.py +240 -0
  80. package/global/hooks/ralph_stop.sh +502 -0
  81. package/global/hooks/statusline.sh +1110 -0
  82. package/global/hooks/statusline_agent_sync.py +224 -0
  83. package/global/hooks/stop_gate.sh +250 -0
  84. package/global/lib/.claude/anvil-state.json +21 -0
  85. package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
  86. package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
  87. package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
  88. package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
  89. package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
  90. package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
  91. package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
  92. package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
  93. package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
  94. package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
  95. package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
  96. package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
  97. package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
  98. package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
  99. package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
  100. package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
  101. package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
  102. package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
  103. package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
  104. package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
  105. package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
  106. package/global/lib/agent_registry.py +995 -0
  107. package/global/lib/anvil-state.sh +435 -0
  108. package/global/lib/claim_service.py +515 -0
  109. package/global/lib/coderabbit_service.py +314 -0
  110. package/global/lib/config_service.py +423 -0
  111. package/global/lib/coordination_service.py +331 -0
  112. package/global/lib/doc_coverage_service.py +1305 -0
  113. package/global/lib/gate_logger.py +316 -0
  114. package/global/lib/github_service.py +310 -0
  115. package/global/lib/handoff_generator.py +775 -0
  116. package/global/lib/hygiene_service.py +712 -0
  117. package/global/lib/issue_models.py +257 -0
  118. package/global/lib/issue_provider.py +339 -0
  119. package/global/lib/linear_data_service.py +210 -0
  120. package/global/lib/linear_provider.py +987 -0
  121. package/global/lib/linear_provider.py.backup +671 -0
  122. package/global/lib/local_provider.py +486 -0
  123. package/global/lib/orient_fast.py +457 -0
  124. package/global/lib/quality_service.py +470 -0
  125. package/global/lib/ralph_prompt_generator.py +563 -0
  126. package/global/lib/ralph_state.py +1202 -0
  127. package/global/lib/state_manager.py +417 -0
  128. package/global/lib/transcript_parser.py +597 -0
  129. package/global/lib/verification_runner.py +557 -0
  130. package/global/lib/verify_iteration.py +490 -0
  131. package/global/lib/verify_subagent.py +250 -0
  132. package/global/skills/README.md +155 -0
  133. package/global/skills/quality-gates/SKILL.md +252 -0
  134. package/global/skills/skill-template/SKILL.md +109 -0
  135. package/global/skills/testing-strategies/SKILL.md +337 -0
  136. package/global/templates/CHANGE-template.md +105 -0
  137. package/global/templates/HANDOFF-template.md +63 -0
  138. package/global/templates/PLAN-template.md +111 -0
  139. package/global/templates/SPEC-template.md +93 -0
  140. package/global/templates/ralph/PROMPT.md.template +89 -0
  141. package/global/templates/ralph/fix_plan.md.template +31 -0
  142. package/global/templates/ralph/progress.txt.template +23 -0
  143. package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
  144. package/global/tests/test_doc_coverage.py +520 -0
  145. package/global/tests/test_issue_models.py +299 -0
  146. package/global/tests/test_local_provider.py +323 -0
  147. package/global/tools/README.md +178 -0
  148. package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
  149. package/global/tools/anvil-hud.py +3622 -0
  150. package/global/tools/anvil-hud.py.bak +3318 -0
  151. package/global/tools/anvil-issue.py +432 -0
  152. package/global/tools/anvil-memory/CLAUDE.md +49 -0
  153. package/global/tools/anvil-memory/README.md +42 -0
  154. package/global/tools/anvil-memory/bun.lock +25 -0
  155. package/global/tools/anvil-memory/bunfig.toml +9 -0
  156. package/global/tools/anvil-memory/package.json +23 -0
  157. package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
  158. package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
  159. package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
  160. package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
  161. package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
  162. package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
  163. package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
  164. package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
  165. package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
  166. package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
  167. package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
  168. package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
  169. package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
  170. package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
  171. package/global/tools/anvil-memory/src/commands/get.ts +115 -0
  172. package/global/tools/anvil-memory/src/commands/init.ts +94 -0
  173. package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
  174. package/global/tools/anvil-memory/src/commands/search.ts +112 -0
  175. package/global/tools/anvil-memory/src/db.ts +638 -0
  176. package/global/tools/anvil-memory/src/index.ts +205 -0
  177. package/global/tools/anvil-memory/src/types.ts +122 -0
  178. package/global/tools/anvil-memory/tsconfig.json +29 -0
  179. package/global/tools/ralph-loop.sh +359 -0
  180. package/package.json +45 -0
  181. package/scripts/anvil +822 -0
  182. package/scripts/extract_patterns.py +222 -0
  183. package/scripts/init-project.sh +541 -0
  184. package/scripts/install.sh +229 -0
  185. package/scripts/postinstall.js +41 -0
  186. package/scripts/rollback.sh +188 -0
  187. package/scripts/sync.sh +623 -0
  188. package/scripts/test-statusline.sh +248 -0
  189. package/scripts/update_claude_md.py +224 -0
  190. package/scripts/verify.sh +255 -0
@@ -0,0 +1,571 @@
1
+ /**
2
+ * Prompt Generator Tests
3
+ *
4
+ * Tests for ralph_prompt_generator.py resume template generation.
5
+ * Verifies PROMPT.md creation for Ralph and Manual resume modes.
6
+ */
7
+
8
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
9
+ import { existsSync } from 'fs';
10
+ import {
11
+ TestStateManager,
12
+ runPythonHook,
13
+ isPythonAvailable,
14
+ } from './test-utils';
15
+ import {
16
+ createPromptGeneratorState,
17
+ createCheckpointedState,
18
+ createMinimalRalphState,
19
+ SAMPLE_HANDOFF,
20
+ } from './fixtures';
21
+
22
+ const PROMPT_GENERATOR_PATH = '/Users/alexandercahiz/Projects/anvil-dev-framework/global/lib/ralph_prompt_generator.py';
23
+
24
+ describe('Prompt Generator', () => {
25
+ let stateManager: TestStateManager;
26
+ let pythonAvailable: boolean;
27
+
28
+ beforeEach(async () => {
29
+ stateManager = new TestStateManager('prompt-generator');
30
+ stateManager.init();
31
+ pythonAvailable = await isPythonAvailable();
32
+ });
33
+
34
+ afterEach(() => {
35
+ stateManager.cleanup();
36
+ });
37
+
38
+ describe('State File Mode', () => {
39
+ test('generates resume prompt from state file', async () => {
40
+ if (!pythonAvailable) {
41
+ console.log('Skipping: Python not available');
42
+ return;
43
+ }
44
+
45
+ const state = createPromptGeneratorState();
46
+ stateManager.writeState(state);
47
+
48
+ const result = await runPythonHook(
49
+ PROMPT_GENERATOR_PATH,
50
+ {},
51
+ {
52
+ cwd: stateManager.baseDir,
53
+ args: ['--state', stateManager.stateFile, '--dry-run'],
54
+ }
55
+ );
56
+
57
+ expect(result.exitCode).toBe(0);
58
+ expect(result.stdout).toContain('Session Resume');
59
+ expect(result.stdout).toContain('L2');
60
+ expect(result.stdout).toContain('Implement Feature X');
61
+ });
62
+
63
+ test('includes checkpoint header with level and percentage', async () => {
64
+ if (!pythonAvailable) {
65
+ console.log('Skipping: Python not available');
66
+ return;
67
+ }
68
+
69
+ const state = createCheckpointedState('L3', 96);
70
+ stateManager.writeState(state);
71
+
72
+ const result = await runPythonHook(
73
+ PROMPT_GENERATOR_PATH,
74
+ {},
75
+ {
76
+ cwd: stateManager.baseDir,
77
+ args: ['--state', stateManager.stateFile, '--dry-run'],
78
+ }
79
+ );
80
+
81
+ expect(result.exitCode).toBe(0);
82
+ expect(result.stdout).toContain('L3');
83
+ expect(result.stdout).toContain('96%');
84
+ });
85
+
86
+ test('includes session summary with progress', async () => {
87
+ if (!pythonAvailable) {
88
+ console.log('Skipping: Python not available');
89
+ return;
90
+ }
91
+
92
+ const state = createPromptGeneratorState({
93
+ task_name: 'Test Task',
94
+ objective: 'Test objective description',
95
+ iteration: 20,
96
+ completed_items: ['Item 1', 'Item 2', 'Item 3'],
97
+ todo_items: ['Item 4', 'Item 5'],
98
+ });
99
+ stateManager.writeState(state);
100
+
101
+ const result = await runPythonHook(
102
+ PROMPT_GENERATOR_PATH,
103
+ {},
104
+ {
105
+ cwd: stateManager.baseDir,
106
+ args: ['--state', stateManager.stateFile, '--dry-run'],
107
+ }
108
+ );
109
+
110
+ expect(result.exitCode).toBe(0);
111
+ expect(result.stdout).toContain('Test Task');
112
+ expect(result.stdout).toContain('Test objective description');
113
+ expect(result.stdout).toContain('Iteration');
114
+ expect(result.stdout).toContain('20');
115
+ });
116
+
117
+ test('includes files in progress', async () => {
118
+ if (!pythonAvailable) {
119
+ console.log('Skipping: Python not available');
120
+ return;
121
+ }
122
+
123
+ const state = createPromptGeneratorState();
124
+ stateManager.writeState(state);
125
+
126
+ const result = await runPythonHook(
127
+ PROMPT_GENERATOR_PATH,
128
+ {},
129
+ {
130
+ cwd: stateManager.baseDir,
131
+ args: ['--state', stateManager.stateFile, '--dry-run'],
132
+ }
133
+ );
134
+
135
+ expect(result.exitCode).toBe(0);
136
+ expect(result.stdout).toContain('src/feature.ts');
137
+ expect(result.stdout).toContain('Files In Progress');
138
+ });
139
+
140
+ test('includes remaining todo items', async () => {
141
+ if (!pythonAvailable) {
142
+ console.log('Skipping: Python not available');
143
+ return;
144
+ }
145
+
146
+ const state = createPromptGeneratorState({
147
+ todo_items: ['Remaining task 1', 'Remaining task 2', 'Remaining task 3'],
148
+ });
149
+ stateManager.writeState(state);
150
+
151
+ const result = await runPythonHook(
152
+ PROMPT_GENERATOR_PATH,
153
+ {},
154
+ {
155
+ cwd: stateManager.baseDir,
156
+ args: ['--state', stateManager.stateFile, '--dry-run'],
157
+ }
158
+ );
159
+
160
+ expect(result.exitCode).toBe(0);
161
+ expect(result.stdout).toContain('Remaining task 1');
162
+ expect(result.stdout).toContain('Remaining Work');
163
+ });
164
+
165
+ test('handles missing state file gracefully', async () => {
166
+ if (!pythonAvailable) {
167
+ console.log('Skipping: Python not available');
168
+ return;
169
+ }
170
+
171
+ // Don't create state file
172
+ const result = await runPythonHook(
173
+ PROMPT_GENERATOR_PATH,
174
+ {},
175
+ {
176
+ cwd: stateManager.baseDir,
177
+ args: ['--state', stateManager.stateFile, '--dry-run'],
178
+ }
179
+ );
180
+
181
+ expect(result.exitCode).toBe(1);
182
+ expect(result.stdout + result.stderr).toContain('not found');
183
+ });
184
+
185
+ test('handles minimal state gracefully', async () => {
186
+ if (!pythonAvailable) {
187
+ console.log('Skipping: Python not available');
188
+ return;
189
+ }
190
+
191
+ // Create state with minimal fields
192
+ stateManager.writeState(createMinimalRalphState());
193
+
194
+ const result = await runPythonHook(
195
+ PROMPT_GENERATOR_PATH,
196
+ {},
197
+ {
198
+ cwd: stateManager.baseDir,
199
+ args: ['--state', stateManager.stateFile, '--dry-run'],
200
+ }
201
+ );
202
+
203
+ // Should not crash, even with missing checkpoint data
204
+ expect(result.exitCode).toBe(0);
205
+ expect(result.stdout).toContain('Session Resume');
206
+ });
207
+ });
208
+
209
+ describe('Ralph vs Manual Mode', () => {
210
+ test('generates Ralph mode output by default', async () => {
211
+ if (!pythonAvailable) {
212
+ console.log('Skipping: Python not available');
213
+ return;
214
+ }
215
+
216
+ const state = createPromptGeneratorState();
217
+ stateManager.writeState(state);
218
+
219
+ const result = await runPythonHook(
220
+ PROMPT_GENERATOR_PATH,
221
+ {},
222
+ {
223
+ cwd: stateManager.baseDir,
224
+ args: ['--state', stateManager.stateFile, '--dry-run'],
225
+ }
226
+ );
227
+
228
+ expect(result.exitCode).toBe(0);
229
+ expect(result.stdout).toContain('Ralph Autonomous');
230
+ expect(result.stdout).toContain('<promise>COMPLETE</promise>');
231
+ });
232
+
233
+ test('generates Manual mode output when specified', async () => {
234
+ if (!pythonAvailable) {
235
+ console.log('Skipping: Python not available');
236
+ return;
237
+ }
238
+
239
+ const state = createPromptGeneratorState();
240
+ stateManager.writeState(state);
241
+
242
+ const result = await runPythonHook(
243
+ PROMPT_GENERATOR_PATH,
244
+ {},
245
+ {
246
+ cwd: stateManager.baseDir,
247
+ args: ['--state', stateManager.stateFile, '--mode', 'manual', '--dry-run'],
248
+ }
249
+ );
250
+
251
+ expect(result.exitCode).toBe(0);
252
+ expect(result.stdout).toContain('Manual');
253
+ expect(result.stdout).toContain('Manual Resume Checklist');
254
+ expect(result.stdout).not.toContain('<promise>COMPLETE</promise>');
255
+ });
256
+
257
+ test('Ralph mode includes loop guidelines', async () => {
258
+ if (!pythonAvailable) {
259
+ console.log('Skipping: Python not available');
260
+ return;
261
+ }
262
+
263
+ const state = createPromptGeneratorState();
264
+ stateManager.writeState(state);
265
+
266
+ const result = await runPythonHook(
267
+ PROMPT_GENERATOR_PATH,
268
+ {},
269
+ {
270
+ cwd: stateManager.baseDir,
271
+ args: ['--state', stateManager.stateFile, '--mode', 'ralph', '--dry-run'],
272
+ }
273
+ );
274
+
275
+ expect(result.exitCode).toBe(0);
276
+ expect(result.stdout).toContain('Ralph Mode Guidelines');
277
+ expect(result.stdout).toContain('git checkpoints');
278
+ });
279
+
280
+ test('Manual mode includes resume checklist', async () => {
281
+ if (!pythonAvailable) {
282
+ console.log('Skipping: Python not available');
283
+ return;
284
+ }
285
+
286
+ const state = createPromptGeneratorState();
287
+ stateManager.writeState(state);
288
+
289
+ const result = await runPythonHook(
290
+ PROMPT_GENERATOR_PATH,
291
+ {},
292
+ {
293
+ cwd: stateManager.baseDir,
294
+ args: ['--state', stateManager.stateFile, '--mode', 'manual', '--dry-run'],
295
+ }
296
+ );
297
+
298
+ expect(result.exitCode).toBe(0);
299
+ expect(result.stdout).toContain('git status');
300
+ expect(result.stdout).toContain('git diff');
301
+ });
302
+ });
303
+
304
+ describe('Handoff Mode', () => {
305
+ test('generates prompt from handoff document', async () => {
306
+ if (!pythonAvailable) {
307
+ console.log('Skipping: Python not available');
308
+ return;
309
+ }
310
+
311
+ const handoffPath = `${stateManager.baseDir}/.claude/handoffs/test-handoff.md`;
312
+ stateManager.writeFile('.claude/handoffs/test-handoff.md', SAMPLE_HANDOFF);
313
+
314
+ const result = await runPythonHook(
315
+ PROMPT_GENERATOR_PATH,
316
+ {},
317
+ {
318
+ cwd: stateManager.baseDir,
319
+ args: ['--handoff', handoffPath, '--dry-run'],
320
+ }
321
+ );
322
+
323
+ expect(result.exitCode).toBe(0);
324
+ expect(result.stdout).toContain('Session Resume');
325
+ expect(result.stdout).toContain('Session Handoff');
326
+ });
327
+
328
+ test('handles missing handoff file gracefully', async () => {
329
+ if (!pythonAvailable) {
330
+ console.log('Skipping: Python not available');
331
+ return;
332
+ }
333
+
334
+ const result = await runPythonHook(
335
+ PROMPT_GENERATOR_PATH,
336
+ {},
337
+ {
338
+ cwd: stateManager.baseDir,
339
+ args: ['--handoff', 'nonexistent.md', '--dry-run'],
340
+ }
341
+ );
342
+
343
+ expect(result.exitCode).toBe(1);
344
+ expect(result.stdout + result.stderr).toContain('not found');
345
+ });
346
+ });
347
+
348
+ describe('Output Options', () => {
349
+ test('--dry-run outputs to stdout', async () => {
350
+ if (!pythonAvailable) {
351
+ console.log('Skipping: Python not available');
352
+ return;
353
+ }
354
+
355
+ const state = createPromptGeneratorState();
356
+ stateManager.writeState(state);
357
+
358
+ const result = await runPythonHook(
359
+ PROMPT_GENERATOR_PATH,
360
+ {},
361
+ {
362
+ cwd: stateManager.baseDir,
363
+ args: ['--state', stateManager.stateFile, '--dry-run'],
364
+ }
365
+ );
366
+
367
+ expect(result.exitCode).toBe(0);
368
+ expect(result.stdout.length).toBeGreaterThan(100);
369
+ // Should not create PROMPT.md in dry-run mode
370
+ expect(existsSync(`${stateManager.baseDir}/PROMPT.md`)).toBe(false);
371
+ });
372
+
373
+ test('writes to PROMPT.md by default', async () => {
374
+ if (!pythonAvailable) {
375
+ console.log('Skipping: Python not available');
376
+ return;
377
+ }
378
+
379
+ const state = createPromptGeneratorState();
380
+ stateManager.writeState(state);
381
+
382
+ const result = await runPythonHook(
383
+ PROMPT_GENERATOR_PATH,
384
+ {},
385
+ {
386
+ cwd: stateManager.baseDir,
387
+ args: ['--state', stateManager.stateFile],
388
+ }
389
+ );
390
+
391
+ expect(result.exitCode).toBe(0);
392
+ expect(existsSync(`${stateManager.baseDir}/PROMPT.md`)).toBe(true);
393
+
394
+ const content = stateManager.readFile('PROMPT.md');
395
+ expect(content).toContain('Session Resume');
396
+ });
397
+
398
+ test('writes to custom output path', async () => {
399
+ if (!pythonAvailable) {
400
+ console.log('Skipping: Python not available');
401
+ return;
402
+ }
403
+
404
+ const state = createPromptGeneratorState();
405
+ stateManager.writeState(state);
406
+ const customOutput = `${stateManager.baseDir}/custom-resume.md`;
407
+
408
+ const result = await runPythonHook(
409
+ PROMPT_GENERATOR_PATH,
410
+ {},
411
+ {
412
+ cwd: stateManager.baseDir,
413
+ args: ['--state', stateManager.stateFile, '--output', customOutput],
414
+ }
415
+ );
416
+
417
+ expect(result.exitCode).toBe(0);
418
+ expect(existsSync(customOutput)).toBe(true);
419
+ });
420
+
421
+ test('--no-memory excludes Anvil Memory section', async () => {
422
+ if (!pythonAvailable) {
423
+ console.log('Skipping: Python not available');
424
+ return;
425
+ }
426
+
427
+ const state = createPromptGeneratorState();
428
+ stateManager.writeState(state);
429
+
430
+ const result = await runPythonHook(
431
+ PROMPT_GENERATOR_PATH,
432
+ {},
433
+ {
434
+ cwd: stateManager.baseDir,
435
+ args: ['--state', stateManager.stateFile, '--no-memory', '--dry-run'],
436
+ }
437
+ );
438
+
439
+ expect(result.exitCode).toBe(0);
440
+ expect(result.stdout).not.toContain('anvil-memory');
441
+ expect(result.stdout).not.toContain('Memory Query');
442
+ });
443
+ });
444
+
445
+ describe('Content Validation', () => {
446
+ test('includes resume instructions section', async () => {
447
+ if (!pythonAvailable) {
448
+ console.log('Skipping: Python not available');
449
+ return;
450
+ }
451
+
452
+ const state = createPromptGeneratorState();
453
+ stateManager.writeState(state);
454
+
455
+ const result = await runPythonHook(
456
+ PROMPT_GENERATOR_PATH,
457
+ {},
458
+ {
459
+ cwd: stateManager.baseDir,
460
+ args: ['--state', stateManager.stateFile, '--dry-run'],
461
+ }
462
+ );
463
+
464
+ expect(result.exitCode).toBe(0);
465
+ expect(result.stdout).toContain('Resume Instructions');
466
+ expect(result.stdout).toContain('CRITICAL');
467
+ expect(result.stdout).toContain('DO NOT restart');
468
+ });
469
+
470
+ test('includes next steps section', async () => {
471
+ if (!pythonAvailable) {
472
+ console.log('Skipping: Python not available');
473
+ return;
474
+ }
475
+
476
+ const state = createPromptGeneratorState({
477
+ context_checkpoint: {
478
+ active: true,
479
+ level: 'L2',
480
+ percent_at_checkpoint: 90,
481
+ timestamp: new Date().toISOString(),
482
+ handoff_file: '.claude/handoffs/test.md',
483
+ resume_summary: '',
484
+ files_in_progress: [],
485
+ current_todo_item: 'Current task',
486
+ progress_on_item: 'In progress',
487
+ },
488
+ todo_items: ['Next task', 'Another task'],
489
+ });
490
+ stateManager.writeState(state);
491
+
492
+ const result = await runPythonHook(
493
+ PROMPT_GENERATOR_PATH,
494
+ {},
495
+ {
496
+ cwd: stateManager.baseDir,
497
+ args: ['--state', stateManager.stateFile, '--dry-run'],
498
+ }
499
+ );
500
+
501
+ expect(result.exitCode).toBe(0);
502
+ expect(result.stdout).toContain('Next Steps');
503
+ expect(result.stdout).toContain('Current task');
504
+ });
505
+
506
+ test('includes footer with timestamp', async () => {
507
+ if (!pythonAvailable) {
508
+ console.log('Skipping: Python not available');
509
+ return;
510
+ }
511
+
512
+ const state = createPromptGeneratorState();
513
+ stateManager.writeState(state);
514
+
515
+ const result = await runPythonHook(
516
+ PROMPT_GENERATOR_PATH,
517
+ {},
518
+ {
519
+ cwd: stateManager.baseDir,
520
+ args: ['--state', stateManager.stateFile, '--dry-run'],
521
+ }
522
+ );
523
+
524
+ expect(result.exitCode).toBe(0);
525
+ expect(result.stdout).toContain('Generated at');
526
+ expect(result.stdout).toContain('ralph_prompt_generator.py');
527
+ });
528
+
529
+ test('handoff reference includes file path', async () => {
530
+ if (!pythonAvailable) {
531
+ console.log('Skipping: Python not available');
532
+ return;
533
+ }
534
+
535
+ const handoffPath = '.claude/handoffs/session-handoff.md';
536
+ const state = createPromptGeneratorState({
537
+ context_checkpoint: {
538
+ active: true,
539
+ level: 'L2',
540
+ percent_at_checkpoint: 90,
541
+ timestamp: new Date().toISOString(),
542
+ handoff_file: handoffPath,
543
+ resume_summary: '',
544
+ files_in_progress: [],
545
+ current_todo_item: '',
546
+ progress_on_item: '',
547
+ },
548
+ });
549
+ stateManager.writeState(state);
550
+
551
+ const result = await runPythonHook(
552
+ PROMPT_GENERATOR_PATH,
553
+ {},
554
+ {
555
+ cwd: stateManager.baseDir,
556
+ args: ['--state', stateManager.stateFile, '--dry-run'],
557
+ }
558
+ );
559
+
560
+ expect(result.exitCode).toBe(0);
561
+ expect(result.stdout).toContain(handoffPath);
562
+ expect(result.stdout).toContain('Handoff Document');
563
+ });
564
+ });
565
+ });
566
+
567
+ describe('Prompt Generator - Script Exists', () => {
568
+ test('prompt generator script exists', () => {
569
+ expect(existsSync(PROMPT_GENERATOR_PATH)).toBe(true);
570
+ });
571
+ });