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,351 @@
1
+ const sqlite3 = require('sqlite3').verbose();
2
+ const migration = require('./002-default-work-item-modes');
3
+
4
+ describe('Migration: 002-default-work-item-modes', () => {
5
+ let db;
6
+
7
+ beforeEach((done) => {
8
+ // Create in-memory database for each test
9
+ db = new sqlite3.Database(':memory:', (err) => {
10
+ if (err) return done(err);
11
+
12
+ // Create work_items table with schema
13
+ db.run(`
14
+ CREATE TABLE work_items (
15
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
16
+ type TEXT NOT NULL,
17
+ title TEXT NOT NULL,
18
+ description TEXT,
19
+ status TEXT DEFAULT 'backlog',
20
+ parent_id INTEGER,
21
+ epic_id INTEGER,
22
+ branch_name TEXT,
23
+ file_paths TEXT,
24
+ commit_sha TEXT,
25
+ mode TEXT,
26
+ current INTEGER DEFAULT 0,
27
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
28
+ )
29
+ `, done);
30
+ });
31
+ });
32
+
33
+ afterEach((done) => {
34
+ db.close(done);
35
+ });
36
+
37
+ describe('metadata', () => {
38
+ it('has correct migration id', () => {
39
+ expect(migration.id).toBe('002-default-work-item-modes');
40
+ });
41
+
42
+ it('has description', () => {
43
+ expect(migration.description).toBeDefined();
44
+ expect(typeof migration.description).toBe('string');
45
+ expect(migration.description.length).toBeGreaterThan(0);
46
+ expect(migration.description).toContain('epics');
47
+ });
48
+
49
+ it('has up function', () => {
50
+ expect(typeof migration.up).toBe('function');
51
+ });
52
+ });
53
+
54
+ describe('up()', () => {
55
+ it('sets mode to stable for features/bugs/chores with NULL mode', async () => {
56
+ // Insert work items without mode
57
+ await new Promise((resolve, reject) => {
58
+ db.run(
59
+ `INSERT INTO work_items (type, title, mode) VALUES
60
+ ('feature', 'Test Feature', NULL),
61
+ ('bug', 'Test Bug', NULL),
62
+ ('chore', 'Test Chore', NULL)`,
63
+ (err) => err ? reject(err) : resolve()
64
+ );
65
+ });
66
+
67
+ // Run migration
68
+ await migration.up(db);
69
+
70
+ // Verify all items have mode set
71
+ const items = await new Promise((resolve, reject) => {
72
+ db.all('SELECT id, type, title, mode FROM work_items ORDER BY id', [], (err, rows) => {
73
+ err ? reject(err) : resolve(rows);
74
+ });
75
+ });
76
+
77
+ expect(items).toHaveLength(3);
78
+ expect(items[0].mode).toBe('stable');
79
+ expect(items[1].mode).toBe('stable');
80
+ expect(items[2].mode).toBe('stable');
81
+ });
82
+
83
+ it('sets epic modes to NULL', async () => {
84
+ // Insert epics with and without modes
85
+ await new Promise((resolve, reject) => {
86
+ db.run(
87
+ `INSERT INTO work_items (type, title, mode) VALUES
88
+ ('epic', 'Epic with mode', 'speed'),
89
+ ('epic', 'Epic without mode', NULL)`,
90
+ (err) => err ? reject(err) : resolve()
91
+ );
92
+ });
93
+
94
+ // Run migration
95
+ await migration.up(db);
96
+
97
+ // Verify all epics have NULL mode
98
+ const items = await new Promise((resolve, reject) => {
99
+ db.all('SELECT id, title, mode FROM work_items WHERE type = "epic"', [], (err, rows) => {
100
+ err ? reject(err) : resolve(rows);
101
+ });
102
+ });
103
+
104
+ expect(items).toHaveLength(2);
105
+ expect(items[0].mode).toBe(null);
106
+ expect(items[1].mode).toBe(null);
107
+ });
108
+
109
+ it('does not change existing mode values for non-epics', async () => {
110
+ // Insert work items with various modes
111
+ await new Promise((resolve, reject) => {
112
+ db.run(
113
+ `INSERT INTO work_items (type, title, mode) VALUES
114
+ ('feature', 'Speed Feature', 'speed'),
115
+ ('bug', 'Discovery Bug', 'discovery'),
116
+ ('chore', 'Production Chore', 'production'),
117
+ ('feature', 'Stable Feature', 'stable')`,
118
+ (err) => err ? reject(err) : resolve()
119
+ );
120
+ });
121
+
122
+ // Run migration
123
+ await migration.up(db);
124
+
125
+ // Verify modes unchanged
126
+ const items = await new Promise((resolve, reject) => {
127
+ db.all('SELECT id, type, title, mode FROM work_items ORDER BY id', [], (err, rows) => {
128
+ err ? reject(err) : resolve(rows);
129
+ });
130
+ });
131
+
132
+ expect(items).toHaveLength(4);
133
+ expect(items[0].mode).toBe('speed');
134
+ expect(items[1].mode).toBe('discovery');
135
+ expect(items[2].mode).toBe('production');
136
+ expect(items[3].mode).toBe('stable');
137
+ });
138
+
139
+ it('handles mixed NULL and non-NULL modes', async () => {
140
+ // Insert mix of items
141
+ await new Promise((resolve, reject) => {
142
+ db.run(
143
+ `INSERT INTO work_items (type, title, mode) VALUES
144
+ ('feature', 'Has Speed', 'speed'),
145
+ ('feature', 'No Mode', NULL),
146
+ ('feature', 'Has Discovery', 'discovery'),
147
+ ('feature', 'Also No Mode', NULL)`,
148
+ (err) => err ? reject(err) : resolve()
149
+ );
150
+ });
151
+
152
+ // Run migration
153
+ await migration.up(db);
154
+
155
+ // Verify selective update
156
+ const items = await new Promise((resolve, reject) => {
157
+ db.all('SELECT id, title, mode FROM work_items ORDER BY id', [], (err, rows) => {
158
+ err ? reject(err) : resolve(rows);
159
+ });
160
+ });
161
+
162
+ expect(items).toHaveLength(4);
163
+ expect(items[0].mode).toBe('speed'); // Unchanged
164
+ expect(items[1].mode).toBe('stable'); // Changed from NULL
165
+ expect(items[2].mode).toBe('discovery'); // Unchanged
166
+ expect(items[3].mode).toBe('stable'); // Changed from NULL
167
+ });
168
+
169
+ it('is idempotent - can run multiple times safely', async () => {
170
+ // Insert items without mode
171
+ await new Promise((resolve, reject) => {
172
+ db.run(
173
+ `INSERT INTO work_items (type, title, mode) VALUES
174
+ ('feature', 'Test', NULL)`,
175
+ (err) => err ? reject(err) : resolve()
176
+ );
177
+ });
178
+
179
+ // Run migration first time
180
+ await migration.up(db);
181
+
182
+ const firstRun = await new Promise((resolve, reject) => {
183
+ db.all('SELECT mode FROM work_items', [], (err, rows) => {
184
+ err ? reject(err) : resolve(rows);
185
+ });
186
+ });
187
+
188
+ expect(firstRun[0].mode).toBe('stable');
189
+
190
+ // Run migration second time
191
+ await migration.up(db);
192
+
193
+ const secondRun = await new Promise((resolve, reject) => {
194
+ db.all('SELECT mode FROM work_items', [], (err, rows) => {
195
+ err ? reject(err) : resolve(rows);
196
+ });
197
+ });
198
+
199
+ expect(secondRun[0].mode).toBe('stable');
200
+ expect(firstRun).toEqual(secondRun);
201
+ });
202
+
203
+ it('handles empty work_items table', async () => {
204
+ // Don't insert any items
205
+
206
+ // Run migration - should not fail
207
+ await expect(migration.up(db)).resolves.not.toThrow();
208
+
209
+ // Verify table still empty
210
+ const items = await new Promise((resolve, reject) => {
211
+ db.all('SELECT * FROM work_items', [], (err, rows) => {
212
+ err ? reject(err) : resolve(rows);
213
+ });
214
+ });
215
+
216
+ expect(items).toHaveLength(0);
217
+ });
218
+
219
+ it('handles all item types (epic, feature, bug, chore) correctly', async () => {
220
+ // Insert one of each type without mode
221
+ await new Promise((resolve, reject) => {
222
+ db.run(
223
+ `INSERT INTO work_items (type, title, mode) VALUES
224
+ ('epic', 'Test Epic', NULL),
225
+ ('feature', 'Test Feature', NULL),
226
+ ('bug', 'Test Bug', NULL),
227
+ ('chore', 'Test Chore', NULL)`,
228
+ (err) => err ? reject(err) : resolve()
229
+ );
230
+ });
231
+
232
+ // Run migration
233
+ await migration.up(db);
234
+
235
+ // Verify correct modes
236
+ const items = await new Promise((resolve, reject) => {
237
+ db.all('SELECT type, mode FROM work_items ORDER BY id', [], (err, rows) => {
238
+ err ? reject(err) : resolve(rows);
239
+ });
240
+ });
241
+
242
+ expect(items).toHaveLength(4);
243
+ expect(items[0].mode).toBe(null); // epic
244
+ expect(items[1].mode).toBe('stable'); // feature
245
+ expect(items[2].mode).toBe('stable'); // bug
246
+ expect(items[3].mode).toBe('stable'); // chore
247
+ });
248
+
249
+ it('preserves other column values during update', async () => {
250
+ // Insert item with various fields populated
251
+ await new Promise((resolve, reject) => {
252
+ db.run(
253
+ `INSERT INTO work_items
254
+ (type, title, description, status, parent_id, branch_name, current, mode)
255
+ VALUES
256
+ ('feature', 'Rich Item', 'A detailed description', 'in_progress', 5, 'feature/test', 1, NULL)`,
257
+ (err) => err ? reject(err) : resolve()
258
+ );
259
+ });
260
+
261
+ // Run migration
262
+ await migration.up(db);
263
+
264
+ // Verify only mode changed
265
+ const item = await new Promise((resolve, reject) => {
266
+ db.get('SELECT * FROM work_items WHERE id = 1', [], (err, row) => {
267
+ err ? reject(err) : resolve(row);
268
+ });
269
+ });
270
+
271
+ expect(item.type).toBe('feature');
272
+ expect(item.title).toBe('Rich Item');
273
+ expect(item.description).toBe('A detailed description');
274
+ expect(item.status).toBe('in_progress');
275
+ expect(item.parent_id).toBe(5);
276
+ expect(item.branch_name).toBe('feature/test');
277
+ expect(item.current).toBe(1);
278
+ expect(item.mode).toBe('stable'); // Only this changed
279
+ });
280
+
281
+ it('returns a Promise', () => {
282
+ const result = migration.up(db);
283
+ expect(result).toBeInstanceOf(Promise);
284
+ });
285
+
286
+ it('resolves Promise on success', async () => {
287
+ await new Promise((resolve, reject) => {
288
+ db.run(
289
+ `INSERT INTO work_items (type, title, mode) VALUES ('feature', 'Test', NULL)`,
290
+ (err) => err ? reject(err) : resolve()
291
+ );
292
+ });
293
+
294
+ await expect(migration.up(db)).resolves.toBeUndefined();
295
+ });
296
+ });
297
+
298
+ describe('edge cases', () => {
299
+ it('handles work items created at exactly the same time', async () => {
300
+ const timestamp = '2024-10-18 12:00:00';
301
+
302
+ await new Promise((resolve, reject) => {
303
+ db.run(
304
+ `INSERT INTO work_items (type, title, mode, created_at) VALUES
305
+ ('feature', 'Item 1', NULL, ?),
306
+ ('feature', 'Item 2', NULL, ?)`,
307
+ [timestamp, timestamp],
308
+ (err) => err ? reject(err) : resolve()
309
+ );
310
+ });
311
+
312
+ await migration.up(db);
313
+
314
+ const items = await new Promise((resolve, reject) => {
315
+ db.all('SELECT mode FROM work_items', [], (err, rows) => {
316
+ err ? reject(err) : resolve(rows);
317
+ });
318
+ });
319
+
320
+ expect(items).toHaveLength(2);
321
+ expect(items[0].mode).toBe('stable');
322
+ expect(items[1].mode).toBe('stable');
323
+ });
324
+
325
+ it('handles work items with parent_id hierarchy', async () => {
326
+ // Epic with children, none have mode
327
+ await new Promise((resolve, reject) => {
328
+ db.run(
329
+ `INSERT INTO work_items (id, type, title, parent_id, mode) VALUES
330
+ (1, 'epic', 'Epic', NULL, NULL),
331
+ (2, 'feature', 'Child 1', 1, NULL),
332
+ (3, 'bug', 'Child 2', 1, NULL)`,
333
+ (err) => err ? reject(err) : resolve()
334
+ );
335
+ });
336
+
337
+ await migration.up(db);
338
+
339
+ const items = await new Promise((resolve, reject) => {
340
+ db.all('SELECT id, type, title, mode FROM work_items ORDER BY id', [], (err, rows) => {
341
+ err ? reject(err) : resolve(rows);
342
+ });
343
+ });
344
+
345
+ expect(items).toHaveLength(3);
346
+ expect(items[0].mode).toBe(null); // Epic (container, no mode)
347
+ expect(items[1].mode).toBe('stable'); // Child feature
348
+ expect(items[2].mode).toBe('stable'); // Child bug
349
+ });
350
+ });
351
+ });
@@ -0,0 +1,52 @@
1
+ // Migration: Add epic-level discovery tracking fields
2
+ // Created: 2025-10-29
3
+ // Purpose: Add needs_discovery and architectural_decision fields for epic-level discovery phase
4
+ // Related: Feature #196 - Epic-level discovery flag system
5
+
6
+ module.exports = {
7
+ id: '003-epic-discovery-fields',
8
+ description: 'Add needs_discovery and architectural_decision fields to work_items for epic discovery',
9
+
10
+ up: (db) => {
11
+ return new Promise((resolve, reject) => {
12
+ // Check if columns already exist
13
+ db.get(
14
+ `SELECT sql FROM sqlite_master WHERE type='table' AND name='work_items'`,
15
+ (err, row) => {
16
+ if (err) return reject(err);
17
+
18
+ const schema = row?.sql || '';
19
+ const hasNeedsDiscovery = schema.includes('needs_discovery');
20
+ const hasArchitecturalDecision = schema.includes('architectural_decision');
21
+
22
+ db.serialize(() => {
23
+ // Add needs_discovery if it doesn't exist
24
+ if (!hasNeedsDiscovery) {
25
+ db.run(
26
+ `ALTER TABLE work_items
27
+ ADD COLUMN needs_discovery INTEGER DEFAULT 0`,
28
+ (err) => {
29
+ if (err && !err.message.includes('duplicate column')) return reject(err);
30
+ }
31
+ );
32
+ }
33
+
34
+ // Add architectural_decision if it doesn't exist
35
+ if (!hasArchitecturalDecision) {
36
+ db.run(
37
+ `ALTER TABLE work_items
38
+ ADD COLUMN architectural_decision TEXT`,
39
+ (err) => {
40
+ if (err && !err.message.includes('duplicate column')) return reject(err);
41
+ else resolve();
42
+ }
43
+ );
44
+ } else {
45
+ resolve();
46
+ }
47
+ });
48
+ }
49
+ );
50
+ });
51
+ }
52
+ };
@@ -0,0 +1,32 @@
1
+ // Migration: Create discovery_decisions table for multi-aspect epic discovery
2
+ // Created: 2025-10-29
3
+ // Purpose: Support multiple architectural decisions per epic (architecture, design patterns, etc.)
4
+ // Related: Feature #196 - Epic-level discovery flag system refactor
5
+
6
+ module.exports = {
7
+ id: '004-discovery-decisions-table',
8
+ description: 'Create discovery_decisions table for tracking multiple decisions per epic',
9
+
10
+ up: (db) => {
11
+ return new Promise((resolve, reject) => {
12
+ db.serialize(() => {
13
+ // Create discovery_decisions table
14
+ db.run(
15
+ `CREATE TABLE IF NOT EXISTS discovery_decisions (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ work_item_id INTEGER NOT NULL,
18
+ aspect TEXT NOT NULL,
19
+ decision TEXT NOT NULL,
20
+ rationale TEXT NOT NULL,
21
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
22
+ FOREIGN KEY (work_item_id) REFERENCES work_items(id)
23
+ )`,
24
+ (err) => {
25
+ if (err) return reject(err);
26
+ else resolve();
27
+ }
28
+ );
29
+ });
30
+ });
31
+ }
32
+ };
@@ -0,0 +1,62 @@
1
+ // Migration: Migrate existing architectural_decision data to discovery_decisions table
2
+ // Created: 2025-10-29
3
+ // Purpose: Transfer data from single-field approach to multi-aspect relational table
4
+ // Related: Feature #196 - Epic-level discovery flag system refactor
5
+
6
+ module.exports = {
7
+ id: '005-migrate-decision-data',
8
+ description: 'Migrate existing architectural_decision data to discovery_decisions table',
9
+
10
+ up: (db) => {
11
+ return new Promise((resolve, reject) => {
12
+ // Get all work items with architectural decisions
13
+ db.all(
14
+ 'SELECT id, architectural_decision FROM work_items WHERE architectural_decision IS NOT NULL',
15
+ [],
16
+ (err, rows) => {
17
+ if (err) return reject(err);
18
+
19
+ // If no data to migrate, we're done
20
+ if (!rows || rows.length === 0) {
21
+ return resolve();
22
+ }
23
+
24
+ // Process each row
25
+ let completed = 0;
26
+ let hasError = false;
27
+
28
+ rows.forEach((row) => {
29
+ // Parse the old format: "Decision\n\nRationale: rationale_text"
30
+ const text = row.architectural_decision;
31
+ let decision = text;
32
+ let rationale = '';
33
+
34
+ const rationaleMatch = text.match(/^([\s\S]*?)\n\nRationale:\s*([\s\S]*)$/);
35
+ if (rationaleMatch) {
36
+ decision = rationaleMatch[1].trim();
37
+ rationale = rationaleMatch[2].trim();
38
+ }
39
+
40
+ // Insert into discovery_decisions table with aspect="Architecture" as default
41
+ db.run(
42
+ `INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale)
43
+ VALUES (?, ?, ?, ?)`,
44
+ [row.id, 'Architecture', decision, rationale],
45
+ (insertErr) => {
46
+ if (insertErr && !hasError) {
47
+ hasError = true;
48
+ return reject(insertErr);
49
+ }
50
+
51
+ completed++;
52
+ if (completed === rows.length && !hasError) {
53
+ resolve();
54
+ }
55
+ }
56
+ );
57
+ });
58
+ }
59
+ );
60
+ });
61
+ }
62
+ };
@@ -0,0 +1,61 @@
1
+ // Migration: Add phase field to work_items
2
+ // Created: 2025-10-31
3
+ // Purpose: Add phase tracking for Discovery → Implementation workflow
4
+ // Related: Feature #502 - Feature-level phase tracking
5
+
6
+ module.exports = {
7
+ id: '006-feature-phase-field',
8
+ description: 'Add phase field to work_items for Discovery → Implementation tracking',
9
+
10
+ up: (db) => {
11
+ return new Promise((resolve, reject) => {
12
+ // Check if phase column already exists
13
+ db.get(
14
+ `SELECT sql FROM sqlite_master WHERE type='table' AND name='work_items'`,
15
+ (err, row) => {
16
+ if (err) return reject(err);
17
+
18
+ const schema = row?.sql || '';
19
+ const hasPhase = schema.includes('phase');
20
+
21
+ if (hasPhase) {
22
+ console.log('Phase column already exists, skipping...');
23
+ return resolve();
24
+ }
25
+
26
+ db.serialize(() => {
27
+ // Add phase field
28
+ db.run(
29
+ `ALTER TABLE work_items ADD COLUMN phase TEXT DEFAULT 'discovery'`,
30
+ (err) => {
31
+ if (err && !err.message.includes('duplicate column')) {
32
+ return reject(err);
33
+ }
34
+ console.log('Added phase column to work_items');
35
+ }
36
+ );
37
+
38
+ // Set existing features to 'implementation' (backwards compatibility)
39
+ db.run(
40
+ `UPDATE work_items SET phase = 'implementation' WHERE type = 'feature'`,
41
+ (err) => {
42
+ if (err) return reject(err);
43
+ console.log('Set existing features to implementation phase');
44
+ }
45
+ );
46
+
47
+ // Clear phase for non-features (only features have phases)
48
+ db.run(
49
+ `UPDATE work_items SET phase = NULL WHERE type IN ('epic', 'chore', 'bug')`,
50
+ (err) => {
51
+ if (err) return reject(err);
52
+ console.log('Cleared phase for non-feature work items');
53
+ resolve();
54
+ }
55
+ );
56
+ });
57
+ }
58
+ );
59
+ });
60
+ }
61
+ };
@@ -0,0 +1,38 @@
1
+ // Migration: Add prototype tracking fields
2
+ // Created: 2025-10-31
3
+ // Purpose: Track prototypes, winners, and rationale at all levels
4
+ // Related: Feature #510 - Multi-level prototype tracking
5
+
6
+ module.exports = {
7
+ id: '007-prototype-tracking',
8
+ description: 'Add prototype_files and discovery_winner fields for prototype tracking',
9
+
10
+ up: (db) => {
11
+ return new Promise((resolve, reject) => {
12
+ db.serialize(() => {
13
+ // Add prototype_files field (JSON array of file paths)
14
+ db.run(
15
+ `ALTER TABLE work_items ADD COLUMN prototype_files TEXT`,
16
+ (err) => {
17
+ if (err && !err.message.includes('duplicate column')) {
18
+ return reject(err);
19
+ }
20
+ console.log('Added prototype_files column to work_items');
21
+ }
22
+ );
23
+
24
+ // Add discovery_winner field (path to winning prototype)
25
+ db.run(
26
+ `ALTER TABLE work_items ADD COLUMN discovery_winner TEXT`,
27
+ (err) => {
28
+ if (err && !err.message.includes('duplicate column')) {
29
+ return reject(err);
30
+ }
31
+ console.log('Added discovery_winner column to work_items');
32
+ resolve();
33
+ }
34
+ );
35
+ });
36
+ });
37
+ }
38
+ };
@@ -0,0 +1,24 @@
1
+ // Migration: Add scenario_file field for BDD scenario tracking
2
+ // Created: 2025-10-31
3
+ // Purpose: Track BDD scenario files generated during feature discovery
4
+ // Related: Feature #516 - BDD scenario integration with Discovery
5
+
6
+ module.exports = {
7
+ id: '008-scenario-file-field',
8
+ description: 'Add scenario_file field for BDD scenario tracking',
9
+
10
+ up: (db) => {
11
+ return new Promise((resolve, reject) => {
12
+ db.run(
13
+ `ALTER TABLE work_items ADD COLUMN scenario_file TEXT`,
14
+ (err) => {
15
+ if (err && !err.message.includes('duplicate column')) {
16
+ return reject(err);
17
+ }
18
+ console.log('Added scenario_file column to work_items');
19
+ resolve();
20
+ }
21
+ );
22
+ });
23
+ }
24
+ };
@@ -0,0 +1,74 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Run all pending migrations
6
+ * @param {Database} db - SQLite database instance
7
+ * @returns {Promise<void>}
8
+ */
9
+ async function runMigrations(db) {
10
+ // Create migrations table if it doesn't exist
11
+ await new Promise((resolve, reject) => {
12
+ db.run(`
13
+ CREATE TABLE IF NOT EXISTS migrations (
14
+ id TEXT PRIMARY KEY,
15
+ applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
16
+ )
17
+ `, (err) => {
18
+ if (err) reject(err);
19
+ else resolve();
20
+ });
21
+ });
22
+
23
+ // Get list of applied migrations
24
+ const applied = await new Promise((resolve, reject) => {
25
+ db.all('SELECT id FROM migrations', [], (err, rows) => {
26
+ if (err) reject(err);
27
+ else resolve(rows.map(r => r.id));
28
+ });
29
+ });
30
+
31
+ // Load all migration files
32
+ const migrationsDir = __dirname;
33
+ const files = fs.readdirSync(migrationsDir)
34
+ .filter(f => f.endsWith('.js') && f !== 'index.js' && !f.endsWith('.test.js'))
35
+ .sort();
36
+
37
+ // Run pending migrations
38
+ for (const file of files) {
39
+ const migration = require(path.join(migrationsDir, file));
40
+
41
+ if (applied.includes(migration.id)) {
42
+ continue; // Already applied
43
+ }
44
+
45
+ try {
46
+ // Double-check migration hasn't been applied by another process
47
+ const alreadyApplied = await new Promise((resolve, reject) => {
48
+ db.get('SELECT id FROM migrations WHERE id = ?', [migration.id], (err, row) => {
49
+ if (err) reject(err);
50
+ else resolve(!!row);
51
+ });
52
+ });
53
+
54
+ if (alreadyApplied) {
55
+ continue; // Applied by another process, skip
56
+ }
57
+
58
+ await migration.up(db);
59
+
60
+ // Record migration as applied (use OR IGNORE to handle race conditions)
61
+ await new Promise((resolve, reject) => {
62
+ db.run('INSERT OR IGNORE INTO migrations (id) VALUES (?)', [migration.id], (err) => {
63
+ if (err) reject(err);
64
+ else resolve();
65
+ });
66
+ });
67
+ } catch (err) {
68
+ console.error(`✗ Migration ${migration.id} failed:`, err.message);
69
+ throw err;
70
+ }
71
+ }
72
+ }
73
+
74
+ module.exports = { runMigrations };