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.
- package/.nvmrc +1 -0
- package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
- package/docs/DECISIONS.md +10 -12
- package/docs/NODE_VERSION.md +83 -0
- package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
- package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
- package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
- package/hooks/post-checkout +17 -0
- package/hooks/post-merge +17 -0
- package/hooks/pre-commit +30 -0
- package/jettypod.js +259 -120
- package/lib/coverage-tracker.js +218 -0
- package/lib/database.js +2 -0
- package/lib/db-export.js +192 -0
- package/lib/db-import.js +193 -0
- package/lib/external-transition-handler.js +32 -0
- package/lib/git-hook-helpers.js +174 -0
- package/lib/git-root.js +90 -0
- package/lib/infrastructure-chore-generator.js +45 -0
- package/lib/install-hooks.js +52 -0
- package/lib/jettypod-backup.js +238 -0
- package/lib/merge-lock.js +193 -0
- package/lib/migrations/012-add-worktree-path.js +38 -0
- package/lib/migrations/013-worktrees-table.js +86 -0
- package/lib/migrations/014-migrate-worktree-data.js +161 -0
- package/lib/migrations/015-merge-locks-table.js +67 -0
- package/lib/pattern-finder.js +152 -0
- package/lib/process-manager.js +140 -0
- package/lib/production-standards-reader.js +13 -2
- package/lib/production-standards-writer.js +85 -0
- package/lib/skills/feature-planning/dry-run-validator.js +135 -0
- package/lib/skills/feature-planning/validation-formatter.js +160 -0
- package/lib/smart-conflict-detection.js +168 -0
- package/lib/smart-fetch-rebase.js +614 -0
- package/lib/step-definition-parser.js +76 -0
- package/lib/unit-test-generator.js +232 -0
- package/lib/verification-command-generator.js +66 -0
- package/lib/worktree-diagnostics.js +413 -0
- package/lib/worktree-facade.js +174 -0
- package/lib/worktree-manager.js +636 -0
- package/lib/worktree-reconciler.js +429 -0
- package/package.json +30 -3
- package/skills-templates/external-transition/SKILL.md +34 -3
- package/skills-templates/feature-planning/SKILL.md +190 -24
- package/skills-templates/production-mode/SKILL.md +127 -9
- package/skills-templates/speed-mode/SKILL.md +454 -51
- package/skills-templates/stable-mode/SKILL.md +285 -76
- package/.claude/PROTECT_SKILLS.md +0 -28
- package/.claude/settings.json +0 -24
- package/.claude/settings.local.json +0 -16
- package/.claude/skills/epic-planning/SKILL.md +0 -297
- package/.claude/skills/external-transition/SKILL.md +0 -384
- package/.claude/skills/feature-planning/SKILL.md +0 -464
- package/.claude/skills/production-mode/SKILL.md +0 -369
- package/.claude/skills/speed-mode/SKILL.md +0 -481
- package/.claude/skills/stable-mode/SKILL.md +0 -713
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
- package/.devpod/current-work.json +0 -10
- package/.devpod/work.db +0 -0
- package/.github/workflows/test-safety.yml +0 -85
- package/.jettypod/config.json +0 -5
- package/.jettypod/current-work.json +0 -10
- package/.jettypod/hooks/README.md +0 -77
- package/.jettypod/hooks/protect-claude-md.js +0 -338
- package/.jettypod/test-work.db +0 -0
- package/.jettypod/work.db +0 -0
- package/CLAUDE.md +0 -49
- package/SPEED-STABLE-AUDIT.md +0 -853
- package/SYSTEM-BEHAVIOR.md +0 -2199
- package/TEST_SAFETY_AUDIT.md +0 -314
- package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
- package/cucumber-report.html +0 -45
- package/dist/devpod-linux +0 -0
- package/dist/devpod-macos +0 -0
- package/dist/devpod-win.exe +0 -0
- package/docs/features/jettypod-standards-explained.md +0 -543
- package/docs/features/standards-inventory.md +0 -257
- package/features/auto-generate-production-chores.feature +0 -13
- package/features/backlog-command.feature +0 -26
- package/features/backlog-filtering-production.feature +0 -10
- package/features/claude-md-protection/steps.js +0 -498
- package/features/decisions/index.js +0 -490
- package/features/decisions/index.test.js +0 -208
- package/features/fix-text-wrapping.feature +0 -42
- package/features/git-hooks/git-hooks.feature +0 -30
- package/features/git-hooks/index.js +0 -93
- package/features/git-hooks/index.test.js +0 -137
- package/features/git-hooks/post-commit +0 -56
- package/features/git-hooks/post-merge +0 -47
- package/features/git-hooks/pre-commit +0 -28
- package/features/git-hooks/simple-steps.js +0 -53
- package/features/git-hooks/simple-test.feature +0 -10
- package/features/git-hooks/steps.js +0 -196
- package/features/jettypod-update-command.feature +0 -46
- package/features/mode-prompts/index.js +0 -95
- package/features/mode-prompts/simple-steps.js +0 -44
- package/features/mode-prompts/simple-test.feature +0 -9
- package/features/mode-prompts/validation.test.js +0 -120
- package/features/multiple-claude-instances.feature +0 -121
- package/features/production-mode-skill.feature +0 -121
- package/features/refactor-mode/steps.js +0 -217
- package/features/refactor-mode.feature +0 -49
- package/features/simplify-external-transition.feature +0 -166
- package/features/skills-update/index.test.js +0 -216
- package/features/step_definitions/backlog-command.steps.js +0 -37
- package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
- package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
- package/features/step_definitions/production-mode-skill.steps.js +0 -862
- package/features/step_definitions/simplify-external-transition.steps.js +0 -370
- package/features/step_definitions/terminal-logo.steps.js +0 -145
- package/features/step_definitions/update-command.steps.js +0 -183
- package/features/support/hooks.js +0 -9
- package/features/terminal-logo/index.js +0 -39
- package/features/terminal-logo/terminal-logo.feature +0 -30
- package/features/update-command/index.js +0 -181
- package/features/update-command/index.test.js +0 -225
- package/features/work-commands/bug-workflow-display.feature +0 -22
- package/features/work-commands/index.js +0 -498
- package/features/work-commands/simple-steps.js +0 -69
- package/features/work-commands/stable-tests.feature +0 -57
- package/features/work-commands/steps.js +0 -1174
- package/features/work-commands/validation.test.js +0 -88
- package/features/work-commands/work-commands.feature +0 -13
- package/features/work-tracking/discovery-validation.test.js +0 -228
- package/features/work-tracking/index.js +0 -1921
- package/features/work-tracking/mode-required.feature +0 -112
- package/features/work-tracking/phase-tracking.test.js +0 -482
- package/features/work-tracking/prototype-tracking.test.js +0 -485
- package/features/work-tracking/tree-view.test.js +0 -310
- package/features/work-tracking/work-set-mode.feature +0 -71
- package/features/work-tracking/work-start-mode.feature +0 -88
- package/full-test.txt +0 -0
- package/lib/bug-workflow.test.js +0 -177
- package/lib/claudemd.test.js +0 -195
- package/lib/config.test.js +0 -511
- package/lib/constants.test.js +0 -164
- package/lib/current-work.test.js +0 -146
- package/lib/database-project-config.test.js +0 -111
- package/lib/database.test.js +0 -106
- package/lib/decisions-generator.test.js +0 -457
- package/lib/decisions-helpers.test.js +0 -310
- package/lib/git-coordinator.js +0 -167
- package/lib/git.test.js +0 -145
- package/lib/migrations/002-default-work-item-modes.test.js +0 -351
- package/lib/production-chore-generator.test.js +0 -432
- package/lib/production-context-detector.test.js +0 -277
- package/lib/production-scenario-appender.test.js +0 -235
- package/lib/production-scenario-validator.test.js +0 -246
- package/lib/production-standards-reader.test.js +0 -270
- package/lib/project-state.test.js +0 -92
- package/lib/push-queue.js +0 -417
- package/lib/queue-processor.js +0 -74
- package/lib/test-helpers.js +0 -202
- package/lib/test-helpers.test.js +0 -255
- package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
- package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
- package/prototypes/2025-01-11-production-mode-guided.js +0 -217
- package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
- package/prototypes/2025-01-11-production-standards-example.md +0 -204
- package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
- package/prototypes/test/index.html +0 -1
- package/setup-dist-repo.sh +0 -68
- package/test-production-standards-engine.js +0 -130
- package/test-results.json +0 -2195
- package/test-safety-check.sh +0 -80
- package/work-item-tracking-plan.md +0 -199
- /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
|
-
});
|