jettypod 3.0.1

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 (122) hide show
  1. package/.claude/PROTECT_SKILLS.md +28 -0
  2. package/.claude/settings.json +24 -0
  3. package/.claude/settings.local.json +16 -0
  4. package/.claude/skills/epic-discover/SKILL.md +262 -0
  5. package/.claude/skills/feature-discover/SKILL.md +393 -0
  6. package/.claude/skills/speed-mode/SKILL.md +364 -0
  7. package/.claude/skills/stable-mode/SKILL.md +591 -0
  8. package/.github/workflows/test-safety.yml +85 -0
  9. package/README.md +25 -0
  10. package/SPEED-STABLE-AUDIT.md +853 -0
  11. package/SYSTEM-BEHAVIOR.md +1241 -0
  12. package/TEST_SAFETY_AUDIT.md +314 -0
  13. package/TEST_SAFETY_IMPLEMENTATION.md +97 -0
  14. package/cucumber.js +8 -0
  15. package/docs/COMMAND_REFERENCE.md +903 -0
  16. package/docs/DECISIONS.md +68 -0
  17. package/docs/README.md +48 -0
  18. package/docs/STANDARDS-SYSTEM-DOCUMENTATION.md +374 -0
  19. package/docs/TEST-REWRITE-PLAN.md +261 -0
  20. package/docs/ai-test-writing-requirements.md +219 -0
  21. package/docs/claude-code-skills.md +607 -0
  22. package/docs/core-jettypod-methodology/comprehensive-jettypod-methodology.md +582 -0
  23. package/docs/core-jettypod-methodology/deprecated/jettypod-comprehensive-standards.md +1222 -0
  24. package/docs/core-jettypod-methodology/deprecated/jettypod-operating-guide.md +3399 -0
  25. package/docs/core-jettypod-methodology/deprecated/jettypod-technical-checklist.md +1325 -0
  26. package/docs/core-jettypod-methodology/deprecated/jettypod-vibe-coding-framework.md +1544 -0
  27. package/docs/core-jettypod-methodology/deprecated/prompt-engineering-guide.md +320 -0
  28. package/docs/core-jettypod-methodology/deprecated/vibe-coding-cheatsheet (1).md +516 -0
  29. package/docs/core-jettypod-methodology/deprecated/vibe-coding-framework.md +1544 -0
  30. package/docs/features/jettypod-standards-explained.md +543 -0
  31. package/docs/features/standards-inventory.md +257 -0
  32. package/docs/gap-analysis-current-vs-comprehensive-methodology.md +939 -0
  33. package/docs/jettypod-system-overview.md +409 -0
  34. package/features/auto-generate-production-chores.feature +14 -0
  35. package/features/claude-md-protection/steps.js +487 -0
  36. package/features/decisions/index.js +490 -0
  37. package/features/decisions/index.test.js +208 -0
  38. package/features/git-hooks/git-hooks.feature +30 -0
  39. package/features/git-hooks/index.js +93 -0
  40. package/features/git-hooks/index.test.js +137 -0
  41. package/features/git-hooks/post-commit +56 -0
  42. package/features/git-hooks/post-merge +47 -0
  43. package/features/git-hooks/pre-commit +28 -0
  44. package/features/git-hooks/simple-steps.js +53 -0
  45. package/features/git-hooks/simple-test.feature +10 -0
  46. package/features/git-hooks/steps.js +196 -0
  47. package/features/jettypod-update-command.feature +46 -0
  48. package/features/mode-prompts/index.js +95 -0
  49. package/features/mode-prompts/simple-steps.js +44 -0
  50. package/features/mode-prompts/simple-test.feature +9 -0
  51. package/features/mode-prompts/validation.test.js +120 -0
  52. package/features/refactor-mode/steps.js +217 -0
  53. package/features/refactor-mode.feature +49 -0
  54. package/features/skills-update/index.test.js +216 -0
  55. package/features/step_definitions/auto-generate-production-chores.steps.js +162 -0
  56. package/features/step_definitions/terminal-logo.steps.js +145 -0
  57. package/features/step_definitions/update-command.steps.js +183 -0
  58. package/features/terminal-logo/index.js +39 -0
  59. package/features/terminal-logo/terminal-logo.feature +30 -0
  60. package/features/update-command/index.js +181 -0
  61. package/features/update-command/index.test.js +225 -0
  62. package/features/work-commands/bug-workflow-display.feature +22 -0
  63. package/features/work-commands/index.js +311 -0
  64. package/features/work-commands/simple-steps.js +69 -0
  65. package/features/work-commands/stable-tests.feature +57 -0
  66. package/features/work-commands/steps.js +1120 -0
  67. package/features/work-commands/validation.test.js +88 -0
  68. package/features/work-commands/work-commands.feature +13 -0
  69. package/features/work-tracking/discovery-validation.test.js +228 -0
  70. package/features/work-tracking/index.js +1511 -0
  71. package/features/work-tracking/mode-required.feature +112 -0
  72. package/features/work-tracking/phase-tracking.test.js +482 -0
  73. package/features/work-tracking/prototype-tracking.test.js +485 -0
  74. package/features/work-tracking/tree-view.test.js +310 -0
  75. package/features/work-tracking/work-set-mode.feature +71 -0
  76. package/features/work-tracking/work-start-mode.feature +88 -0
  77. package/full-test.txt +0 -0
  78. package/install.sh +89 -0
  79. package/jettypod.js +1640 -0
  80. package/lib/bug-workflow.js +94 -0
  81. package/lib/bug-workflow.test.js +177 -0
  82. package/lib/claudemd.js +130 -0
  83. package/lib/claudemd.test.js +195 -0
  84. package/lib/comprehensive-standards-full.json +1778 -0
  85. package/lib/config.js +181 -0
  86. package/lib/config.test.js +511 -0
  87. package/lib/constants.js +107 -0
  88. package/lib/constants.test.js +164 -0
  89. package/lib/current-work.js +130 -0
  90. package/lib/current-work.test.js +146 -0
  91. package/lib/database-project-config.test.js +107 -0
  92. package/lib/database.js +256 -0
  93. package/lib/database.test.js +106 -0
  94. package/lib/decisions-generator.js +102 -0
  95. package/lib/decisions-generator.test.js +457 -0
  96. package/lib/decisions-helpers.js +119 -0
  97. package/lib/decisions-helpers.test.js +310 -0
  98. package/lib/discovery-checkpoint.js +83 -0
  99. package/lib/docs-generator.js +280 -0
  100. package/lib/external-checklist.js +177 -0
  101. package/lib/git.js +142 -0
  102. package/lib/git.test.js +145 -0
  103. package/lib/logo.js +3 -0
  104. package/lib/migrations/001-epic-to-parent.js +24 -0
  105. package/lib/migrations/002-default-work-item-modes.js +37 -0
  106. package/lib/migrations/002-default-work-item-modes.test.js +351 -0
  107. package/lib/migrations/003-epic-discovery-fields.js +52 -0
  108. package/lib/migrations/004-discovery-decisions-table.js +32 -0
  109. package/lib/migrations/005-migrate-decision-data.js +62 -0
  110. package/lib/migrations/006-feature-phase-field.js +61 -0
  111. package/lib/migrations/007-prototype-tracking.js +38 -0
  112. package/lib/migrations/008-scenario-file-field.js +24 -0
  113. package/lib/migrations/index.js +74 -0
  114. package/lib/production-helpers.js +69 -0
  115. package/lib/project-state.test.js +92 -0
  116. package/lib/test-helpers.js +184 -0
  117. package/lib/test-helpers.test.js +255 -0
  118. package/package.json +36 -0
  119. package/prototypes/test/index.html +1 -0
  120. package/setup-dist-repo.sh +68 -0
  121. package/test-safety-check.sh +80 -0
  122. package/work-item-tracking-plan.md +199 -0
@@ -0,0 +1,112 @@
1
+ Feature: Mode defaults to discovery on work item creation
2
+ As a developer
3
+ I want new work items to default to discovery mode
4
+ So I can start exploring without specifying mode every time
5
+
6
+ # Epic scenarios - epics don't have mode
7
+ Scenario: Create epic without mode
8
+ Given jettypod is initialized
9
+ When I create an epic "Test Epic" without mode
10
+ Then the work item is created successfully
11
+ And the work item has NULL mode
12
+
13
+ Scenario: Create epic with mode (optional)
14
+ Given jettypod is initialized
15
+ When I create an epic "Test Epic" with mode "stable"
16
+ Then the work item is created successfully
17
+ And the work item has mode "stable"
18
+
19
+ # Feature scenarios - defaults to discovery
20
+ Scenario: Create feature with explicit mode
21
+ Given jettypod is initialized
22
+ When I create a feature "Test Feature" with mode "speed"
23
+ Then the work item is created successfully
24
+ And the work item has mode "speed"
25
+
26
+ Scenario: Create feature without mode defaults to discovery
27
+ Given jettypod is initialized
28
+ When I create a feature "Test Feature" without mode
29
+ Then the work item is created successfully
30
+ And the work item has mode "discovery"
31
+
32
+ Scenario: Create feature with invalid mode
33
+ Given jettypod is initialized
34
+ When I try to create a feature "Test Feature" with mode "invalid"
35
+ Then I get an error "Invalid mode"
36
+ And no work item is created
37
+
38
+ # Bug scenarios - defaults to discovery
39
+ Scenario: Create bug with explicit mode
40
+ Given jettypod is initialized
41
+ When I create a bug "Test Bug" with mode "stable"
42
+ Then the work item is created successfully
43
+ And the work item has mode "stable"
44
+
45
+ Scenario: Create bug without mode defaults to discovery
46
+ Given jettypod is initialized
47
+ When I create a bug "Test Bug" without mode
48
+ Then the work item is created successfully
49
+ And the work item has mode "discovery"
50
+
51
+ # Chore scenarios - chores don't have modes
52
+ Scenario: Create chore with mode fails
53
+ Given jettypod is initialized
54
+ When I try to create a chore "Test Chore" with mode "production"
55
+ Then I get an error "Chores do not have modes"
56
+ And no work item is created
57
+
58
+ Scenario: Create chore without mode succeeds with NULL
59
+ Given jettypod is initialized
60
+ When I create a chore "Test Chore" without mode
61
+ Then the work item is created successfully
62
+ And the work item has NULL mode
63
+
64
+ # All valid modes
65
+ Scenario Outline: Create feature with all valid modes
66
+ Given jettypod is initialized
67
+ When I create a feature "Test Feature" with mode "<mode>"
68
+ Then the work item is created successfully
69
+ And the work item has mode "<mode>"
70
+
71
+ Examples:
72
+ | mode |
73
+ | speed |
74
+ | discovery |
75
+ | stable |
76
+ | production |
77
+
78
+ # Integration - hierarchical structure
79
+ Scenario: Epic with children in different modes
80
+ Given jettypod is initialized
81
+ And I create an epic "Test Epic" without mode
82
+ And I create a feature "Speed Feature" with mode "speed" and parent epic
83
+ And I create a bug "Stable Bug" with mode "stable" and parent epic
84
+ And I create a chore "Test Chore" without mode and parent epic
85
+ When I view the work tree
86
+ Then I see the epic without mode indicator
87
+ And I see the feature with mode "speed"
88
+ And I see the bug with mode "stable"
89
+ And I see the chore without mode indicator
90
+
91
+ # Edge cases
92
+ Scenario: Create multiple items with same title but different modes
93
+ Given jettypod is initialized
94
+ When I create a feature "Duplicate" with mode "speed"
95
+ And I create a feature "Duplicate" with mode "stable"
96
+ Then both work items are created successfully
97
+ And they have different modes
98
+
99
+ Scenario: Mode is case-sensitive
100
+ Given jettypod is initialized
101
+ When I try to create a feature "Test" with mode "Speed"
102
+ Then I get an error "Invalid mode"
103
+
104
+ Scenario: Mode with whitespace is invalid
105
+ Given jettypod is initialized
106
+ When I try to create a feature "Test" with mode " speed "
107
+ Then I get an error "Invalid mode"
108
+
109
+ Scenario: Empty string mode is treated as NULL
110
+ Given jettypod is initialized
111
+ When I try to create a feature "Test" with mode ""
112
+ Then I get an error "Invalid mode"
@@ -0,0 +1,482 @@
1
+ const { createTestEnvironment } = require('../../lib/test-helpers');
2
+ const { getDb, closeDb, resetDb } = require('../../lib/database');
3
+
4
+ describe('Feature Phase 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('phase 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('phase');
29
+ resolve();
30
+ });
31
+ });
32
+ });
33
+
34
+ test('phase column allows NULL values', async () => {
35
+ await new Promise((resolve) => {
36
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['epic', 'Test Epic', null], function(err) {
37
+ expect(err).toBeNull();
38
+ resolve();
39
+ });
40
+ });
41
+ });
42
+
43
+ test('phase column accepts discovery value', async () => {
44
+ await new Promise((resolve) => {
45
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Test Feature', 'discovery'], function(err) {
46
+ expect(err).toBeNull();
47
+ resolve();
48
+ });
49
+ });
50
+ });
51
+
52
+ test('phase column accepts implementation value', async () => {
53
+ await new Promise((resolve) => {
54
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Test Feature', 'implementation'], function(err) {
55
+ expect(err).toBeNull();
56
+ resolve();
57
+ });
58
+ });
59
+ });
60
+ });
61
+
62
+ describe('Feature Creation with Default Phase', () => {
63
+ test('new features default to discovery phase', async () => {
64
+ await new Promise((resolve) => {
65
+ db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
66
+ ['feature', 'Test Feature', 'Description', null, 'discovery', 0, 'discovery'],
67
+ function(err) {
68
+ expect(err).toBeNull();
69
+ const featureId = this.lastID;
70
+
71
+ db.get('SELECT phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
72
+ expect(err).toBeNull();
73
+ expect(row.phase).toBe('discovery');
74
+ resolve();
75
+ });
76
+ }
77
+ );
78
+ });
79
+ });
80
+
81
+ test('epics have NULL phase', async () => {
82
+ await new Promise((resolve) => {
83
+ db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
84
+ ['epic', 'Test Epic', 'Description', null, null, 0, null],
85
+ function(err) {
86
+ expect(err).toBeNull();
87
+ const epicId = this.lastID;
88
+
89
+ db.get('SELECT phase FROM work_items WHERE id = ?', [epicId], (err, row) => {
90
+ expect(err).toBeNull();
91
+ expect(row.phase).toBeNull();
92
+ resolve();
93
+ });
94
+ }
95
+ );
96
+ });
97
+ });
98
+
99
+ test('chores have NULL phase', async () => {
100
+ await new Promise((resolve) => {
101
+ db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
102
+ ['chore', 'Test Chore', 'Description', null, 'discovery', 0, null],
103
+ function(err) {
104
+ expect(err).toBeNull();
105
+ const choreId = this.lastID;
106
+
107
+ db.get('SELECT phase FROM work_items WHERE id = ?', [choreId], (err, row) => {
108
+ expect(err).toBeNull();
109
+ expect(row.phase).toBeNull();
110
+ resolve();
111
+ });
112
+ }
113
+ );
114
+ });
115
+ });
116
+
117
+ test('bugs have NULL phase', async () => {
118
+ await new Promise((resolve) => {
119
+ db.run('INSERT INTO work_items (type, title, description, parent_id, mode, needs_discovery, phase) VALUES (?, ?, ?, ?, ?, ?, ?)',
120
+ ['bug', 'Test Bug', 'Description', null, 'stable', 0, null],
121
+ function(err) {
122
+ expect(err).toBeNull();
123
+ const bugId = this.lastID;
124
+
125
+ db.get('SELECT phase FROM work_items WHERE id = ?', [bugId], (err, row) => {
126
+ expect(err).toBeNull();
127
+ expect(row.phase).toBeNull();
128
+ resolve();
129
+ });
130
+ }
131
+ );
132
+ });
133
+ });
134
+ });
135
+
136
+ describe('Phase Transition (Discovery → Implementation)', () => {
137
+ test('can transition feature from discovery to implementation', async () => {
138
+ // Create feature in discovery phase
139
+ await new Promise((resolve) => {
140
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
141
+ ['feature', 'Test Feature', 'discovery', 'discovery'],
142
+ resolve
143
+ );
144
+ });
145
+
146
+ // Transition to implementation
147
+ await new Promise((resolve) => {
148
+ db.run('UPDATE work_items SET phase = ?, mode = ? WHERE id = ?',
149
+ ['implementation', 'speed', 1],
150
+ resolve
151
+ );
152
+ });
153
+
154
+ // Verify phase and mode
155
+ await new Promise((resolve) => {
156
+ db.get('SELECT phase, mode FROM work_items WHERE id = ?', [1], (err, row) => {
157
+ expect(err).toBeNull();
158
+ expect(row.phase).toBe('implementation');
159
+ expect(row.mode).toBe('speed');
160
+ resolve();
161
+ });
162
+ });
163
+ });
164
+
165
+ test('implementation phase sets mode to speed', async () => {
166
+ await new Promise((resolve) => {
167
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
168
+ ['feature', 'Test Feature', 'discovery', 'discovery'],
169
+ function(err) {
170
+ const featureId = this.lastID;
171
+
172
+ db.run('UPDATE work_items SET phase = ?, mode = ? WHERE id = ?',
173
+ ['implementation', 'speed', featureId],
174
+ () => {
175
+ db.get('SELECT mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
176
+ expect(row.mode).toBe('speed');
177
+ resolve();
178
+ });
179
+ }
180
+ );
181
+ }
182
+ );
183
+ });
184
+ });
185
+
186
+ test('cannot transition epic (has NULL phase)', async () => {
187
+ await new Promise((resolve) => {
188
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)',
189
+ ['epic', 'Test Epic', null],
190
+ function(err) {
191
+ const epicId = this.lastID;
192
+
193
+ db.get('SELECT phase, type FROM work_items WHERE id = ?', [epicId], (err, row) => {
194
+ expect(row.phase).toBeNull();
195
+ expect(row.type).toBe('epic');
196
+ resolve();
197
+ });
198
+ }
199
+ );
200
+ });
201
+ });
202
+
203
+ test('cannot transition chore (has NULL phase)', async () => {
204
+ await new Promise((resolve) => {
205
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
206
+ ['chore', 'Test Chore', null, 'discovery'],
207
+ function(err) {
208
+ const choreId = this.lastID;
209
+
210
+ db.get('SELECT phase, type FROM work_items WHERE id = ?', [choreId], (err, row) => {
211
+ expect(row.phase).toBeNull();
212
+ expect(row.type).toBe('chore');
213
+ resolve();
214
+ });
215
+ }
216
+ );
217
+ });
218
+ });
219
+ });
220
+
221
+ describe('Phase Display Logic', () => {
222
+ test('discovery phase shows with 🔍 icon', () => {
223
+ const item = { type: 'feature', phase: 'discovery', mode: 'discovery' };
224
+
225
+ let modeIndicator = '';
226
+ if (item.type === 'feature') {
227
+ if (item.phase === 'discovery') {
228
+ modeIndicator = ' [🔍 discovery]';
229
+ } else if (item.mode) {
230
+ modeIndicator = ` [${item.mode}]`;
231
+ }
232
+ }
233
+
234
+ expect(modeIndicator).toBe(' [🔍 discovery]');
235
+ });
236
+
237
+ test('implementation phase shows mode', () => {
238
+ const item = { type: 'feature', phase: 'implementation', mode: 'speed' };
239
+
240
+ let modeIndicator = '';
241
+ if (item.type === 'feature') {
242
+ if (item.phase === 'discovery') {
243
+ modeIndicator = ' [🔍 discovery]';
244
+ } else if (item.mode) {
245
+ modeIndicator = ` [${item.mode}]`;
246
+ }
247
+ }
248
+
249
+ expect(modeIndicator).toBe(' [speed]');
250
+ });
251
+
252
+ test('implementation phase with stable mode', () => {
253
+ const item = { type: 'feature', phase: 'implementation', mode: 'stable' };
254
+
255
+ let modeIndicator = '';
256
+ if (item.type === 'feature') {
257
+ if (item.phase === 'discovery') {
258
+ modeIndicator = ' [🔍 discovery]';
259
+ } else if (item.mode) {
260
+ modeIndicator = ` [${item.mode}]`;
261
+ }
262
+ }
263
+
264
+ expect(modeIndicator).toBe(' [stable]');
265
+ });
266
+
267
+ test('implementation phase with production mode', () => {
268
+ const item = { type: 'feature', phase: 'implementation', mode: 'production' };
269
+
270
+ let modeIndicator = '';
271
+ if (item.type === 'feature') {
272
+ if (item.phase === 'discovery') {
273
+ modeIndicator = ' [🔍 discovery]';
274
+ } else if (item.mode) {
275
+ modeIndicator = ` [${item.mode}]`;
276
+ }
277
+ }
278
+
279
+ expect(modeIndicator).toBe(' [production]');
280
+ });
281
+
282
+ test('epic shows no phase indicator', () => {
283
+ const item = { type: 'epic', phase: null, mode: null };
284
+
285
+ let modeIndicator = '';
286
+ if (item.type === 'feature') {
287
+ if (item.phase === 'discovery') {
288
+ modeIndicator = ' [🔍 discovery]';
289
+ } else if (item.mode) {
290
+ modeIndicator = ` [${item.mode}]`;
291
+ }
292
+ } else if (item.type !== 'epic' && item.mode) {
293
+ modeIndicator = ` [${item.mode}]`;
294
+ }
295
+
296
+ expect(modeIndicator).toBe('');
297
+ });
298
+
299
+ test('chore shows mode indicator', () => {
300
+ const item = { type: 'chore', phase: null, mode: 'discovery' };
301
+
302
+ let modeIndicator = '';
303
+ if (item.type === 'feature') {
304
+ if (item.phase === 'discovery') {
305
+ modeIndicator = ' [🔍 discovery]';
306
+ } else if (item.mode) {
307
+ modeIndicator = ` [${item.mode}]`;
308
+ }
309
+ } else if (item.type !== 'epic' && item.mode) {
310
+ modeIndicator = ` [${item.mode}]`;
311
+ }
312
+
313
+ expect(modeIndicator).toBe(' [discovery]');
314
+ });
315
+
316
+ test('bug shows mode indicator', () => {
317
+ const item = { type: 'bug', phase: null, mode: 'stable' };
318
+
319
+ let modeIndicator = '';
320
+ if (item.type === 'feature') {
321
+ if (item.phase === 'discovery') {
322
+ modeIndicator = ' [🔍 discovery]';
323
+ } else if (item.mode) {
324
+ modeIndicator = ` [${item.mode}]`;
325
+ }
326
+ } else if (item.type !== 'epic' && item.mode) {
327
+ modeIndicator = ` [${item.mode}]`;
328
+ }
329
+
330
+ expect(modeIndicator).toBe(' [stable]');
331
+ });
332
+ });
333
+
334
+ describe('Edge Cases', () => {
335
+ test('feature with NULL phase treated as implementation', async () => {
336
+ await new Promise((resolve) => {
337
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
338
+ ['feature', 'Legacy Feature', null, 'stable'],
339
+ function(err) {
340
+ const featureId = this.lastID;
341
+
342
+ db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
343
+ expect(row.phase).toBeNull();
344
+ expect(row.mode).toBe('stable');
345
+ resolve();
346
+ });
347
+ }
348
+ );
349
+ });
350
+ });
351
+
352
+ test('feature in discovery with NULL mode', async () => {
353
+ await new Promise((resolve) => {
354
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
355
+ ['feature', 'Test Feature', 'discovery', null],
356
+ function(err) {
357
+ const featureId = this.lastID;
358
+
359
+ db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
360
+ expect(row.phase).toBe('discovery');
361
+ expect(row.mode).toBeNull();
362
+ resolve();
363
+ });
364
+ }
365
+ );
366
+ });
367
+ });
368
+
369
+ test('feature in implementation with NULL mode defaults to speed', async () => {
370
+ await new Promise((resolve) => {
371
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
372
+ ['feature', 'Test Feature', 'implementation', null],
373
+ function(err) {
374
+ const featureId = this.lastID;
375
+
376
+ // Simulate what implement command would do
377
+ db.run('UPDATE work_items SET mode = ? WHERE id = ? AND mode IS NULL',
378
+ ['speed', featureId],
379
+ () => {
380
+ db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
381
+ expect(row.phase).toBe('implementation');
382
+ expect(row.mode).toBe('speed');
383
+ resolve();
384
+ });
385
+ }
386
+ );
387
+ }
388
+ );
389
+ });
390
+ });
391
+
392
+ test('handles empty phase value', async () => {
393
+ await new Promise((resolve) => {
394
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)',
395
+ ['feature', 'Test Feature', '', 'discovery'],
396
+ function(err) {
397
+ const featureId = this.lastID;
398
+
399
+ db.get('SELECT phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
400
+ // Empty string is stored as is
401
+ expect(row.phase).toBe('');
402
+ resolve();
403
+ });
404
+ }
405
+ );
406
+ });
407
+ });
408
+
409
+ test('multiple features can be in discovery simultaneously', async () => {
410
+ await new Promise((resolve) => {
411
+ db.serialize(() => {
412
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Feature 1', 'discovery']);
413
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Feature 2', 'discovery']);
414
+ db.run('INSERT INTO work_items (type, title, phase) VALUES (?, ?, ?)', ['feature', 'Feature 3', 'discovery'], () => {
415
+ db.all('SELECT COUNT(*) as count FROM work_items WHERE phase = ?', ['discovery'], (err, rows) => {
416
+ expect(rows[0].count).toBe(3);
417
+ resolve();
418
+ });
419
+ });
420
+ });
421
+ });
422
+ });
423
+
424
+ test('multiple features can be in implementation simultaneously', async () => {
425
+ await new Promise((resolve) => {
426
+ db.serialize(() => {
427
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)', ['feature', 'Feature 1', 'implementation', 'speed']);
428
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)', ['feature', 'Feature 2', 'implementation', 'stable']);
429
+ db.run('INSERT INTO work_items (type, title, phase, mode) VALUES (?, ?, ?, ?)', ['feature', 'Feature 3', 'implementation', 'production'], () => {
430
+ db.all('SELECT COUNT(*) as count FROM work_items WHERE phase = ?', ['implementation'], (err, rows) => {
431
+ expect(rows[0].count).toBe(3);
432
+ resolve();
433
+ });
434
+ });
435
+ });
436
+ });
437
+ });
438
+ });
439
+
440
+ describe('Backwards Compatibility', () => {
441
+ test('existing features without phase field work correctly', async () => {
442
+ // Simulate old feature (before migration)
443
+ await new Promise((resolve) => {
444
+ db.run('INSERT INTO work_items (type, title, mode) VALUES (?, ?, ?)',
445
+ ['feature', 'Legacy Feature', 'stable'],
446
+ function(err) {
447
+ const featureId = this.lastID;
448
+
449
+ db.get('SELECT phase, mode FROM work_items WHERE id = ?', [featureId], (err, row) => {
450
+ // Phase will be NULL or default value from schema
451
+ expect(row.mode).toBe('stable');
452
+ resolve();
453
+ });
454
+ }
455
+ );
456
+ });
457
+ });
458
+
459
+ test('migration sets existing features to implementation phase', async () => {
460
+ // Create feature without phase
461
+ await new Promise((resolve) => {
462
+ db.run('INSERT INTO work_items (type, title, mode, phase) VALUES (?, ?, ?, ?)',
463
+ ['feature', 'Old Feature', 'stable', null],
464
+ function(err) {
465
+ const featureId = this.lastID;
466
+
467
+ // Simulate migration
468
+ db.run('UPDATE work_items SET phase = ? WHERE type = ? AND phase IS NULL',
469
+ ['implementation', 'feature'],
470
+ () => {
471
+ db.get('SELECT phase FROM work_items WHERE id = ?', [featureId], (err, row) => {
472
+ expect(row.phase).toBe('implementation');
473
+ resolve();
474
+ });
475
+ }
476
+ );
477
+ }
478
+ );
479
+ });
480
+ });
481
+ });
482
+ });