jettypod 4.1.2 → 4.1.4

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 (179) hide show
  1. package/.nvmrc +1 -0
  2. package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
  3. package/docs/DECISIONS.md +10 -12
  4. package/docs/NODE_VERSION.md +83 -0
  5. package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
  6. package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
  7. package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
  8. package/hooks/post-checkout +17 -0
  9. package/hooks/post-merge +17 -0
  10. package/hooks/pre-commit +30 -0
  11. package/jettypod.js +259 -120
  12. package/lib/coverage-tracker.js +218 -0
  13. package/lib/database.js +2 -0
  14. package/lib/db-export.js +192 -0
  15. package/lib/db-import.js +193 -0
  16. package/lib/external-transition-handler.js +32 -0
  17. package/lib/git-hook-helpers.js +174 -0
  18. package/lib/git-root.js +90 -0
  19. package/lib/infrastructure-chore-generator.js +45 -0
  20. package/lib/install-hooks.js +52 -0
  21. package/lib/jettypod-backup.js +238 -0
  22. package/lib/merge-lock.js +193 -0
  23. package/lib/migrations/012-add-worktree-path.js +38 -0
  24. package/lib/migrations/013-worktrees-table.js +86 -0
  25. package/lib/migrations/014-migrate-worktree-data.js +161 -0
  26. package/lib/migrations/015-merge-locks-table.js +67 -0
  27. package/lib/pattern-finder.js +152 -0
  28. package/lib/process-manager.js +140 -0
  29. package/lib/production-standards-reader.js +13 -2
  30. package/lib/production-standards-writer.js +85 -0
  31. package/lib/skills/feature-planning/dry-run-validator.js +135 -0
  32. package/lib/skills/feature-planning/validation-formatter.js +160 -0
  33. package/lib/smart-conflict-detection.js +168 -0
  34. package/lib/smart-fetch-rebase.js +614 -0
  35. package/lib/step-definition-parser.js +76 -0
  36. package/lib/unit-test-generator.js +232 -0
  37. package/lib/verification-command-generator.js +66 -0
  38. package/lib/worktree-diagnostics.js +413 -0
  39. package/lib/worktree-facade.js +174 -0
  40. package/lib/worktree-manager.js +636 -0
  41. package/lib/worktree-reconciler.js +429 -0
  42. package/package.json +30 -3
  43. package/skills-templates/external-transition/SKILL.md +34 -3
  44. package/skills-templates/feature-planning/SKILL.md +190 -24
  45. package/skills-templates/production-mode/SKILL.md +127 -9
  46. package/skills-templates/speed-mode/SKILL.md +454 -51
  47. package/skills-templates/stable-mode/SKILL.md +285 -76
  48. package/.claude/PROTECT_SKILLS.md +0 -28
  49. package/.claude/settings.json +0 -24
  50. package/.claude/settings.local.json +0 -16
  51. package/.claude/skills/epic-planning/SKILL.md +0 -297
  52. package/.claude/skills/external-transition/SKILL.md +0 -384
  53. package/.claude/skills/feature-planning/SKILL.md +0 -464
  54. package/.claude/skills/production-mode/SKILL.md +0 -369
  55. package/.claude/skills/speed-mode/SKILL.md +0 -481
  56. package/.claude/skills/stable-mode/SKILL.md +0 -713
  57. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
  58. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
  59. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
  60. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
  61. package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
  62. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
  63. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
  64. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
  65. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
  66. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
  67. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
  68. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
  69. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
  70. package/.devpod/current-work.json +0 -10
  71. package/.devpod/work.db +0 -0
  72. package/.github/workflows/test-safety.yml +0 -85
  73. package/.jettypod/config.json +0 -5
  74. package/.jettypod/current-work.json +0 -10
  75. package/.jettypod/hooks/README.md +0 -77
  76. package/.jettypod/hooks/protect-claude-md.js +0 -338
  77. package/.jettypod/test-work.db +0 -0
  78. package/.jettypod/work.db +0 -0
  79. package/CLAUDE.md +0 -49
  80. package/SPEED-STABLE-AUDIT.md +0 -853
  81. package/SYSTEM-BEHAVIOR.md +0 -2199
  82. package/TEST_SAFETY_AUDIT.md +0 -314
  83. package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
  84. package/cucumber-report.html +0 -45
  85. package/dist/devpod-linux +0 -0
  86. package/dist/devpod-macos +0 -0
  87. package/dist/devpod-win.exe +0 -0
  88. package/docs/features/jettypod-standards-explained.md +0 -543
  89. package/docs/features/standards-inventory.md +0 -257
  90. package/features/auto-generate-production-chores.feature +0 -13
  91. package/features/backlog-command.feature +0 -26
  92. package/features/backlog-filtering-production.feature +0 -10
  93. package/features/claude-md-protection/steps.js +0 -498
  94. package/features/decisions/index.js +0 -490
  95. package/features/decisions/index.test.js +0 -208
  96. package/features/fix-text-wrapping.feature +0 -42
  97. package/features/git-hooks/git-hooks.feature +0 -30
  98. package/features/git-hooks/index.js +0 -93
  99. package/features/git-hooks/index.test.js +0 -137
  100. package/features/git-hooks/post-commit +0 -56
  101. package/features/git-hooks/post-merge +0 -47
  102. package/features/git-hooks/pre-commit +0 -28
  103. package/features/git-hooks/simple-steps.js +0 -53
  104. package/features/git-hooks/simple-test.feature +0 -10
  105. package/features/git-hooks/steps.js +0 -196
  106. package/features/jettypod-update-command.feature +0 -46
  107. package/features/mode-prompts/index.js +0 -95
  108. package/features/mode-prompts/simple-steps.js +0 -44
  109. package/features/mode-prompts/simple-test.feature +0 -9
  110. package/features/mode-prompts/validation.test.js +0 -120
  111. package/features/multiple-claude-instances.feature +0 -121
  112. package/features/production-mode-skill.feature +0 -121
  113. package/features/refactor-mode/steps.js +0 -217
  114. package/features/refactor-mode.feature +0 -49
  115. package/features/simplify-external-transition.feature +0 -166
  116. package/features/skills-update/index.test.js +0 -216
  117. package/features/step_definitions/backlog-command.steps.js +0 -37
  118. package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
  119. package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
  120. package/features/step_definitions/production-mode-skill.steps.js +0 -862
  121. package/features/step_definitions/simplify-external-transition.steps.js +0 -370
  122. package/features/step_definitions/terminal-logo.steps.js +0 -145
  123. package/features/step_definitions/update-command.steps.js +0 -183
  124. package/features/support/hooks.js +0 -9
  125. package/features/terminal-logo/index.js +0 -39
  126. package/features/terminal-logo/terminal-logo.feature +0 -30
  127. package/features/update-command/index.js +0 -181
  128. package/features/update-command/index.test.js +0 -225
  129. package/features/work-commands/bug-workflow-display.feature +0 -22
  130. package/features/work-commands/index.js +0 -498
  131. package/features/work-commands/simple-steps.js +0 -69
  132. package/features/work-commands/stable-tests.feature +0 -57
  133. package/features/work-commands/steps.js +0 -1174
  134. package/features/work-commands/validation.test.js +0 -88
  135. package/features/work-commands/work-commands.feature +0 -13
  136. package/features/work-tracking/discovery-validation.test.js +0 -228
  137. package/features/work-tracking/index.js +0 -1921
  138. package/features/work-tracking/mode-required.feature +0 -112
  139. package/features/work-tracking/phase-tracking.test.js +0 -482
  140. package/features/work-tracking/prototype-tracking.test.js +0 -485
  141. package/features/work-tracking/tree-view.test.js +0 -310
  142. package/features/work-tracking/work-set-mode.feature +0 -71
  143. package/features/work-tracking/work-start-mode.feature +0 -88
  144. package/full-test.txt +0 -0
  145. package/lib/bug-workflow.test.js +0 -177
  146. package/lib/claudemd.test.js +0 -195
  147. package/lib/config.test.js +0 -511
  148. package/lib/constants.test.js +0 -164
  149. package/lib/current-work.test.js +0 -146
  150. package/lib/database-project-config.test.js +0 -111
  151. package/lib/database.test.js +0 -106
  152. package/lib/decisions-generator.test.js +0 -457
  153. package/lib/decisions-helpers.test.js +0 -310
  154. package/lib/git-coordinator.js +0 -167
  155. package/lib/git.test.js +0 -145
  156. package/lib/migrations/002-default-work-item-modes.test.js +0 -351
  157. package/lib/production-chore-generator.test.js +0 -432
  158. package/lib/production-context-detector.test.js +0 -277
  159. package/lib/production-scenario-appender.test.js +0 -235
  160. package/lib/production-scenario-validator.test.js +0 -246
  161. package/lib/production-standards-reader.test.js +0 -270
  162. package/lib/project-state.test.js +0 -92
  163. package/lib/push-queue.js +0 -417
  164. package/lib/queue-processor.js +0 -74
  165. package/lib/test-helpers.js +0 -202
  166. package/lib/test-helpers.test.js +0 -255
  167. package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
  168. package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
  169. package/prototypes/2025-01-11-production-mode-guided.js +0 -217
  170. package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
  171. package/prototypes/2025-01-11-production-standards-example.md +0 -204
  172. package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
  173. package/prototypes/test/index.html +0 -1
  174. package/setup-dist-repo.sh +0 -68
  175. package/test-production-standards-engine.js +0 -130
  176. package/test-results.json +0 -2195
  177. package/test-safety-check.sh +0 -80
  178. package/work-item-tracking-plan.md +0 -199
  179. /package/{.jettypod/devpod.db → jettypod.db} +0 -0
@@ -1,457 +0,0 @@
1
- const { generateDecisionsFile } = require('./decisions-generator');
2
- const { createTestEnvironment } = require('./test-helpers');
3
- const { getDb, closeDb, resetDb } = require('./database');
4
- const config = require('./config');
5
- const fs = require('fs');
6
- const path = require('path');
7
-
8
- describe('Decisions Generator', () => {
9
- let testEnv;
10
- let db;
11
-
12
- beforeEach(() => {
13
- resetDb();
14
- testEnv = createTestEnvironment();
15
- process.chdir(testEnv.testDir);
16
- db = getDb();
17
- });
18
-
19
- afterEach(async () => {
20
-
21
-
22
- await closeDb();
23
- testEnv.cleanup();
24
- resetDb();
25
- });
26
-
27
- describe('generateDecisionsFile', () => {
28
- test('creates docs directory if it does not exist', async () => {
29
- // Ensure db is ready
30
- await new Promise((resolve) => {
31
- db.get('SELECT 1', [], resolve);
32
- });
33
-
34
- const docsDir = path.join(process.cwd(), 'docs');
35
- expect(fs.existsSync(docsDir)).toBe(false);
36
-
37
- await generateDecisionsFile();
38
-
39
- expect(fs.existsSync(docsDir)).toBe(true);
40
- });
41
-
42
- test('creates DECISIONS.md in docs directory', async () => {
43
- await new Promise((resolve) => {
44
- db.get('SELECT 1', [], resolve);
45
- });
46
-
47
- const decisionsPath = path.join(process.cwd(), 'docs', 'DECISIONS.md');
48
-
49
- await generateDecisionsFile();
50
-
51
- expect(fs.existsSync(decisionsPath)).toBe(true);
52
- });
53
-
54
- test('generates file with header when no decisions exist', async () => {
55
- await new Promise((resolve) => {
56
- db.get('SELECT 1', [], resolve);
57
- });
58
-
59
- const decisionsPath = await generateDecisionsFile();
60
- const content = fs.readFileSync(decisionsPath, 'utf8');
61
-
62
- expect(content).toContain('# Architectural and Technical Decisions');
63
- expect(content).toContain('This document records key decisions made during project discovery and epic planning');
64
- });
65
-
66
- test('includes project-level decision when it exists', async () => {
67
- await new Promise((resolve) => {
68
- db.get('SELECT 1', [], resolve);
69
- });
70
-
71
- // Mock config with decision
72
- const originalRead = config.read;
73
- config.read = () => ({
74
- project_discovery: {
75
- winner: 'prototypes/rest-api',
76
- rationale: 'Simple and widely understood',
77
- started_date: '2025-10-31T00:00:00.000Z'
78
- }
79
- });
80
-
81
- const decisionsPath = await generateDecisionsFile();
82
- const content = fs.readFileSync(decisionsPath, 'utf8');
83
-
84
- expect(content).toContain('## Project-Level Decisions');
85
- expect(content).toContain('prototypes/rest-api');
86
- expect(content).toContain('Simple and widely understood');
87
-
88
- config.read = originalRead;
89
- });
90
-
91
- test('formats project decision date correctly', async () => {
92
- await new Promise((resolve) => { db.get("SELECT 1", [], resolve); });
93
-
94
- const originalRead = config.read;
95
- config.read = () => ({
96
- project_discovery: {
97
- winner: 'prototypes/test',
98
- rationale: 'Testing',
99
- started_date: '2025-10-31T00:00:00.000Z'
100
- }
101
- });
102
-
103
- const decisionsPath = await generateDecisionsFile();
104
- const content = fs.readFileSync(decisionsPath, 'utf8');
105
-
106
- const date = new Date('2025-10-31T00:00:00.000Z');
107
- expect(content).toContain(date.toLocaleDateString());
108
-
109
- config.read = originalRead;
110
- });
111
-
112
- test('handles missing project rationale gracefully', async () => {
113
- await new Promise((resolve) => { db.get("SELECT 1", [], resolve); });
114
-
115
- const originalRead = config.read;
116
- config.read = () => ({
117
- project_discovery: {
118
- winner: 'prototypes/test'
119
- }
120
- });
121
-
122
- const decisionsPath = await generateDecisionsFile();
123
- const content = fs.readFileSync(decisionsPath, 'utf8');
124
-
125
- expect(content).toContain('prototypes/test');
126
- expect(content).not.toContain('**Rationale:**');
127
-
128
- config.read = originalRead;
129
- });
130
-
131
- test('handles missing project date gracefully', async () => {
132
- await new Promise((resolve) => { db.get("SELECT 1", [], resolve); });
133
-
134
- const originalRead = config.read;
135
- config.read = () => ({
136
- project_discovery: {
137
- winner: 'prototypes/test',
138
- rationale: 'Testing'
139
- }
140
- });
141
-
142
- const decisionsPath = await generateDecisionsFile();
143
- const content = fs.readFileSync(decisionsPath, 'utf8');
144
-
145
- expect(content).toContain('prototypes/test');
146
- expect(content).not.toContain('**Date:**');
147
-
148
- config.read = originalRead;
149
- });
150
-
151
- test('includes epic decisions when they exist', async () => {
152
- // Create epic with decision
153
- await new Promise((resolve) => {
154
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
155
- });
156
-
157
- await new Promise((resolve) => {
158
- db.run(
159
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
160
- [1, 'Architecture', 'REST API', 'Simple and widely understood'],
161
- resolve
162
- );
163
- });
164
-
165
- const decisionsPath = await generateDecisionsFile();
166
- const content = fs.readFileSync(decisionsPath, 'utf8');
167
-
168
- expect(content).toContain('## Epic-Level Decisions');
169
- expect(content).toContain('Epic #1: Test Epic');
170
- expect(content).toContain('**Architecture:** REST API');
171
- expect(content).toContain('*Rationale:* Simple and widely understood');
172
- });
173
-
174
- test('formats epic decision dates correctly', async () => {
175
- // Create epic with decision
176
- await new Promise((resolve) => {
177
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
178
- });
179
-
180
- await new Promise((resolve) => {
181
- db.run(
182
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale, created_at) VALUES (?, ?, ?, ?, ?)',
183
- [1, 'Architecture', 'REST API', 'Simple', '2025-10-31 12:00:00'],
184
- resolve
185
- );
186
- });
187
-
188
- const decisionsPath = await generateDecisionsFile();
189
- const content = fs.readFileSync(decisionsPath, 'utf8');
190
-
191
- expect(content).toContain('*Date:*');
192
- });
193
-
194
- test('groups multiple decisions by epic', async () => {
195
- // Create epic with multiple decisions
196
- await new Promise((resolve) => {
197
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
198
- });
199
-
200
- await new Promise((resolve) => {
201
- db.run(
202
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
203
- [1, 'Architecture', 'REST API', 'Simple'],
204
- resolve
205
- );
206
- });
207
-
208
- await new Promise((resolve) => {
209
- db.run(
210
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
211
- [1, 'Database', 'PostgreSQL', 'Robust'],
212
- resolve
213
- );
214
- });
215
-
216
- const decisionsPath = await generateDecisionsFile();
217
- const content = fs.readFileSync(decisionsPath, 'utf8');
218
-
219
- // Should have one epic section with two decisions
220
- const epicSections = content.match(/### Epic #1:/g);
221
- expect(epicSections).toHaveLength(1);
222
- expect(content).toContain('**Architecture:** REST API');
223
- expect(content).toContain('**Database:** PostgreSQL');
224
- });
225
-
226
- test('separates decisions from different epics', async () => {
227
- // Create two epics with decisions
228
- await new Promise((resolve) => {
229
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Epic 1'], resolve);
230
- });
231
- await new Promise((resolve) => {
232
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Epic 2'], resolve);
233
- });
234
-
235
- await new Promise((resolve) => {
236
- db.run(
237
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
238
- [1, 'Architecture', 'REST API', 'Simple'],
239
- resolve
240
- );
241
- });
242
-
243
- await new Promise((resolve) => {
244
- db.run(
245
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
246
- [2, 'Architecture', 'GraphQL', 'Flexible'],
247
- resolve
248
- );
249
- });
250
-
251
- const decisionsPath = await generateDecisionsFile();
252
- const content = fs.readFileSync(decisionsPath, 'utf8');
253
-
254
- expect(content).toContain('### Epic #1: Epic 1');
255
- expect(content).toContain('### Epic #2: Epic 2');
256
- expect(content).toContain('REST API');
257
- expect(content).toContain('GraphQL');
258
- });
259
-
260
- test('includes both project and epic decisions in single file', async () => {
261
- // Mock project decision
262
- const originalRead = config.read;
263
- config.read = () => ({
264
- project_discovery: {
265
- winner: 'prototypes/web-app',
266
- rationale: 'Web-first approach'
267
- }
268
- });
269
-
270
- // Create epic with decision
271
- await new Promise((resolve) => {
272
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
273
- });
274
-
275
- await new Promise((resolve) => {
276
- db.run(
277
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
278
- [1, 'Architecture', 'REST API', 'Simple'],
279
- resolve
280
- );
281
- });
282
-
283
- const decisionsPath = await generateDecisionsFile();
284
- const content = fs.readFileSync(decisionsPath, 'utf8');
285
-
286
- expect(content).toContain('## Project-Level Decisions');
287
- expect(content).toContain('prototypes/web-app');
288
- expect(content).toContain('## Epic-Level Decisions');
289
- expect(content).toContain('REST API');
290
-
291
- config.read = originalRead;
292
- });
293
-
294
- test('overwrites existing DECISIONS.md file', async () => {
295
- // Ensure db is ready
296
- await new Promise((resolve) => {
297
- db.get('SELECT 1', [], resolve);
298
- });
299
-
300
- const decisionsPath = path.join(process.cwd(), 'docs', 'DECISIONS.md');
301
-
302
- // Create initial file
303
- await generateDecisionsFile();
304
- const initialContent = fs.readFileSync(decisionsPath, 'utf8');
305
-
306
- // Add a decision
307
- await new Promise((resolve) => {
308
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'New Epic'], resolve);
309
- });
310
-
311
- await new Promise((resolve) => {
312
- db.run(
313
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
314
- [1, 'Architecture', 'REST API', 'Simple'],
315
- resolve
316
- );
317
- });
318
-
319
- // Regenerate
320
- await generateDecisionsFile();
321
- const updatedContent = fs.readFileSync(decisionsPath, 'utf8');
322
-
323
- expect(updatedContent).not.toBe(initialContent);
324
- expect(updatedContent).toContain('New Epic');
325
- });
326
-
327
- test('returns path to generated file', async () => {
328
- // Ensure db is ready
329
- await new Promise((resolve) => {
330
- db.get('SELECT 1', [], resolve);
331
- });
332
-
333
- const returnedPath = await generateDecisionsFile();
334
- const expectedPath = path.join(process.cwd(), 'docs', 'DECISIONS.md');
335
-
336
- expect(returnedPath).toBe(expectedPath);
337
- });
338
-
339
- test('generates valid markdown structure', async () => {
340
- const originalRead = config.read;
341
- config.read = () => ({
342
- project_discovery: {
343
- winner: 'prototypes/test',
344
- rationale: 'Testing'
345
- }
346
- });
347
-
348
- await new Promise((resolve) => {
349
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
350
- });
351
-
352
- await new Promise((resolve) => {
353
- db.run(
354
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
355
- [1, 'Architecture', 'REST API', 'Simple'],
356
- resolve
357
- );
358
- });
359
-
360
- const decisionsPath = await generateDecisionsFile();
361
- const content = fs.readFileSync(decisionsPath, 'utf8');
362
-
363
- // Check markdown structure
364
- expect(content).toContain('# Architectural and Technical Decisions');
365
- expect(content).toContain('## Project-Level Decisions');
366
- expect(content).toContain('### UX Approach & Tech Stack');
367
- expect(content).toContain('## Epic-Level Decisions');
368
- expect(content).toContain('### Epic #1');
369
- expect(content).toContain('---'); // Section separators
370
-
371
- config.read = originalRead;
372
- });
373
-
374
- test('orders epic decisions by created_at ASC', async () => {
375
- await new Promise((resolve) => {
376
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
377
- });
378
-
379
- // Add decisions with specific timestamps
380
- await new Promise((resolve) => {
381
- db.run(
382
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale, created_at) VALUES (?, ?, ?, ?, ?)',
383
- [1, 'Second', 'Decision 2', 'Later', '2025-10-31 12:00:00'],
384
- resolve
385
- );
386
- });
387
-
388
- await new Promise((resolve) => {
389
- db.run(
390
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale, created_at) VALUES (?, ?, ?, ?, ?)',
391
- [1, 'First', 'Decision 1', 'Earlier', '2025-10-31 11:00:00'],
392
- resolve
393
- );
394
- });
395
-
396
- const decisionsPath = await generateDecisionsFile();
397
- const content = fs.readFileSync(decisionsPath, 'utf8');
398
-
399
- const firstIndex = content.indexOf('**First:**');
400
- const secondIndex = content.indexOf('**Second:**');
401
-
402
- expect(firstIndex).toBeLessThan(secondIndex);
403
- });
404
-
405
- test('handles empty rationale field', async () => {
406
- await new Promise((resolve) => {
407
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
408
- });
409
-
410
- await new Promise((resolve) => {
411
- db.run(
412
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
413
- [1, 'Architecture', 'REST API', ''],
414
- resolve
415
- );
416
- });
417
-
418
- const decisionsPath = await generateDecisionsFile();
419
- const content = fs.readFileSync(decisionsPath, 'utf8');
420
-
421
- // Should still include the decision even with empty rationale
422
- expect(content).toContain('**Architecture:** REST API');
423
- });
424
-
425
- test('handles special characters in decisions', async () => {
426
- const originalRead = config.read;
427
- config.read = () => ({
428
- project_discovery: {
429
- winner: 'prototypes/api-v2.0',
430
- rationale: 'RESTful API with OAuth 2.0 & JWT tokens'
431
- }
432
- });
433
-
434
- await new Promise((resolve) => {
435
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test & Production'], resolve);
436
- });
437
-
438
- await new Promise((resolve) => {
439
- db.run(
440
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
441
- [1, 'Auth & Security', 'OAuth 2.0', 'Industry standard (RFC 6749)'],
442
- resolve
443
- );
444
- });
445
-
446
- const decisionsPath = await generateDecisionsFile();
447
- const content = fs.readFileSync(decisionsPath, 'utf8');
448
-
449
- expect(content).toContain('api-v2.0');
450
- expect(content).toContain('OAuth 2.0 & JWT');
451
- expect(content).toContain('Test & Production');
452
- expect(content).toContain('RFC 6749');
453
-
454
- config.read = originalRead;
455
- });
456
- });
457
- });