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,432 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
generateChoresFromStandards,
|
|
3
|
-
generateInfrastructureChores
|
|
4
|
-
} = require('./production-chore-generator');
|
|
5
|
-
|
|
6
|
-
describe('production-chore-generator', () => {
|
|
7
|
-
describe('generateChoresFromStandards', () => {
|
|
8
|
-
test('generates chores grouped by domain', () => {
|
|
9
|
-
const standards = {
|
|
10
|
-
preset: 'production-saas',
|
|
11
|
-
standards: [
|
|
12
|
-
{
|
|
13
|
-
id: 'tls_enforced',
|
|
14
|
-
domain: 'security',
|
|
15
|
-
acceptance: 'HSTS ≥ 6mo; TLS ≥1.2',
|
|
16
|
-
reasoning: 'External-facing app with user data'
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: 'rate_limiting',
|
|
20
|
-
domain: 'security',
|
|
21
|
-
acceptance: 'Per-IP 100/min, per-token 1000/min',
|
|
22
|
-
reasoning: 'Public internet exposure'
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: 'performance_budgets',
|
|
26
|
-
domain: 'performance',
|
|
27
|
-
acceptance: 'p95 < 2s, p50 < 500ms',
|
|
28
|
-
reasoning: '500 users expect responsive UI'
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
34
|
-
|
|
35
|
-
expect(chores).toHaveLength(2); // security + performance
|
|
36
|
-
expect(chores[0].title).toBe('Add security hardening to Test Feature');
|
|
37
|
-
expect(chores[1].title).toBe('Add performance hardening to Test Feature');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('includes acceptance criteria in description', () => {
|
|
41
|
-
const standards = {
|
|
42
|
-
standards: [
|
|
43
|
-
{
|
|
44
|
-
id: 'tls_enforced',
|
|
45
|
-
domain: 'security',
|
|
46
|
-
acceptance: 'HSTS ≥ 6mo; TLS ≥1.2'
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
52
|
-
|
|
53
|
-
expect(chores[0].description).toContain('Acceptance: HSTS ≥ 6mo; TLS ≥1.2');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('includes reasoning when available', () => {
|
|
57
|
-
const standards = {
|
|
58
|
-
standards: [
|
|
59
|
-
{
|
|
60
|
-
id: 'tls_enforced',
|
|
61
|
-
domain: 'security',
|
|
62
|
-
acceptance: 'TLS 1.2',
|
|
63
|
-
reasoning: 'External-facing app with user data'
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
69
|
-
|
|
70
|
-
expect(chores[0].description).toContain('Why: External-facing app with user data');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('includes pattern when available', () => {
|
|
74
|
-
const standards = {
|
|
75
|
-
standards: [
|
|
76
|
-
{
|
|
77
|
-
id: 'tls_enforced',
|
|
78
|
-
domain: 'security',
|
|
79
|
-
acceptance: 'TLS 1.2',
|
|
80
|
-
pattern: 'Use Let\'s Encrypt or cloud-managed certificates'
|
|
81
|
-
}
|
|
82
|
-
]
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
86
|
-
|
|
87
|
-
expect(chores[0].description).toContain('Pattern: Use Let\'s Encrypt or cloud-managed certificates');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('formats standard IDs as readable text', () => {
|
|
91
|
-
const standards = {
|
|
92
|
-
standards: [
|
|
93
|
-
{
|
|
94
|
-
id: 'performance_budgets_enforced',
|
|
95
|
-
domain: 'performance',
|
|
96
|
-
acceptance: 'test'
|
|
97
|
-
}
|
|
98
|
-
]
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
102
|
-
|
|
103
|
-
expect(chores[0].description).toContain('performance budgets enforced');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('includes scenario steps in description', () => {
|
|
107
|
-
const standards = {
|
|
108
|
-
standards: [
|
|
109
|
-
{
|
|
110
|
-
id: 'test',
|
|
111
|
-
domain: 'security',
|
|
112
|
-
acceptance: 'test'
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
118
|
-
|
|
119
|
-
expect(chores[0].description).toContain('Scenario steps addressed:');
|
|
120
|
-
expect(chores[0].description).toContain('Given the system is deployed to production');
|
|
121
|
-
expect(chores[0].description).toContain('When security measures are validated');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('includes verification instructions', () => {
|
|
125
|
-
const standards = {
|
|
126
|
-
standards: [
|
|
127
|
-
{
|
|
128
|
-
id: 'test',
|
|
129
|
-
domain: 'security',
|
|
130
|
-
acceptance: 'test'
|
|
131
|
-
}
|
|
132
|
-
]
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
136
|
-
|
|
137
|
-
expect(chores[0].description).toContain('Verification:');
|
|
138
|
-
expect(chores[0].description).toContain('npx cucumber-js');
|
|
139
|
-
expect(chores[0].description).toContain('All production mode scenarios should pass');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('handles multiple standards in same domain', () => {
|
|
143
|
-
const standards = {
|
|
144
|
-
standards: [
|
|
145
|
-
{
|
|
146
|
-
id: 'standard1',
|
|
147
|
-
domain: 'security',
|
|
148
|
-
acceptance: 'test1'
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
id: 'standard2',
|
|
152
|
-
domain: 'security',
|
|
153
|
-
acceptance: 'test2'
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
id: 'standard3',
|
|
157
|
-
domain: 'security',
|
|
158
|
-
acceptance: 'test3'
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const chores = generateChoresFromStandards(standards, 'Test Feature');
|
|
164
|
-
|
|
165
|
-
expect(chores).toHaveLength(1); // All grouped into one security chore
|
|
166
|
-
expect(chores[0].description).toContain('standard1');
|
|
167
|
-
expect(chores[0].description).toContain('standard2');
|
|
168
|
-
expect(chores[0].description).toContain('standard3');
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('generateInfrastructureChores', () => {
|
|
173
|
-
test('filters standards by infrastructure scope', () => {
|
|
174
|
-
const standards = {
|
|
175
|
-
preset: 'production-saas',
|
|
176
|
-
standards: [
|
|
177
|
-
{
|
|
178
|
-
id: 'database_backups',
|
|
179
|
-
domain: 'infrastructure',
|
|
180
|
-
scope: 'infrastructure',
|
|
181
|
-
acceptance: 'Automated daily backups with 30-day retention'
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
id: 'tls_enforced',
|
|
185
|
-
domain: 'security',
|
|
186
|
-
scope: 'feature',
|
|
187
|
-
acceptance: 'HSTS ≥ 6mo; TLS ≥1.2'
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
id: 'monitoring_setup',
|
|
191
|
-
domain: 'infrastructure',
|
|
192
|
-
scope: 'infrastructure',
|
|
193
|
-
acceptance: 'Error tracking and APM configured'
|
|
194
|
-
}
|
|
195
|
-
]
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const chores = generateInfrastructureChores(standards);
|
|
199
|
-
|
|
200
|
-
expect(chores).toHaveLength(2); // Only infrastructure-scoped
|
|
201
|
-
expect(chores[0].title).toBe('Database Backups');
|
|
202
|
-
expect(chores[1].title).toBe('Monitoring Setup');
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test('creates one chore per standard (not grouped by domain)', () => {
|
|
206
|
-
const standards = {
|
|
207
|
-
standards: [
|
|
208
|
-
{
|
|
209
|
-
id: 'tls_enforced',
|
|
210
|
-
domain: 'security',
|
|
211
|
-
scope: 'infrastructure',
|
|
212
|
-
acceptance: 'HSTS ≥ 6mo'
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
id: 'rate_limiting',
|
|
216
|
-
domain: 'security',
|
|
217
|
-
scope: 'infrastructure',
|
|
218
|
-
acceptance: 'Per-IP 100/min'
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
id: 'monitoring_setup',
|
|
222
|
-
domain: 'infrastructure',
|
|
223
|
-
scope: 'infrastructure',
|
|
224
|
-
acceptance: 'APM configured'
|
|
225
|
-
}
|
|
226
|
-
]
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const chores = generateInfrastructureChores(standards);
|
|
230
|
-
|
|
231
|
-
expect(chores).toHaveLength(3); // One per standard, not grouped
|
|
232
|
-
expect(chores[0].title).toBe('Tls Enforced');
|
|
233
|
-
expect(chores[1].title).toBe('Rate Limiting');
|
|
234
|
-
expect(chores[2].title).toBe('Monitoring Setup');
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test('includes acceptance criteria in chore description', () => {
|
|
238
|
-
const standards = {
|
|
239
|
-
standards: [
|
|
240
|
-
{
|
|
241
|
-
id: 'database_backups',
|
|
242
|
-
domain: 'infrastructure',
|
|
243
|
-
scope: 'infrastructure',
|
|
244
|
-
acceptance: 'Automated daily backups with 30-day retention'
|
|
245
|
-
}
|
|
246
|
-
]
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const chores = generateInfrastructureChores(standards);
|
|
250
|
-
|
|
251
|
-
expect(chores[0].description).toContain('Automated daily backups with 30-day retention');
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test('includes reasoning when available', () => {
|
|
255
|
-
const standards = {
|
|
256
|
-
standards: [
|
|
257
|
-
{
|
|
258
|
-
id: 'database_backups',
|
|
259
|
-
domain: 'infrastructure',
|
|
260
|
-
scope: 'infrastructure',
|
|
261
|
-
acceptance: 'Daily backups',
|
|
262
|
-
reasoning: 'Data loss would be catastrophic'
|
|
263
|
-
}
|
|
264
|
-
]
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const chores = generateInfrastructureChores(standards);
|
|
268
|
-
|
|
269
|
-
expect(chores[0].description).toContain('Why:\nData loss would be catastrophic');
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test('includes pattern guidance when available', () => {
|
|
273
|
-
const standards = {
|
|
274
|
-
standards: [
|
|
275
|
-
{
|
|
276
|
-
id: 'database_backups',
|
|
277
|
-
domain: 'infrastructure',
|
|
278
|
-
scope: 'infrastructure',
|
|
279
|
-
acceptance: 'Daily backups',
|
|
280
|
-
pattern: 'Use pg_dump with --clean flag'
|
|
281
|
-
}
|
|
282
|
-
]
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const chores = generateInfrastructureChores(standards);
|
|
286
|
-
|
|
287
|
-
expect(chores[0].description).toContain('Pattern:\nUse pg_dump with --clean flag');
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test('formats standard ID as readable title', () => {
|
|
291
|
-
const standards = {
|
|
292
|
-
standards: [
|
|
293
|
-
{
|
|
294
|
-
id: 'automated_database_backups_configured',
|
|
295
|
-
domain: 'infrastructure',
|
|
296
|
-
scope: 'infrastructure',
|
|
297
|
-
acceptance: 'test'
|
|
298
|
-
}
|
|
299
|
-
]
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const chores = generateInfrastructureChores(standards);
|
|
303
|
-
|
|
304
|
-
expect(chores[0].title).toBe('Automated Database Backups Configured');
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test('excludes feature-scoped standards', () => {
|
|
308
|
-
const standards = {
|
|
309
|
-
standards: [
|
|
310
|
-
{
|
|
311
|
-
id: 'input_validation',
|
|
312
|
-
domain: 'security',
|
|
313
|
-
scope: 'feature',
|
|
314
|
-
acceptance: 'All inputs validated'
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
id: 'sql_injection_prevention',
|
|
318
|
-
domain: 'security',
|
|
319
|
-
scope: 'feature',
|
|
320
|
-
acceptance: 'Parameterized queries'
|
|
321
|
-
}
|
|
322
|
-
]
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const chores = generateInfrastructureChores(standards);
|
|
326
|
-
|
|
327
|
-
expect(chores).toHaveLength(0); // No infrastructure-scoped standards
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test('handles standards without scope field', () => {
|
|
331
|
-
const standards = {
|
|
332
|
-
standards: [
|
|
333
|
-
{
|
|
334
|
-
id: 'database_backups',
|
|
335
|
-
domain: 'infrastructure',
|
|
336
|
-
acceptance: 'Daily backups'
|
|
337
|
-
// Missing scope field
|
|
338
|
-
}
|
|
339
|
-
]
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
const chores = generateInfrastructureChores(standards);
|
|
343
|
-
|
|
344
|
-
expect(chores).toHaveLength(0); // No scope means not infrastructure
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test('validates standards object structure', () => {
|
|
348
|
-
expect(() => {
|
|
349
|
-
generateInfrastructureChores(null);
|
|
350
|
-
}).toThrow('Standards object is required');
|
|
351
|
-
|
|
352
|
-
expect(() => {
|
|
353
|
-
generateInfrastructureChores({});
|
|
354
|
-
}).toThrow('Standards object missing "standards" array');
|
|
355
|
-
|
|
356
|
-
expect(() => {
|
|
357
|
-
generateInfrastructureChores({ standards: 'not-an-array' });
|
|
358
|
-
}).toThrow('Standards "standards" field must be an array');
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test('validates individual standard structure', () => {
|
|
362
|
-
const standards = {
|
|
363
|
-
standards: [
|
|
364
|
-
{
|
|
365
|
-
// Missing id
|
|
366
|
-
domain: 'infrastructure',
|
|
367
|
-
scope: 'infrastructure',
|
|
368
|
-
acceptance: 'test'
|
|
369
|
-
}
|
|
370
|
-
]
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
expect(() => {
|
|
374
|
-
generateInfrastructureChores(standards);
|
|
375
|
-
}).toThrow('Standard missing required field "id"');
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test('includes domain in chore description', () => {
|
|
379
|
-
const standards = {
|
|
380
|
-
standards: [
|
|
381
|
-
{
|
|
382
|
-
id: 'monitoring_setup',
|
|
383
|
-
domain: 'infrastructure',
|
|
384
|
-
scope: 'infrastructure',
|
|
385
|
-
acceptance: 'APM configured'
|
|
386
|
-
}
|
|
387
|
-
]
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
const chores = generateInfrastructureChores(standards);
|
|
391
|
-
|
|
392
|
-
expect(chores[0].domain).toBe('infrastructure');
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
test('handles mixed scope standards correctly', () => {
|
|
396
|
-
const standards = {
|
|
397
|
-
standards: [
|
|
398
|
-
{
|
|
399
|
-
id: 'infra1',
|
|
400
|
-
domain: 'security',
|
|
401
|
-
scope: 'infrastructure',
|
|
402
|
-
acceptance: 'test1'
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
id: 'feature1',
|
|
406
|
-
domain: 'security',
|
|
407
|
-
scope: 'feature',
|
|
408
|
-
acceptance: 'test2'
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
id: 'infra2',
|
|
412
|
-
domain: 'performance',
|
|
413
|
-
scope: 'infrastructure',
|
|
414
|
-
acceptance: 'test3'
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
id: 'feature2',
|
|
418
|
-
domain: 'performance',
|
|
419
|
-
scope: 'feature',
|
|
420
|
-
acceptance: 'test4'
|
|
421
|
-
}
|
|
422
|
-
]
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
const chores = generateInfrastructureChores(standards);
|
|
426
|
-
|
|
427
|
-
expect(chores).toHaveLength(2);
|
|
428
|
-
expect(chores[0].title).toBe('Infra1');
|
|
429
|
-
expect(chores[1].title).toBe('Infra2');
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
});
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
detectContext,
|
|
3
|
-
hasProductionScenarios,
|
|
4
|
-
hasProductionChores,
|
|
5
|
-
getTimeSinceStableCompletion
|
|
6
|
-
} = require('./production-context-detector');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { getDb } = require('./database');
|
|
10
|
-
|
|
11
|
-
describe('production-context-detector', () => {
|
|
12
|
-
let testDir;
|
|
13
|
-
let db;
|
|
14
|
-
let featureId;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Create test directory
|
|
18
|
-
testDir = path.join(__dirname, '../test-tmp', `context-detector-${Date.now()}`);
|
|
19
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
20
|
-
process.chdir(testDir);
|
|
21
|
-
|
|
22
|
-
// Initialize database
|
|
23
|
-
db = getDb();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach((done) => {
|
|
27
|
-
if (db) {
|
|
28
|
-
db.close(() => {
|
|
29
|
-
if (testDir && fs.existsSync(testDir)) {
|
|
30
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
31
|
-
}
|
|
32
|
-
done();
|
|
33
|
-
});
|
|
34
|
-
} else {
|
|
35
|
-
if (testDir && fs.existsSync(testDir)) {
|
|
36
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
37
|
-
}
|
|
38
|
-
done();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('hasProductionScenarios', () => {
|
|
43
|
-
test('returns false when no scenario file', async () => {
|
|
44
|
-
const result = await hasProductionScenarios(null);
|
|
45
|
-
expect(result).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('returns false when scenario file does not exist', async () => {
|
|
49
|
-
const result = await hasProductionScenarios('nonexistent.feature');
|
|
50
|
-
expect(result).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('returns true when file contains "# PRODUCTION MODE"', async () => {
|
|
54
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
55
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\n# PRODUCTION MODE\nScenario: Production test\n');
|
|
56
|
-
|
|
57
|
-
const result = await hasProductionScenarios('test.feature');
|
|
58
|
-
expect(result).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('returns true when file contains "production mode"', async () => {
|
|
62
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
63
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\nScenario: Test (production mode)\n');
|
|
64
|
-
|
|
65
|
-
const result = await hasProductionScenarios('test.feature');
|
|
66
|
-
expect(result).toBe(true);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('returns false when file has no production markers', async () => {
|
|
70
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
71
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\nScenario: Happy path\n');
|
|
72
|
-
|
|
73
|
-
const result = await hasProductionScenarios('test.feature');
|
|
74
|
-
expect(result).toBe(false);
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('hasProductionChores', () => {
|
|
79
|
-
beforeEach((done) => {
|
|
80
|
-
// Create a test feature
|
|
81
|
-
db.run(
|
|
82
|
-
`INSERT INTO work_items (type, title, status, mode, phase) VALUES (?, ?, ?, ?, ?)`,
|
|
83
|
-
['feature', 'Test Feature', 'done', 'stable', 'implementation'],
|
|
84
|
-
function(err) {
|
|
85
|
-
if (err) return done(err);
|
|
86
|
-
featureId = this.lastID;
|
|
87
|
-
done();
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('returns false when no production chores exist', async () => {
|
|
93
|
-
const result = await hasProductionChores(featureId);
|
|
94
|
-
expect(result).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test('returns true when production chores exist', async () => {
|
|
98
|
-
// Create production chore
|
|
99
|
-
await new Promise((resolve, reject) => {
|
|
100
|
-
db.run(
|
|
101
|
-
`INSERT INTO work_items (type, title, parent_id, status, mode) VALUES (?, ?, ?, ?, ?)`,
|
|
102
|
-
['chore', 'Production chore', featureId, 'todo', 'production'],
|
|
103
|
-
(err) => {
|
|
104
|
-
if (err) return reject(err);
|
|
105
|
-
resolve();
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const result = await hasProductionChores(featureId);
|
|
111
|
-
expect(result).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('getTimeSinceStableCompletion', () => {
|
|
116
|
-
beforeEach((done) => {
|
|
117
|
-
// Create a test feature
|
|
118
|
-
db.run(
|
|
119
|
-
`INSERT INTO work_items (type, title, status, mode, phase) VALUES (?, ?, ?, ?, ?)`,
|
|
120
|
-
['feature', 'Test Feature', 'done', 'stable', 'implementation'],
|
|
121
|
-
function(err) {
|
|
122
|
-
if (err) return done(err);
|
|
123
|
-
featureId = this.lastID;
|
|
124
|
-
done();
|
|
125
|
-
}
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('returns 0 when stable chore completed today', async () => {
|
|
130
|
-
// Create stable chore completed today
|
|
131
|
-
await new Promise((resolve, reject) => {
|
|
132
|
-
db.run(
|
|
133
|
-
`INSERT INTO work_items (type, title, parent_id, status, mode, completed_at)
|
|
134
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
135
|
-
['chore', 'Stable chore', featureId, 'done', 'stable', new Date().toISOString()],
|
|
136
|
-
(err) => {
|
|
137
|
-
if (err) return reject(err);
|
|
138
|
-
resolve();
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const result = await getTimeSinceStableCompletion(featureId);
|
|
144
|
-
expect(result).toBe(0);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test('returns correct days when stable chore completed 7 days ago', async () => {
|
|
148
|
-
const sevenDaysAgo = new Date();
|
|
149
|
-
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
150
|
-
|
|
151
|
-
// Create stable chore completed 7 days ago
|
|
152
|
-
await new Promise((resolve, reject) => {
|
|
153
|
-
db.run(
|
|
154
|
-
`INSERT INTO work_items (type, title, parent_id, status, mode, completed_at)
|
|
155
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
156
|
-
['chore', 'Stable chore', featureId, 'done', 'stable', sevenDaysAgo.toISOString()],
|
|
157
|
-
(err) => {
|
|
158
|
-
if (err) return reject(err);
|
|
159
|
-
resolve();
|
|
160
|
-
}
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const result = await getTimeSinceStableCompletion(featureId);
|
|
165
|
-
expect(result).toBe(7);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('detectContext', () => {
|
|
170
|
-
beforeEach((done) => {
|
|
171
|
-
// Create a test feature
|
|
172
|
-
const yesterday = new Date();
|
|
173
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
174
|
-
|
|
175
|
-
db.run(
|
|
176
|
-
`INSERT INTO work_items (type, title, status, mode, phase, completed_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
177
|
-
['feature', 'Test Feature', 'done', 'stable', 'implementation', yesterday.toISOString()],
|
|
178
|
-
function(err) {
|
|
179
|
-
if (err) return done(err);
|
|
180
|
-
featureId = this.lastID;
|
|
181
|
-
done();
|
|
182
|
-
}
|
|
183
|
-
);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('detects SCENARIO_A when fresh from stable with production content', async () => {
|
|
187
|
-
// Create scenario file with production markers
|
|
188
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
189
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\n# PRODUCTION MODE\nScenario: Production test\n');
|
|
190
|
-
|
|
191
|
-
// Update feature with scenario file
|
|
192
|
-
await new Promise((resolve, reject) => {
|
|
193
|
-
db.run(
|
|
194
|
-
`UPDATE work_items SET scenario_file = ? WHERE id = ?`,
|
|
195
|
-
['test.feature', featureId],
|
|
196
|
-
(err) => {
|
|
197
|
-
if (err) return reject(err);
|
|
198
|
-
resolve();
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Create production chore
|
|
204
|
-
await new Promise((resolve, reject) => {
|
|
205
|
-
db.run(
|
|
206
|
-
`INSERT INTO work_items (type, title, parent_id, status, mode) VALUES (?, ?, ?, ?, ?)`,
|
|
207
|
-
['chore', 'Production chore', featureId, 'todo', 'production'],
|
|
208
|
-
(err) => {
|
|
209
|
-
if (err) return reject(err);
|
|
210
|
-
resolve();
|
|
211
|
-
}
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const result = await detectContext(featureId);
|
|
216
|
-
expect(result).toBe('SCENARIO_A');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('detects SCENARIO_B when gap in time', async () => {
|
|
220
|
-
// Create scenario file with production markers
|
|
221
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
222
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\n# PRODUCTION MODE\nScenario: Production test\n');
|
|
223
|
-
|
|
224
|
-
// Update feature with old date
|
|
225
|
-
const oldDate = new Date();
|
|
226
|
-
oldDate.setDate(oldDate.getDate() - 10);
|
|
227
|
-
|
|
228
|
-
await new Promise((resolve, reject) => {
|
|
229
|
-
db.run(
|
|
230
|
-
`UPDATE work_items SET scenario_file = ?, completed_at = ? WHERE id = ?`,
|
|
231
|
-
['test.feature', oldDate.toISOString(), featureId],
|
|
232
|
-
(err) => {
|
|
233
|
-
if (err) return reject(err);
|
|
234
|
-
resolve();
|
|
235
|
-
}
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// Create production chore
|
|
240
|
-
await new Promise((resolve, reject) => {
|
|
241
|
-
db.run(
|
|
242
|
-
`INSERT INTO work_items (type, title, parent_id, status, mode) VALUES (?, ?, ?, ?, ?)`,
|
|
243
|
-
['chore', 'Production chore', featureId, 'todo', 'production'],
|
|
244
|
-
(err) => {
|
|
245
|
-
if (err) return reject(err);
|
|
246
|
-
resolve();
|
|
247
|
-
}
|
|
248
|
-
);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const result = await detectContext(featureId);
|
|
252
|
-
expect(result).toBe('SCENARIO_B');
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test('detects SCENARIO_C when no production content exists', async () => {
|
|
256
|
-
// Create scenario file WITHOUT production markers
|
|
257
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
258
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\nScenario: Happy path\n');
|
|
259
|
-
|
|
260
|
-
await new Promise((resolve, reject) => {
|
|
261
|
-
db.run(
|
|
262
|
-
`UPDATE work_items SET scenario_file = ? WHERE id = ?`,
|
|
263
|
-
['test.feature', featureId],
|
|
264
|
-
(err) => {
|
|
265
|
-
if (err) return reject(err);
|
|
266
|
-
resolve();
|
|
267
|
-
}
|
|
268
|
-
);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// No production chores created
|
|
272
|
-
|
|
273
|
-
const result = await detectContext(featureId);
|
|
274
|
-
expect(result).toBe('SCENARIO_C');
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
});
|