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,235 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
appendScenarios,
|
|
3
|
-
formatScenarioForAppend,
|
|
4
|
-
generateTitleFromStandard,
|
|
5
|
-
extractScenarioTitles,
|
|
6
|
-
extractScenarioTitle,
|
|
7
|
-
deduplicateScenarios
|
|
8
|
-
} = require('./production-scenario-appender');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
describe('production-scenario-appender', () => {
|
|
13
|
-
let testDir;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
testDir = path.join(__dirname, '../test-tmp', `scenario-appender-${Date.now()}`);
|
|
17
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
18
|
-
process.chdir(testDir);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
if (testDir && fs.existsSync(testDir)) {
|
|
23
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('generateTitleFromStandard', () => {
|
|
28
|
-
test('converts snake_case to Title Case', () => {
|
|
29
|
-
const standard = {
|
|
30
|
-
id: 'tls_enforced',
|
|
31
|
-
domain: 'security'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const title = generateTitleFromStandard(standard);
|
|
35
|
-
expect(title).toBe('Security - Tls Enforced (production mode)');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('handles multi-word IDs', () => {
|
|
39
|
-
const standard = {
|
|
40
|
-
id: 'performance_budgets_enforced',
|
|
41
|
-
domain: 'performance'
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const title = generateTitleFromStandard(standard);
|
|
45
|
-
expect(title).toBe('Performance - Performance Budgets Enforced (production mode)');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('formatScenarioForAppend', () => {
|
|
50
|
-
test('formats security standard as scenario', () => {
|
|
51
|
-
const standard = {
|
|
52
|
-
id: 'tls_enforced',
|
|
53
|
-
domain: 'security',
|
|
54
|
-
acceptance: 'HSTS ≥ 6mo; TLS ≥1.2'
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const scenario = formatScenarioForAppend(standard);
|
|
58
|
-
|
|
59
|
-
expect(scenario).toContain('Scenario: Security - Tls Enforced');
|
|
60
|
-
expect(scenario).toContain('Given the system is deployed to production');
|
|
61
|
-
expect(scenario).toContain('Then HSTS ≥ 6mo; TLS ≥1.2');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('formats performance standard as scenario', () => {
|
|
65
|
-
const standard = {
|
|
66
|
-
id: 'performance_budgets',
|
|
67
|
-
domain: 'performance',
|
|
68
|
-
acceptance: 'p95 < 2s'
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const scenario = formatScenarioForAppend(standard);
|
|
72
|
-
|
|
73
|
-
expect(scenario).toContain('Scenario: Performance - Performance Budgets');
|
|
74
|
-
expect(scenario).toContain('Given the system is under production load');
|
|
75
|
-
expect(scenario).toContain('Then p95 < 2s');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('extractScenarioTitles', () => {
|
|
80
|
-
test('extracts titles from content', () => {
|
|
81
|
-
const content = `Feature: Test
|
|
82
|
-
|
|
83
|
-
Scenario: User logs in
|
|
84
|
-
Given credentials
|
|
85
|
-
When submitting
|
|
86
|
-
Then success
|
|
87
|
-
|
|
88
|
-
Scenario: Password reset
|
|
89
|
-
Given forgot password
|
|
90
|
-
When requesting reset
|
|
91
|
-
Then email sent`;
|
|
92
|
-
|
|
93
|
-
const titles = extractScenarioTitles(content);
|
|
94
|
-
|
|
95
|
-
expect(titles).toHaveLength(2);
|
|
96
|
-
expect(titles).toContain('user logs in');
|
|
97
|
-
expect(titles).toContain('password reset');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('returns empty array for no scenarios', () => {
|
|
101
|
-
const content = 'Feature: Test\n\nNo scenarios here';
|
|
102
|
-
|
|
103
|
-
const titles = extractScenarioTitles(content);
|
|
104
|
-
expect(titles).toEqual([]);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('extractScenarioTitle', () => {
|
|
109
|
-
test('extracts title from scenario string', () => {
|
|
110
|
-
const scenario = `Scenario: User logs in
|
|
111
|
-
Given credentials
|
|
112
|
-
Then success`;
|
|
113
|
-
|
|
114
|
-
const title = extractScenarioTitle(scenario);
|
|
115
|
-
expect(title).toBe('User logs in');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('returns null for invalid scenario', () => {
|
|
119
|
-
const scenario = 'Not a scenario';
|
|
120
|
-
|
|
121
|
-
const title = extractScenarioTitle(scenario);
|
|
122
|
-
expect(title).toBeNull();
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('appendScenarios', () => {
|
|
127
|
-
test('throws error when file not found', async () => {
|
|
128
|
-
await expect(appendScenarios('nonexistent.feature', []))
|
|
129
|
-
.rejects.toThrow('Scenario file not found');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('adds production mode header if not present', async () => {
|
|
133
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
134
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\nScenario: Happy path\n Given test\n');
|
|
135
|
-
|
|
136
|
-
const newScenarios = ['Scenario: New scenario\n Given production\n Then test'];
|
|
137
|
-
|
|
138
|
-
await appendScenarios('test.feature', newScenarios);
|
|
139
|
-
|
|
140
|
-
const content = fs.readFileSync(scenarioFile, 'utf8');
|
|
141
|
-
expect(content).toContain('# PRODUCTION MODE SCENARIOS');
|
|
142
|
-
expect(content).toContain('Scenario: New scenario');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test('does not duplicate production mode header', async () => {
|
|
146
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
147
|
-
fs.writeFileSync(scenarioFile, `Feature: Test
|
|
148
|
-
|
|
149
|
-
# PRODUCTION MODE SCENARIOS
|
|
150
|
-
|
|
151
|
-
Scenario: Existing production scenario
|
|
152
|
-
Given test
|
|
153
|
-
`);
|
|
154
|
-
|
|
155
|
-
const newScenarios = ['Scenario: New scenario\n Given production\n Then test'];
|
|
156
|
-
|
|
157
|
-
await appendScenarios('test.feature', newScenarios);
|
|
158
|
-
|
|
159
|
-
const content = fs.readFileSync(scenarioFile, 'utf8');
|
|
160
|
-
const headerCount = (content.match(/# PRODUCTION MODE SCENARIOS/g) || []).length;
|
|
161
|
-
expect(headerCount).toBe(1);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('deduplicates scenarios by title', async () => {
|
|
165
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
166
|
-
fs.writeFileSync(scenarioFile, `Feature: Test
|
|
167
|
-
|
|
168
|
-
Scenario: Existing scenario
|
|
169
|
-
Given test
|
|
170
|
-
`);
|
|
171
|
-
|
|
172
|
-
const newScenarios = [
|
|
173
|
-
'Scenario: Existing scenario\n Given duplicate\n Then skip',
|
|
174
|
-
'Scenario: New scenario\n Given production\n Then test'
|
|
175
|
-
];
|
|
176
|
-
|
|
177
|
-
const result = await appendScenarios('test.feature', newScenarios);
|
|
178
|
-
|
|
179
|
-
expect(result.added).toBe(1);
|
|
180
|
-
expect(result.skipped).toBe(1);
|
|
181
|
-
|
|
182
|
-
const content = fs.readFileSync(scenarioFile, 'utf8');
|
|
183
|
-
expect(content).toContain('Scenario: New scenario');
|
|
184
|
-
expect(content.match(/Scenario: Existing scenario/g)).toHaveLength(1);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('deduplicateScenarios', () => {
|
|
189
|
-
test('removes duplicate scenarios', async () => {
|
|
190
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
191
|
-
fs.writeFileSync(scenarioFile, `Feature: Test
|
|
192
|
-
|
|
193
|
-
Scenario: Test scenario
|
|
194
|
-
Given test1
|
|
195
|
-
Then success
|
|
196
|
-
|
|
197
|
-
Scenario: Another scenario
|
|
198
|
-
Given test2
|
|
199
|
-
Then success
|
|
200
|
-
|
|
201
|
-
Scenario: Test scenario
|
|
202
|
-
Given duplicate
|
|
203
|
-
Then success
|
|
204
|
-
`);
|
|
205
|
-
|
|
206
|
-
const result = await deduplicateScenarios('test.feature');
|
|
207
|
-
|
|
208
|
-
expect(result.duplicatesRemoved).toBe(1);
|
|
209
|
-
|
|
210
|
-
const content = fs.readFileSync(scenarioFile, 'utf8');
|
|
211
|
-
expect(content.match(/Scenario: Test scenario/g)).toHaveLength(1);
|
|
212
|
-
expect(content.match(/Scenario: Another scenario/g)).toHaveLength(1);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test('preserves first occurrence of duplicate', async () => {
|
|
216
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
217
|
-
fs.writeFileSync(scenarioFile, `Feature: Test
|
|
218
|
-
|
|
219
|
-
Scenario: Test scenario
|
|
220
|
-
Given test1
|
|
221
|
-
Then success
|
|
222
|
-
|
|
223
|
-
Scenario: Test scenario
|
|
224
|
-
Given test2
|
|
225
|
-
Then success
|
|
226
|
-
`);
|
|
227
|
-
|
|
228
|
-
await deduplicateScenarios('test.feature');
|
|
229
|
-
|
|
230
|
-
const content = fs.readFileSync(scenarioFile, 'utf8');
|
|
231
|
-
expect(content).toContain('Given test1');
|
|
232
|
-
expect(content).not.toContain('Given test2');
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
});
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
validateScenarios,
|
|
3
|
-
parseScenarios,
|
|
4
|
-
matchStandardToScenario,
|
|
5
|
-
generateKeywords
|
|
6
|
-
} = require('./production-scenario-validator');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
|
|
10
|
-
describe('production-scenario-validator', () => {
|
|
11
|
-
let testDir;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
testDir = path.join(__dirname, '../test-tmp', `scenario-validator-${Date.now()}`);
|
|
15
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
16
|
-
process.chdir(testDir);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
if (testDir && fs.existsSync(testDir)) {
|
|
21
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('parseScenarios', () => {
|
|
26
|
-
test('parses single scenario', () => {
|
|
27
|
-
const content = `Feature: Test
|
|
28
|
-
|
|
29
|
-
Scenario: User logs in
|
|
30
|
-
Given I am on the login page
|
|
31
|
-
When I enter credentials
|
|
32
|
-
Then I am logged in`;
|
|
33
|
-
|
|
34
|
-
const scenarios = parseScenarios(content);
|
|
35
|
-
|
|
36
|
-
expect(scenarios).toHaveLength(1);
|
|
37
|
-
expect(scenarios[0].title).toBe('User logs in');
|
|
38
|
-
expect(scenarios[0].content).toContain('Given I am on the login page');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('parses multiple scenarios', () => {
|
|
42
|
-
const content = `Feature: Test
|
|
43
|
-
|
|
44
|
-
Scenario: Happy path
|
|
45
|
-
Given initial state
|
|
46
|
-
When action
|
|
47
|
-
Then success
|
|
48
|
-
|
|
49
|
-
Scenario: Error case
|
|
50
|
-
Given error state
|
|
51
|
-
When action
|
|
52
|
-
Then error`;
|
|
53
|
-
|
|
54
|
-
const scenarios = parseScenarios(content);
|
|
55
|
-
|
|
56
|
-
expect(scenarios).toHaveLength(2);
|
|
57
|
-
expect(scenarios[0].title).toBe('Happy path');
|
|
58
|
-
expect(scenarios[1].title).toBe('Error case');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('returns empty array for no scenarios', () => {
|
|
62
|
-
const content = 'Feature: Test\n\nNo scenarios here';
|
|
63
|
-
|
|
64
|
-
const scenarios = parseScenarios(content);
|
|
65
|
-
|
|
66
|
-
expect(scenarios).toHaveLength(0);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('generateKeywords', () => {
|
|
71
|
-
test('generates keywords from standard ID', () => {
|
|
72
|
-
const standard = {
|
|
73
|
-
id: 'tls_enforced',
|
|
74
|
-
domain: 'security',
|
|
75
|
-
acceptance: 'TLS 1.2 required'
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const keywords = generateKeywords(standard);
|
|
79
|
-
|
|
80
|
-
expect(keywords).toContain('tls');
|
|
81
|
-
expect(keywords).toContain('enforced');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('includes domain-specific keywords', () => {
|
|
85
|
-
const standard = {
|
|
86
|
-
id: 'test_security',
|
|
87
|
-
domain: 'security',
|
|
88
|
-
acceptance: 'test'
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const keywords = generateKeywords(standard);
|
|
92
|
-
|
|
93
|
-
expect(keywords).toContain('authentication');
|
|
94
|
-
expect(keywords).toContain('encryption');
|
|
95
|
-
expect(keywords).toContain('rate limiting');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('includes keywords from acceptance criteria', () => {
|
|
99
|
-
const standard = {
|
|
100
|
-
id: 'test',
|
|
101
|
-
domain: 'performance',
|
|
102
|
-
acceptance: 'p95 latency < 2s; load testing required'
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const keywords = generateKeywords(standard);
|
|
106
|
-
|
|
107
|
-
expect(keywords).toContain('latency');
|
|
108
|
-
expect(keywords).toContain('load');
|
|
109
|
-
expect(keywords).toContain('testing');
|
|
110
|
-
expect(keywords).toContain('required');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('matchStandardToScenario', () => {
|
|
115
|
-
test('returns false when no scenarios match', () => {
|
|
116
|
-
const standard = {
|
|
117
|
-
id: 'tls_enforced',
|
|
118
|
-
domain: 'security',
|
|
119
|
-
acceptance: 'TLS 1.2 required'
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const scenarios = [
|
|
123
|
-
{
|
|
124
|
-
title: 'User login',
|
|
125
|
-
content: 'Given I am logged in'
|
|
126
|
-
}
|
|
127
|
-
];
|
|
128
|
-
|
|
129
|
-
const result = matchStandardToScenario(standard, scenarios);
|
|
130
|
-
expect(result).toBe(false);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('returns true when scenario matches standard', () => {
|
|
134
|
-
const standard = {
|
|
135
|
-
id: 'tls_enforced',
|
|
136
|
-
domain: 'security',
|
|
137
|
-
acceptance: 'TLS 1.2 required'
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const scenarios = [
|
|
141
|
-
{
|
|
142
|
-
title: 'TLS encryption is enforced',
|
|
143
|
-
content: 'Given HTTPS is enabled\nWhen connecting\nThen TLS 1.2 is required'
|
|
144
|
-
}
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
const result = matchStandardToScenario(standard, scenarios);
|
|
148
|
-
expect(result).toBe(true);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test('matches on domain keywords', () => {
|
|
152
|
-
const standard = {
|
|
153
|
-
id: 'auth_required',
|
|
154
|
-
domain: 'security',
|
|
155
|
-
acceptance: 'Authentication required for all endpoints'
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const scenarios = [
|
|
159
|
-
{
|
|
160
|
-
title: 'User authentication',
|
|
161
|
-
content: 'Given user has valid credentials\nWhen accessing API\nThen authentication succeeds'
|
|
162
|
-
}
|
|
163
|
-
];
|
|
164
|
-
|
|
165
|
-
const result = matchStandardToScenario(standard, scenarios);
|
|
166
|
-
expect(result).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe('validateScenarios', () => {
|
|
171
|
-
test('throws error when scenario file not found', async () => {
|
|
172
|
-
const standards = {
|
|
173
|
-
standards: []
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
await expect(validateScenarios('nonexistent.feature', standards))
|
|
177
|
-
.rejects.toThrow('Scenario file not found');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('returns all gaps when no scenarios exist', async () => {
|
|
181
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
182
|
-
fs.writeFileSync(scenarioFile, 'Feature: Test\n\n');
|
|
183
|
-
|
|
184
|
-
const standards = {
|
|
185
|
-
standards: [
|
|
186
|
-
{
|
|
187
|
-
id: 'tls_enforced',
|
|
188
|
-
domain: 'security',
|
|
189
|
-
acceptance: 'TLS 1.2',
|
|
190
|
-
reasoning: 'Security requirement'
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
id: 'performance_budgets',
|
|
194
|
-
domain: 'performance',
|
|
195
|
-
acceptance: 'p95 < 2s',
|
|
196
|
-
reasoning: 'Performance requirement'
|
|
197
|
-
}
|
|
198
|
-
]
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const result = await validateScenarios('test.feature', standards);
|
|
202
|
-
|
|
203
|
-
expect(result.covered).toBe(0);
|
|
204
|
-
expect(result.total).toBe(2);
|
|
205
|
-
expect(result.gaps).toHaveLength(2);
|
|
206
|
-
expect(result.hasGaps).toBe(true);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('returns no gaps when all standards are covered', async () => {
|
|
210
|
-
const scenarioFile = path.join(testDir, 'test.feature');
|
|
211
|
-
fs.writeFileSync(scenarioFile, `Feature: Test
|
|
212
|
-
|
|
213
|
-
Scenario: TLS encryption
|
|
214
|
-
Given HTTPS enabled
|
|
215
|
-
When connecting
|
|
216
|
-
Then TLS 1.2 required
|
|
217
|
-
|
|
218
|
-
Scenario: Performance testing
|
|
219
|
-
Given system under load
|
|
220
|
-
When measuring latency
|
|
221
|
-
Then p95 < 2s`);
|
|
222
|
-
|
|
223
|
-
const standards = {
|
|
224
|
-
standards: [
|
|
225
|
-
{
|
|
226
|
-
id: 'tls_enforced',
|
|
227
|
-
domain: 'security',
|
|
228
|
-
acceptance: 'TLS 1.2'
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
id: 'performance_budgets',
|
|
232
|
-
domain: 'performance',
|
|
233
|
-
acceptance: 'p95 < 2s'
|
|
234
|
-
}
|
|
235
|
-
]
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
const result = await validateScenarios('test.feature', standards);
|
|
239
|
-
|
|
240
|
-
expect(result.covered).toBe(2);
|
|
241
|
-
expect(result.total).toBe(2);
|
|
242
|
-
expect(result.gaps).toHaveLength(0);
|
|
243
|
-
expect(result.hasGaps).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
});
|