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,485 +0,0 @@
1
- const { createTestEnvironment } = require('../../lib/test-helpers');
2
- const { getDb, closeDb, resetDb } = require('../../lib/database');
3
-
4
- describe('Multi-level Prototype Tracking', () => {
5
- let testEnv;
6
- let db;
7
-
8
- beforeEach(() => {
9
- resetDb();
10
- testEnv = createTestEnvironment();
11
- process.chdir(testEnv.testDir);
12
- db = getDb();
13
- });
14
-
15
- afterEach(async () => {
16
-
17
-
18
- await closeDb();
19
- testEnv.cleanup();
20
- resetDb();
21
- });
22
-
23
- describe('Database Schema', () => {
24
- test('prototype_files column exists in work_items table', async () => {
25
- await new Promise((resolve) => {
26
- db.get('SELECT sql FROM sqlite_master WHERE type="table" AND name="work_items"', [], (err, row) => {
27
- expect(err).toBeNull();
28
- expect(row.sql).toContain('prototype_files');
29
- resolve();
30
- });
31
- });
32
- });
33
-
34
- test('discovery_winner column exists in work_items table', async () => {
35
- await new Promise((resolve) => {
36
- db.get('SELECT sql FROM sqlite_master WHERE type="table" AND name="work_items"', [], (err, row) => {
37
- expect(err).toBeNull();
38
- expect(row.sql).toContain('discovery_winner');
39
- resolve();
40
- });
41
- });
42
- });
43
-
44
- test('prototype_files accepts JSON array', async () => {
45
- await new Promise((resolve) => {
46
- const prototypeFiles = JSON.stringify(['proto1.js', 'proto2.js']);
47
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)',
48
- ['feature', 'Test Feature', prototypeFiles],
49
- function(err) {
50
- expect(err).toBeNull();
51
-
52
- db.get('SELECT prototype_files FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
53
- expect(err).toBeNull();
54
- expect(row.prototype_files).toBe(prototypeFiles);
55
- expect(JSON.parse(row.prototype_files)).toEqual(['proto1.js', 'proto2.js']);
56
- resolve();
57
- });
58
- }
59
- );
60
- });
61
- });
62
-
63
- test('discovery_winner accepts file path', async () => {
64
- await new Promise((resolve) => {
65
- db.run('INSERT INTO work_items (type, title, discovery_winner) VALUES (?, ?, ?)',
66
- ['feature', 'Test Feature', 'prototypes/winner.js'],
67
- function(err) {
68
- expect(err).toBeNull();
69
-
70
- db.get('SELECT discovery_winner FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
71
- expect(err).toBeNull();
72
- expect(row.discovery_winner).toBe('prototypes/winner.js');
73
- resolve();
74
- });
75
- }
76
- );
77
- });
78
- });
79
-
80
- test('prototype fields allow NULL values', async () => {
81
- await new Promise((resolve) => {
82
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner) VALUES (?, ?, ?, ?)',
83
- ['feature', 'Test Feature', null, null],
84
- function(err) {
85
- expect(err).toBeNull();
86
-
87
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
88
- expect(err).toBeNull();
89
- expect(row.prototype_files).toBeNull();
90
- expect(row.discovery_winner).toBeNull();
91
- resolve();
92
- });
93
- }
94
- );
95
- });
96
- });
97
- });
98
-
99
- describe('Project-level Prototype Tracking', () => {
100
- test('project stores prototypes from discovery', async () => {
101
- await new Promise((resolve) => {
102
- const prototypeFiles = JSON.stringify(['prototypes/web.js', 'prototypes/mobile.js']);
103
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner) VALUES (?, ?, ?, ?)',
104
- ['epic', 'Project Discovery', prototypeFiles, 'prototypes/web.js'],
105
- function(err) {
106
- expect(err).toBeNull();
107
- const projectId = this.lastID;
108
-
109
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [projectId], (err, row) => {
110
- expect(err).toBeNull();
111
- expect(JSON.parse(row.prototype_files)).toEqual(['prototypes/web.js', 'prototypes/mobile.js']);
112
- expect(row.discovery_winner).toBe('prototypes/web.js');
113
- resolve();
114
- });
115
- }
116
- );
117
- });
118
- });
119
-
120
- test('project without prototypes has NULL fields', async () => {
121
- await new Promise((resolve) => {
122
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)',
123
- ['epic', 'Project Without Prototypes'],
124
- function(err) {
125
- expect(err).toBeNull();
126
- const projectId = this.lastID;
127
-
128
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [projectId], (err, row) => {
129
- expect(err).toBeNull();
130
- expect(row.prototype_files).toBeNull();
131
- expect(row.discovery_winner).toBeNull();
132
- resolve();
133
- });
134
- }
135
- );
136
- });
137
- });
138
- });
139
-
140
- describe('Epic-level Prototype Tracking', () => {
141
- test('epic stores prototypes from architectural decision', async () => {
142
- await new Promise((resolve) => {
143
- const prototypeFiles = JSON.stringify(['prototypes/websocket.js', 'prototypes/sse.js']);
144
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner) VALUES (?, ?, ?, ?)',
145
- ['epic', 'Real-time Updates Epic', prototypeFiles, 'WebSockets with Socket.io'],
146
- function(err) {
147
- expect(err).toBeNull();
148
- const epicId = this.lastID;
149
-
150
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [epicId], (err, row) => {
151
- expect(err).toBeNull();
152
- expect(JSON.parse(row.prototype_files)).toEqual(['prototypes/websocket.js', 'prototypes/sse.js']);
153
- expect(row.discovery_winner).toBe('WebSockets with Socket.io');
154
- resolve();
155
- });
156
- }
157
- );
158
- });
159
- });
160
-
161
- test('epic without architectural prototypes has NULL fields', async () => {
162
- await new Promise((resolve) => {
163
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)',
164
- ['epic', 'Simple Epic'],
165
- function(err) {
166
- expect(err).toBeNull();
167
- const epicId = this.lastID;
168
-
169
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [epicId], (err, row) => {
170
- expect(err).toBeNull();
171
- expect(row.prototype_files).toBeNull();
172
- expect(row.discovery_winner).toBeNull();
173
- resolve();
174
- });
175
- }
176
- );
177
- });
178
- });
179
- });
180
-
181
- describe('Feature-level Prototype Tracking', () => {
182
- test('feature stores prototypes from discovery phase', async () => {
183
- await new Promise((resolve) => {
184
- const prototypeFiles = JSON.stringify(['prototypes/option-a.js', 'prototypes/option-b.js']);
185
- db.run('INSERT INTO work_items (type, title, phase, prototype_files, discovery_winner) VALUES (?, ?, ?, ?, ?)',
186
- ['feature', 'Test Feature', 'implementation', prototypeFiles, 'prototypes/option-a.js'],
187
- function(err) {
188
- expect(err).toBeNull();
189
- const featureId = this.lastID;
190
-
191
- db.get('SELECT prototype_files, discovery_winner, phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
192
- expect(err).toBeNull();
193
- expect(JSON.parse(row.prototype_files)).toEqual(['prototypes/option-a.js', 'prototypes/option-b.js']);
194
- expect(row.discovery_winner).toBe('prototypes/option-a.js');
195
- expect(row.phase).toBe('implementation');
196
- resolve();
197
- });
198
- }
199
- );
200
- });
201
- });
202
-
203
- test('feature transition to implementation preserves prototypes', async () => {
204
- await new Promise((resolve) => {
205
- const prototypeFiles = JSON.stringify(['proto1.js', 'proto2.js']);
206
-
207
- // Create feature in discovery with prototypes
208
- db.run('INSERT INTO work_items (type, title, phase, mode, prototype_files, discovery_winner) VALUES (?, ?, ?, ?, ?, ?)',
209
- ['feature', 'Test Feature', 'discovery', 'discovery', prototypeFiles, 'proto1.js'],
210
- function(err) {
211
- const featureId = this.lastID;
212
-
213
- // Transition to implementation (simulate work implement command)
214
- db.run('UPDATE work_items SET phase = ?, mode = ? WHERE id = ?',
215
- ['implementation', 'speed', featureId],
216
- () => {
217
- db.get('SELECT phase, mode, prototype_files, discovery_winner FROM work_items WHERE id = ?', [featureId], (err, row) => {
218
- expect(row.phase).toBe('implementation');
219
- expect(row.mode).toBe('speed');
220
- expect(JSON.parse(row.prototype_files)).toEqual(['proto1.js', 'proto2.js']);
221
- expect(row.discovery_winner).toBe('proto1.js');
222
- resolve();
223
- });
224
- }
225
- );
226
- }
227
- );
228
- });
229
- });
230
-
231
- test('feature without prototypes has NULL fields', async () => {
232
- await new Promise((resolve) => {
233
- db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
234
- ['feature', 'Simple Feature', 'discovery', 'discovery'],
235
- function(err) {
236
- expect(err).toBeNull();
237
- const featureId = this.lastID;
238
-
239
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [featureId], (err, row) => {
240
- expect(err).toBeNull();
241
- expect(row.prototype_files).toBeNull();
242
- expect(row.discovery_winner).toBeNull();
243
- resolve();
244
- });
245
- }
246
- );
247
- });
248
- });
249
- });
250
-
251
- describe('Edge Cases', () => {
252
- test('empty JSON array is valid', async () => {
253
- await new Promise((resolve) => {
254
- const prototypeFiles = JSON.stringify([]);
255
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)',
256
- ['feature', 'Test Feature', prototypeFiles],
257
- function(err) {
258
- expect(err).toBeNull();
259
-
260
- db.get('SELECT prototype_files FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
261
- expect(JSON.parse(row.prototype_files)).toEqual([]);
262
- resolve();
263
- });
264
- }
265
- );
266
- });
267
- });
268
-
269
- test('invalid JSON is stored as-is', async () => {
270
- await new Promise((resolve) => {
271
- const invalidJson = 'not valid json';
272
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)',
273
- ['feature', 'Test Feature', invalidJson],
274
- function(err) {
275
- expect(err).toBeNull();
276
-
277
- db.get('SELECT prototype_files FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
278
- expect(row.prototype_files).toBe(invalidJson);
279
- // Parsing should throw
280
- expect(() => JSON.parse(row.prototype_files)).toThrow();
281
- resolve();
282
- });
283
- }
284
- );
285
- });
286
- });
287
-
288
- test('empty string for discovery_winner', async () => {
289
- await new Promise((resolve) => {
290
- db.run('INSERT INTO work_items (type, title, discovery_winner) VALUES (?, ?, ?)',
291
- ['feature', 'Test Feature', ''],
292
- function(err) {
293
- expect(err).toBeNull();
294
-
295
- db.get('SELECT discovery_winner FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
296
- expect(row.discovery_winner).toBe('');
297
- resolve();
298
- });
299
- }
300
- );
301
- });
302
- });
303
-
304
- test('long file paths are stored correctly', async () => {
305
- await new Promise((resolve) => {
306
- const longPath = 'prototypes/' + 'a'.repeat(500) + '/winner.js';
307
- db.run('INSERT INTO work_items (type, title, discovery_winner) VALUES (?, ?, ?)',
308
- ['feature', 'Test Feature', longPath],
309
- function(err) {
310
- expect(err).toBeNull();
311
-
312
- db.get('SELECT discovery_winner FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
313
- expect(row.discovery_winner).toBe(longPath);
314
- expect(row.discovery_winner.length).toBe(longPath.length);
315
- resolve();
316
- });
317
- }
318
- );
319
- });
320
- });
321
-
322
- test('JSON with special characters', async () => {
323
- await new Promise((resolve) => {
324
- const specialFiles = JSON.stringify(['proto "quoted".js', "proto 'single'.js", 'proto\\backslash.js']);
325
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)',
326
- ['feature', 'Test Feature', specialFiles],
327
- function(err) {
328
- expect(err).toBeNull();
329
-
330
- db.get('SELECT prototype_files FROM work_items WHERE id = ?', [this.lastID], (err, row) => {
331
- const parsed = JSON.parse(row.prototype_files);
332
- expect(parsed).toEqual(['proto "quoted".js', "proto 'single'.js", 'proto\\backslash.js']);
333
- resolve();
334
- });
335
- }
336
- );
337
- });
338
- });
339
-
340
- test('multiple work items with same prototypes', async () => {
341
- await new Promise((resolve) => {
342
- const prototypeFiles = JSON.stringify(['proto1.js', 'proto2.js']);
343
-
344
- db.serialize(() => {
345
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)', ['feature', 'Feature 1', prototypeFiles]);
346
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)', ['feature', 'Feature 2', prototypeFiles]);
347
- db.run('INSERT INTO work_items (type, title, prototype_files) VALUES (?, ?, ?)', ['epic', 'Epic 1', prototypeFiles], () => {
348
- db.all('SELECT COUNT(*) as count FROM work_items WHERE prototype_files = ?', [prototypeFiles], (err, rows) => {
349
- expect(rows[0].count).toBe(3);
350
- resolve();
351
- });
352
- });
353
- });
354
- });
355
- });
356
- });
357
-
358
- describe('Backwards Compatibility', () => {
359
- test('existing work items without prototype fields work correctly', async () => {
360
- await new Promise((resolve) => {
361
- db.run('INSERT INTO work_items (type, title, status) VALUES (?, ?, ?)',
362
- ['feature', 'Legacy Feature', 'backlog'],
363
- function(err) {
364
- expect(err).toBeNull();
365
- const featureId = this.lastID;
366
-
367
- db.get('SELECT * FROM work_items WHERE id = ?', [featureId], (err, row) => {
368
- expect(err).toBeNull();
369
- expect(row.title).toBe('Legacy Feature');
370
- expect(row.status).toBe('backlog');
371
- expect(row.prototype_files).toBeNull();
372
- expect(row.discovery_winner).toBeNull();
373
- resolve();
374
- });
375
- }
376
- );
377
- });
378
- });
379
-
380
- test('can add prototypes to existing work item', async () => {
381
- await new Promise((resolve) => {
382
- // Create without prototypes
383
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)',
384
- ['feature', 'Existing Feature'],
385
- function(err) {
386
- const featureId = this.lastID;
387
-
388
- // Later add prototypes
389
- const prototypeFiles = JSON.stringify(['new-proto.js']);
390
- db.run('UPDATE work_items SET prototype_files = ?, discovery_winner = ? WHERE id = ?',
391
- [prototypeFiles, 'new-proto.js', featureId],
392
- () => {
393
- db.get('SELECT prototype_files, discovery_winner FROM work_items WHERE id = ?', [featureId], (err, row) => {
394
- expect(JSON.parse(row.prototype_files)).toEqual(['new-proto.js']);
395
- expect(row.discovery_winner).toBe('new-proto.js');
396
- resolve();
397
- });
398
- }
399
- );
400
- }
401
- );
402
- });
403
- });
404
- });
405
-
406
- describe('Multi-level Tracking Integration', () => {
407
- test('project, epic, and feature can all have different prototypes', async () => {
408
- await new Promise((resolve) => {
409
- db.serialize(() => {
410
- // Project-level prototypes
411
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner) VALUES (?, ?, ?, ?)',
412
- ['epic', 'Project', JSON.stringify(['project-proto.js']), 'project-proto.js']
413
- );
414
-
415
- // Epic-level prototypes
416
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner, parent_id) VALUES (?, ?, ?, ?, ?)',
417
- ['epic', 'Epic', JSON.stringify(['epic-proto.js']), 'epic-proto.js', 1]
418
- );
419
-
420
- // Feature-level prototypes
421
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner, parent_id, epic_id) VALUES (?, ?, ?, ?, ?, ?)',
422
- ['feature', 'Feature', JSON.stringify(['feature-proto.js']), 'feature-proto.js', 2, 2],
423
- () => {
424
- db.all('SELECT type, title, prototype_files, discovery_winner FROM work_items ORDER BY id', [], (err, rows) => {
425
- expect(rows.length).toBe(3);
426
-
427
- expect(rows[0].type).toBe('epic');
428
- expect(JSON.parse(rows[0].prototype_files)).toEqual(['project-proto.js']);
429
- expect(rows[0].discovery_winner).toBe('project-proto.js');
430
-
431
- expect(rows[1].type).toBe('epic');
432
- expect(JSON.parse(rows[1].prototype_files)).toEqual(['epic-proto.js']);
433
- expect(rows[1].discovery_winner).toBe('epic-proto.js');
434
-
435
- expect(rows[2].type).toBe('feature');
436
- expect(JSON.parse(rows[2].prototype_files)).toEqual(['feature-proto.js']);
437
- expect(rows[2].discovery_winner).toBe('feature-proto.js');
438
-
439
- resolve();
440
- });
441
- }
442
- );
443
- });
444
- });
445
- });
446
-
447
- test('child inherits no prototypes from parent by default', async () => {
448
- await new Promise((resolve) => {
449
- db.serialize(() => {
450
- // Epic with prototypes
451
- db.run('INSERT INTO work_items (type, title, prototype_files, discovery_winner) VALUES (?, ?, ?, ?)',
452
- ['epic', 'Parent Epic', JSON.stringify(['parent-proto.js']), 'parent-proto.js'],
453
- function() {
454
- const epicId = this.lastID;
455
-
456
- // Feature without prototypes
457
- db.run('INSERT INTO work_items (type, title, parent_id, epic_id) VALUES (?, ?, ?, ?)',
458
- ['feature', 'Child Feature', epicId, epicId],
459
- function() {
460
- const featureId = this.lastID;
461
-
462
- db.all('SELECT id, type, prototype_files, discovery_winner FROM work_items WHERE id IN (?, ?)',
463
- [epicId, featureId],
464
- (err, rows) => {
465
- const epic = rows.find(r => r.type === 'epic');
466
- const feature = rows.find(r => r.type === 'feature');
467
-
468
- expect(JSON.parse(epic.prototype_files)).toEqual(['parent-proto.js']);
469
- expect(epic.discovery_winner).toBe('parent-proto.js');
470
-
471
- expect(feature.prototype_files).toBeNull();
472
- expect(feature.discovery_winner).toBeNull();
473
-
474
- resolve();
475
- }
476
- );
477
- }
478
- );
479
- }
480
- );
481
- });
482
- });
483
- });
484
- });
485
- });