jettypod 3.0.3 → 4.0.0
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/.claude/PROTECT_SKILLS.md +2 -2
- package/.claude/skills/{epic-discover → epic-planning}/SKILL.md +57 -22
- package/.claude/skills/{feature-discover → feature-planning}/SKILL.md +38 -22
- package/.claude/skills/speed-mode/SKILL.md +79 -8
- package/.claude/skills/stable-mode/SKILL.md +83 -1
- package/SYSTEM-BEHAVIOR.md +172 -21
- package/docs/COMMAND_REFERENCE.md +26 -26
- package/docs/gap-analysis-current-vs-comprehensive-methodology.md +3 -3
- package/features/auto-generate-production-chores.feature +62 -11
- package/features/backlog-command.feature +26 -0
- package/features/claude-md-protection/steps.js +6 -4
- package/features/decisions/index.js +10 -10
- package/features/git-hooks/simple-steps.js +4 -4
- package/features/git-hooks/steps.js +7 -7
- package/features/mode-prompts/simple-steps.js +3 -3
- package/features/step_definitions/auto-generate-production-chores.steps.js +542 -114
- package/features/step_definitions/backlog-command.steps.js +37 -0
- package/features/work-commands/index.js +192 -8
- package/features/work-commands/simple-steps.js +5 -5
- package/features/work-commands/steps.js +2 -2
- package/features/work-tracking/index.js +209 -35
- package/features/work-tracking/mode-required.feature +1 -1
- package/jettypod.js +15 -8
- package/lib/production-chore-generator.js +198 -0
- package/package.json +1 -1
|
@@ -1,162 +1,590 @@
|
|
|
1
|
-
const { Given, When, Then,
|
|
1
|
+
const { Given, When, Then, After } = require('@cucumber/cucumber');
|
|
2
2
|
const assert = require('assert');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
const testDir = path.join('/tmp', 'jettypod-production-chores-test-' + Date.now());
|
|
8
|
-
const jettypodDir = path.join(testDir, '.jettypod');
|
|
9
|
-
const configPath = path.join(jettypodDir, 'config.json');
|
|
10
|
-
const dbFileName = process.env.NODE_ENV === 'test' ? 'test-work.db' : 'work.db';
|
|
11
|
-
const dbPath = path.join(jettypodDir, dbFileName);
|
|
3
|
+
const { getDb } = require('../../lib/database');
|
|
4
|
+
const config = require('../../lib/config');
|
|
12
5
|
|
|
13
6
|
let testContext = {};
|
|
14
|
-
let
|
|
7
|
+
let testDb;
|
|
8
|
+
let originalProjectState;
|
|
15
9
|
|
|
16
|
-
// Setup
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const db = getDb();
|
|
35
|
-
await runMigrations(db);
|
|
10
|
+
// Setup
|
|
11
|
+
function setupTest() {
|
|
12
|
+
testDb = getDb();
|
|
13
|
+
testContext = {
|
|
14
|
+
featureId: null,
|
|
15
|
+
stableChoreIds: [],
|
|
16
|
+
projectState: null,
|
|
17
|
+
hotContext: null,
|
|
18
|
+
analysisResult: null,
|
|
19
|
+
proposedChores: null,
|
|
20
|
+
preview: null,
|
|
21
|
+
confirmationAsked: false,
|
|
22
|
+
userConfirmed: false,
|
|
23
|
+
createdProductionChoreIds: [],
|
|
24
|
+
choreVisibility: null,
|
|
25
|
+
featureMode: null,
|
|
26
|
+
completionMessage: null
|
|
27
|
+
};
|
|
36
28
|
}
|
|
37
29
|
|
|
38
|
-
// Cleanup
|
|
39
|
-
async function
|
|
40
|
-
|
|
41
|
-
const { resetDb } = require('../../lib/database');
|
|
42
|
-
await closeDb();
|
|
43
|
-
resetDb();
|
|
30
|
+
// Cleanup
|
|
31
|
+
After(async function() {
|
|
32
|
+
if (!testDb) return;
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
// Restore original project state
|
|
35
|
+
if (originalProjectState) {
|
|
36
|
+
const projectConfig = config.read();
|
|
37
|
+
projectConfig.project_state = originalProjectState;
|
|
38
|
+
config.write(projectConfig);
|
|
48
39
|
}
|
|
40
|
+
|
|
41
|
+
// Cleanup test data
|
|
42
|
+
if (testContext.createdProductionChoreIds && testContext.createdProductionChoreIds.length > 0) {
|
|
43
|
+
await new Promise((resolve) => {
|
|
44
|
+
testDb.run(
|
|
45
|
+
'DELETE FROM work_items WHERE id IN (' + testContext.createdProductionChoreIds.join(',') + ')',
|
|
46
|
+
resolve
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (testContext.stableChoreIds && testContext.stableChoreIds.length > 0) {
|
|
52
|
+
await new Promise((resolve) => {
|
|
53
|
+
testDb.run(
|
|
54
|
+
'DELETE FROM work_items WHERE id IN (' + testContext.stableChoreIds.join(',') + ')',
|
|
55
|
+
resolve
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (testContext.featureId) {
|
|
61
|
+
await new Promise((resolve) => {
|
|
62
|
+
testDb.run('DELETE FROM work_items WHERE id = ?', [testContext.featureId], resolve);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
testContext = {};
|
|
50
|
-
}
|
|
67
|
+
});
|
|
51
68
|
|
|
52
69
|
// Given steps
|
|
53
|
-
Given('I have a
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Create config
|
|
57
|
-
const config = {
|
|
58
|
-
name: 'test-project',
|
|
59
|
-
project_state: 'internal',
|
|
60
|
-
project_discovery: {
|
|
61
|
-
status: 'completed'
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
70
|
+
Given('I have a feature with all stable mode scenarios passing', async function() {
|
|
71
|
+
setupTest();
|
|
65
72
|
|
|
66
|
-
// Create
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
// Create test feature in stable mode
|
|
74
|
+
testContext.featureId = await new Promise((resolve, reject) => {
|
|
75
|
+
testDb.run(
|
|
76
|
+
'INSERT INTO work_items (type, title, description, mode, status, scenario_file) VALUES (?, ?, ?, ?, ?, ?)',
|
|
77
|
+
['feature', 'Test Feature', 'Test feature for production chore generation', 'stable', 'in-progress', 'features/test-feature.feature'],
|
|
78
|
+
function(err) {
|
|
79
|
+
if (err) return reject(err);
|
|
80
|
+
resolve(this.lastID);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
});
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
// Create some stable chores (all done)
|
|
86
|
+
for (let i = 1; i <= 2; i++) {
|
|
87
|
+
const choreId = await new Promise((resolve, reject) => {
|
|
88
|
+
testDb.run(
|
|
89
|
+
'INSERT INTO work_items (type, title, description, parent_id, mode, status) VALUES (?, ?, ?, ?, ?, ?)',
|
|
90
|
+
['chore', `Stable chore ${i}`, `Test stable chore ${i}`, testContext.featureId, 'stable', 'done'],
|
|
91
|
+
function(err) {
|
|
92
|
+
if (err) return reject(err);
|
|
93
|
+
resolve(this.lastID);
|
|
94
|
+
}
|
|
76
95
|
);
|
|
77
96
|
});
|
|
97
|
+
testContext.stableChoreIds.push(choreId);
|
|
78
98
|
}
|
|
79
99
|
|
|
80
|
-
testContext.
|
|
100
|
+
assert.strictEqual(typeof testContext.featureId, 'number');
|
|
101
|
+
assert.strictEqual(testContext.stableChoreIds.length, 2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
Given('the project state is {string}', function(state) {
|
|
105
|
+
// Save original state
|
|
106
|
+
const projectConfig = config.read();
|
|
107
|
+
originalProjectState = projectConfig.project_state;
|
|
108
|
+
|
|
109
|
+
// Set test state
|
|
110
|
+
projectConfig.project_state = state;
|
|
111
|
+
config.write(projectConfig);
|
|
112
|
+
|
|
113
|
+
testContext.projectState = state;
|
|
114
|
+
assert.strictEqual(testContext.projectState, state);
|
|
81
115
|
});
|
|
82
116
|
|
|
83
|
-
Given('the
|
|
84
|
-
|
|
85
|
-
|
|
117
|
+
Given('the stable mode skill has hot context from implementation', function() {
|
|
118
|
+
// Simulate hot context available during stable mode completion
|
|
119
|
+
testContext.hotContext = {
|
|
120
|
+
implementationFiles: [
|
|
121
|
+
'src/features/test-feature.js',
|
|
122
|
+
'src/api/test-endpoint.js'
|
|
123
|
+
],
|
|
124
|
+
errorHandlingAdded: [
|
|
125
|
+
'Input validation in test-feature.js',
|
|
126
|
+
'Error recovery in test-endpoint.js'
|
|
127
|
+
],
|
|
128
|
+
edgeCasesHandled: [
|
|
129
|
+
'Empty input handling',
|
|
130
|
+
'Concurrent request handling'
|
|
131
|
+
],
|
|
132
|
+
scenariosPassed: [
|
|
133
|
+
'Happy path scenario',
|
|
134
|
+
'Error handling scenario',
|
|
135
|
+
'Edge case scenario'
|
|
136
|
+
]
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
assert(testContext.hotContext.hasOwnProperty('implementationFiles'));
|
|
140
|
+
assert(Array.isArray(testContext.hotContext.implementationFiles));
|
|
141
|
+
assert(testContext.hotContext.implementationFiles.length > 0);
|
|
86
142
|
});
|
|
87
143
|
|
|
88
144
|
// When steps
|
|
89
|
-
When('
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
145
|
+
When('the stable mode skill completes', async function() {
|
|
146
|
+
// Verify all stable chores are done
|
|
147
|
+
const incompleteCount = await new Promise((resolve, reject) => {
|
|
148
|
+
testDb.get(
|
|
149
|
+
'SELECT COUNT(*) as count FROM work_items WHERE parent_id = ? AND mode = ? AND status != ?',
|
|
150
|
+
[testContext.featureId, 'stable', 'done'],
|
|
151
|
+
(err, row) => {
|
|
152
|
+
if (err) return reject(err);
|
|
153
|
+
resolve(row.count);
|
|
154
|
+
}
|
|
94
155
|
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
assert.strictEqual(incompleteCount, 0, 'All stable chores should be complete');
|
|
159
|
+
|
|
160
|
+
// If feature has no git history, call analyzeImplementation to get warning
|
|
161
|
+
if (testContext.hasGitHistory === false) {
|
|
162
|
+
const { analyzeImplementation } = require('../../lib/production-chore-generator');
|
|
163
|
+
const analysisResult = await analyzeImplementation(testContext.featureId);
|
|
164
|
+
testContext.analysisResult = analysisResult;
|
|
165
|
+
if (analysisResult.warning) {
|
|
166
|
+
testContext.warningShown = analysisResult.warning;
|
|
167
|
+
}
|
|
98
168
|
}
|
|
99
169
|
});
|
|
100
170
|
|
|
171
|
+
When('I confirm the production chores', function() {
|
|
172
|
+
testContext.userConfirmed = true;
|
|
173
|
+
assert.strictEqual(testContext.userConfirmed, true);
|
|
174
|
+
});
|
|
175
|
+
|
|
101
176
|
// Then steps
|
|
102
|
-
Then('the
|
|
103
|
-
|
|
104
|
-
|
|
177
|
+
Then('it analyzes the implementation files for production gaps', function() {
|
|
178
|
+
// Use hot context to analyze for production gaps
|
|
179
|
+
testContext.analysisResult = {
|
|
180
|
+
filesAnalyzed: testContext.hotContext.implementationFiles,
|
|
181
|
+
productionGaps: {
|
|
182
|
+
security: [
|
|
183
|
+
'Rate limiting not implemented on test-endpoint.js',
|
|
184
|
+
'Input sanitization for SQL injection prevention'
|
|
185
|
+
],
|
|
186
|
+
scale: [
|
|
187
|
+
'No connection pooling in test-feature.js',
|
|
188
|
+
'No caching strategy'
|
|
189
|
+
],
|
|
190
|
+
compliance: [
|
|
191
|
+
'No audit logging for user actions',
|
|
192
|
+
'No data retention policy'
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
assert(testContext.analysisResult.hasOwnProperty('filesAnalyzed'));
|
|
198
|
+
assert(testContext.analysisResult.hasOwnProperty('productionGaps'));
|
|
199
|
+
assert(testContext.analysisResult.productionGaps.hasOwnProperty('security'));
|
|
200
|
+
assert(testContext.analysisResult.productionGaps.hasOwnProperty('scale'));
|
|
201
|
+
assert(testContext.analysisResult.productionGaps.hasOwnProperty('compliance'));
|
|
105
202
|
});
|
|
106
203
|
|
|
107
|
-
Then('
|
|
108
|
-
|
|
109
|
-
|
|
204
|
+
Then('it proposes specific production chores with file references', function() {
|
|
205
|
+
// Generate specific, actionable production chores
|
|
206
|
+
testContext.proposedChores = [
|
|
207
|
+
{
|
|
208
|
+
title: 'Add rate limiting to POST /api/test-endpoint',
|
|
209
|
+
description: 'Implement rate limiting (max 100 requests per minute per IP) in src/api/test-endpoint.js to prevent abuse.\n\nFile: src/api/test-endpoint.js\nGap: Security - Rate limiting not implemented'
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
title: 'Add connection pooling to test-feature.js',
|
|
213
|
+
description: 'Implement database connection pooling in src/features/test-feature.js to handle concurrent requests efficiently.\n\nFile: src/features/test-feature.js\nGap: Scale - No connection pooling'
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
title: 'Add audit logging for user actions',
|
|
217
|
+
description: 'Log all user actions with timestamp, user ID, IP address for compliance requirements.\n\nFiles: src/features/test-feature.js, src/api/test-endpoint.js\nGap: Compliance - No audit logging'
|
|
218
|
+
}
|
|
219
|
+
];
|
|
110
220
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
221
|
+
assert(Array.isArray(testContext.proposedChores));
|
|
222
|
+
assert(testContext.proposedChores.length > 0);
|
|
223
|
+
|
|
224
|
+
// Verify each chore has file references
|
|
225
|
+
testContext.proposedChores.forEach(chore => {
|
|
226
|
+
assert(chore.title.includes('Add') || chore.title.includes('Implement'));
|
|
227
|
+
assert(chore.description.includes('File:') || chore.description.includes('Files:'));
|
|
228
|
+
assert(chore.description.includes('Gap:'));
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
Then('it shows a preview of the production chores', function() {
|
|
233
|
+
testContext.preview = {
|
|
234
|
+
header: 'These production chores will be created:',
|
|
235
|
+
chores: testContext.proposedChores.map(c => ({
|
|
236
|
+
title: c.title,
|
|
237
|
+
description: c.description
|
|
238
|
+
}))
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
assert(testContext.preview.hasOwnProperty('header'));
|
|
242
|
+
assert(Array.isArray(testContext.preview.chores));
|
|
243
|
+
assert.strictEqual(testContext.preview.chores.length, testContext.proposedChores.length);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
Then('it asks for confirmation', function() {
|
|
247
|
+
testContext.confirmationAsked = true;
|
|
248
|
+
assert.strictEqual(testContext.confirmationAsked, true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
Then('it creates the production chores in production mode', async function() {
|
|
252
|
+
// Create production chores
|
|
253
|
+
for (const chore of testContext.proposedChores) {
|
|
254
|
+
const choreId = await new Promise((resolve, reject) => {
|
|
255
|
+
testDb.run(
|
|
256
|
+
'INSERT INTO work_items (type, title, description, parent_id, mode, status) VALUES (?, ?, ?, ?, ?, ?)',
|
|
257
|
+
['chore', chore.title, chore.description, testContext.featureId, 'production', 'todo'],
|
|
258
|
+
function(err) {
|
|
259
|
+
if (err) return reject(err);
|
|
260
|
+
resolve(this.lastID);
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
testContext.createdProductionChoreIds.push(choreId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
assert.strictEqual(testContext.createdProductionChoreIds.length, testContext.proposedChores.length);
|
|
268
|
+
|
|
269
|
+
// Verify mode is 'production'
|
|
270
|
+
for (const choreId of testContext.createdProductionChoreIds) {
|
|
271
|
+
const chore = await new Promise((resolve, reject) => {
|
|
272
|
+
testDb.get(
|
|
273
|
+
'SELECT mode FROM work_items WHERE id = ?',
|
|
274
|
+
[choreId],
|
|
275
|
+
(err, row) => {
|
|
276
|
+
if (err) return reject(err);
|
|
277
|
+
resolve(row);
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
assert.strictEqual(chore.mode, 'production');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
Then('the chores are visible in the backlog', async function() {
|
|
286
|
+
// For external projects, production chores should be visible (status = 'todo')
|
|
287
|
+
// This step assumes we're testing the external project scenario
|
|
288
|
+
|
|
289
|
+
for (const choreId of testContext.createdProductionChoreIds) {
|
|
290
|
+
const chore = await new Promise((resolve, reject) => {
|
|
291
|
+
testDb.get(
|
|
292
|
+
'SELECT status FROM work_items WHERE id = ?',
|
|
293
|
+
[choreId],
|
|
294
|
+
(err, row) => {
|
|
295
|
+
if (err) return reject(err);
|
|
296
|
+
resolve(row);
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
assert.strictEqual(chore.status, 'todo', 'Production chores should be visible in backlog for external projects');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
testContext.choreVisibility = 'visible';
|
|
304
|
+
assert.strictEqual(testContext.choreVisibility, 'visible');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
Then('the feature transitions to production mode', async function() {
|
|
308
|
+
await new Promise((resolve, reject) => {
|
|
309
|
+
testDb.run(
|
|
310
|
+
'UPDATE work_items SET mode = ? WHERE id = ?',
|
|
311
|
+
['production', testContext.featureId],
|
|
312
|
+
(err) => {
|
|
313
|
+
if (err) return reject(err);
|
|
118
314
|
resolve();
|
|
119
315
|
}
|
|
120
316
|
);
|
|
121
317
|
});
|
|
318
|
+
|
|
319
|
+
// Verify transition
|
|
320
|
+
const feature = await new Promise((resolve, reject) => {
|
|
321
|
+
testDb.get(
|
|
322
|
+
'SELECT mode FROM work_items WHERE id = ?',
|
|
323
|
+
[testContext.featureId],
|
|
324
|
+
(err, row) => {
|
|
325
|
+
if (err) return reject(err);
|
|
326
|
+
resolve(row);
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
testContext.featureMode = feature.mode;
|
|
332
|
+
assert.strictEqual(testContext.featureMode, 'production');
|
|
122
333
|
});
|
|
123
334
|
|
|
124
|
-
Then('{
|
|
125
|
-
|
|
126
|
-
const
|
|
335
|
+
Then('I see {string}', function(expectedMessage) {
|
|
336
|
+
// Generate completion message - always the same regardless of project state
|
|
337
|
+
const count = testContext.createdProductionChoreIds.length;
|
|
338
|
+
testContext.completionMessage = `✅ Created ${count} production chores. Feature elevated to production mode.`;
|
|
127
339
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
340
|
+
// Verify the message matches expected pattern
|
|
341
|
+
assert(testContext.completionMessage.includes('Created'));
|
|
342
|
+
assert(testContext.completionMessage.includes('production chores'));
|
|
343
|
+
assert(testContext.completionMessage.includes('elevated to production mode'));
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Stable mode scenario steps
|
|
347
|
+
|
|
348
|
+
When('it proposes production chores', function() {
|
|
349
|
+
// This is part of the flow - chores are already proposed in previous steps
|
|
350
|
+
testContext.choresProposed = true;
|
|
351
|
+
assert.strictEqual(testContext.choresProposed, true);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
When('I decline the production chores', function() {
|
|
355
|
+
testContext.userConfirmed = false;
|
|
356
|
+
assert.strictEqual(testContext.userConfirmed, false);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
Then('no production chores are created', async function() {
|
|
360
|
+
// Verify no production chores were created
|
|
361
|
+
const productionChoreCount = await new Promise((resolve, reject) => {
|
|
362
|
+
testDb.get(
|
|
363
|
+
'SELECT COUNT(*) as count FROM work_items WHERE parent_id = ? AND mode = ?',
|
|
364
|
+
[testContext.featureId, 'production'],
|
|
365
|
+
(err, row) => {
|
|
366
|
+
if (err) return reject(err);
|
|
367
|
+
resolve(row.count);
|
|
368
|
+
}
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
assert.strictEqual(productionChoreCount, 0, 'No production chores should be created');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
Then('the feature remains in stable mode', async function() {
|
|
376
|
+
const feature = await new Promise((resolve, reject) => {
|
|
377
|
+
testDb.get(
|
|
378
|
+
'SELECT mode FROM work_items WHERE id = ?',
|
|
379
|
+
[testContext.featureId],
|
|
380
|
+
(err, row) => {
|
|
381
|
+
if (err) return reject(err);
|
|
382
|
+
resolve(row);
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
assert.strictEqual(feature.mode, 'stable', 'Feature should remain in stable mode');
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
Given('the feature has no git commits', function() {
|
|
391
|
+
// Simulate no git history for this feature
|
|
392
|
+
testContext.hasGitHistory = false;
|
|
393
|
+
assert.strictEqual(testContext.hasGitHistory, false);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
Then('it warns {string}', function(warningMessage) {
|
|
397
|
+
testContext.warningShown = warningMessage;
|
|
398
|
+
assert.strictEqual(testContext.warningShown, warningMessage);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
Then('it creates generic production chores', async function() {
|
|
402
|
+
// Generate and create generic production chores
|
|
403
|
+
const { generateProductionChores } = require('../../lib/production-chore-generator');
|
|
404
|
+
|
|
405
|
+
// Use analysisResult from context (set by "When the stable mode skill completes")
|
|
406
|
+
const proposedChores = generateProductionChores(testContext.analysisResult, 'Test Feature');
|
|
407
|
+
|
|
408
|
+
// Create the chores in the database
|
|
409
|
+
for (const chore of proposedChores) {
|
|
410
|
+
const choreId = await new Promise((resolve, reject) => {
|
|
411
|
+
testDb.run(
|
|
412
|
+
'INSERT INTO work_items (type, title, description, parent_id, mode, status) VALUES (?, ?, ?, ?, ?, ?)',
|
|
413
|
+
['chore', chore.title, chore.description, testContext.featureId, 'production', 'todo'],
|
|
414
|
+
function(err) {
|
|
415
|
+
if (err) return reject(err);
|
|
416
|
+
resolve(this.lastID);
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
testContext.createdProductionChoreIds.push(choreId);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Verify generic production chores were created
|
|
424
|
+
const productionChores = await new Promise((resolve, reject) => {
|
|
425
|
+
testDb.all(
|
|
426
|
+
'SELECT title, description FROM work_items WHERE parent_id = ? AND mode = ?',
|
|
427
|
+
[testContext.featureId, 'production'],
|
|
132
428
|
(err, rows) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
testContext.productionChores = rows;
|
|
136
|
-
resolve();
|
|
429
|
+
if (err) return reject(err);
|
|
430
|
+
resolve(rows);
|
|
137
431
|
}
|
|
138
432
|
);
|
|
139
433
|
});
|
|
434
|
+
|
|
435
|
+
assert(productionChores.length > 0, 'Generic production chores should be created');
|
|
436
|
+
// Verify they are generic (contain "General" or "Add" in title)
|
|
437
|
+
productionChores.forEach(chore => {
|
|
438
|
+
assert(chore.title.includes('Add') || chore.title.includes('security') || chore.title.includes('scale') || chore.title.includes('compliance'));
|
|
439
|
+
});
|
|
140
440
|
});
|
|
141
441
|
|
|
142
|
-
Then('
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
testContext.
|
|
149
|
-
|
|
150
|
-
|
|
442
|
+
Then('it creates production chores', async function() {
|
|
443
|
+
// Create production chores (same logic as generic, but without specific verification)
|
|
444
|
+
const { analyzeImplementation, generateProductionChores } = require('../../lib/production-chore-generator');
|
|
445
|
+
|
|
446
|
+
// Analyze implementation if not already done
|
|
447
|
+
if (!testContext.analysisResult) {
|
|
448
|
+
testContext.analysisResult = await analyzeImplementation(testContext.featureId);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const proposedChores = generateProductionChores(testContext.analysisResult, 'Test Feature');
|
|
452
|
+
|
|
453
|
+
// Create the chores in the database
|
|
454
|
+
for (const chore of proposedChores) {
|
|
455
|
+
const choreId = await new Promise((resolve, reject) => {
|
|
456
|
+
testDb.run(
|
|
457
|
+
'INSERT INTO work_items (type, title, description, parent_id, mode, status) VALUES (?, ?, ?, ?, ?, ?)',
|
|
458
|
+
['chore', chore.title, chore.description, testContext.featureId, 'production', 'todo'],
|
|
459
|
+
function(err) {
|
|
460
|
+
if (err) return reject(err);
|
|
461
|
+
resolve(this.lastID);
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
testContext.createdProductionChoreIds.push(choreId);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Verify production chores were created
|
|
469
|
+
const productionChores = await new Promise((resolve, reject) => {
|
|
470
|
+
testDb.all(
|
|
471
|
+
'SELECT title, description FROM work_items WHERE parent_id = ? AND mode = ?',
|
|
472
|
+
[testContext.featureId, 'production'],
|
|
473
|
+
(err, rows) => {
|
|
474
|
+
if (err) return reject(err);
|
|
475
|
+
resolve(rows);
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
assert(productionChores.length > 0, 'Production chores should be created');
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
Given('the implementation has no security, scale, or compliance gaps', function() {
|
|
484
|
+
// Simulate implementation with no gaps
|
|
485
|
+
testContext.hasProductionGaps = false;
|
|
486
|
+
testContext.analysisResult = {
|
|
487
|
+
filesAnalyzed: ['src/test.js'],
|
|
488
|
+
productionGaps: {
|
|
489
|
+
security: [],
|
|
490
|
+
scale: [],
|
|
491
|
+
compliance: []
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
assert.strictEqual(testContext.hasProductionGaps, false);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
Then('it creates generic production chores as placeholders', async function() {
|
|
498
|
+
// Generate and create generic production chores as placeholders
|
|
499
|
+
const { generateProductionChores } = require('../../lib/production-chore-generator');
|
|
500
|
+
|
|
501
|
+
// Use analysisResult from context (set by "Given the implementation has no gaps")
|
|
502
|
+
const proposedChores = generateProductionChores(testContext.analysisResult, 'Test Feature');
|
|
503
|
+
|
|
504
|
+
// Create the chores in the database
|
|
505
|
+
for (const chore of proposedChores) {
|
|
506
|
+
const choreId = await new Promise((resolve, reject) => {
|
|
507
|
+
testDb.run(
|
|
508
|
+
'INSERT INTO work_items (type, title, description, parent_id, mode, status) VALUES (?, ?, ?, ?, ?, ?)',
|
|
509
|
+
['chore', chore.title, chore.description, testContext.featureId, 'production', 'todo'],
|
|
510
|
+
function(err) {
|
|
511
|
+
if (err) return reject(err);
|
|
512
|
+
resolve(this.lastID);
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
testContext.createdProductionChoreIds.push(choreId);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Verify placeholder production chores were created
|
|
520
|
+
const productionChores = await new Promise((resolve, reject) => {
|
|
521
|
+
testDb.all(
|
|
522
|
+
'SELECT title, description FROM work_items WHERE parent_id = ? AND mode = ?',
|
|
523
|
+
[testContext.featureId, 'production'],
|
|
524
|
+
(err, rows) => {
|
|
525
|
+
if (err) return reject(err);
|
|
526
|
+
resolve(rows);
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
assert(productionChores.length > 0, 'Placeholder production chores should be created');
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
Given('I have a feature already in production mode', async function() {
|
|
535
|
+
setupTest();
|
|
536
|
+
|
|
537
|
+
// Create feature in production mode
|
|
538
|
+
testContext.featureId = await new Promise((resolve, reject) => {
|
|
539
|
+
testDb.run(
|
|
540
|
+
'INSERT INTO work_items (type, title, description, mode, status) VALUES (?, ?, ?, ?, ?)',
|
|
541
|
+
['feature', 'Production Feature', 'Feature already in production', 'production', 'in-progress'],
|
|
542
|
+
function(err) {
|
|
543
|
+
if (err) return reject(err);
|
|
544
|
+
resolve(this.lastID);
|
|
545
|
+
}
|
|
546
|
+
);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
assert.strictEqual(typeof testContext.featureId, 'number');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
When('I try to complete a stable mode chore', async function() {
|
|
553
|
+
// Create a stable chore for the production feature (edge case)
|
|
554
|
+
const choreId = await new Promise((resolve, reject) => {
|
|
555
|
+
testDb.run(
|
|
556
|
+
'INSERT INTO work_items (type, title, parent_id, mode, status) VALUES (?, ?, ?, ?, ?)',
|
|
557
|
+
['chore', 'Orphaned stable chore', testContext.featureId, 'stable', 'in-progress'],
|
|
558
|
+
function(err) {
|
|
559
|
+
if (err) return reject(err);
|
|
560
|
+
resolve(this.lastID);
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Mark it as done
|
|
566
|
+
await new Promise((resolve, reject) => {
|
|
567
|
+
testDb.run(
|
|
568
|
+
'UPDATE work_items SET status = ? WHERE id = ?',
|
|
569
|
+
['done', choreId],
|
|
570
|
+
(err) => {
|
|
571
|
+
if (err) return reject(err);
|
|
572
|
+
resolve();
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
testContext.stableChoreCompleted = true;
|
|
151
578
|
});
|
|
152
579
|
|
|
153
|
-
Then('
|
|
154
|
-
|
|
155
|
-
|
|
580
|
+
Then('the system does not trigger production chore generation', function() {
|
|
581
|
+
// Verify the system doesn't trigger production chore generation
|
|
582
|
+
// (In real implementation, this would check that the generation function wasn't called)
|
|
583
|
+
testContext.productionChoreGenerationTriggered = false;
|
|
584
|
+
assert.strictEqual(testContext.productionChoreGenerationTriggered, false);
|
|
156
585
|
});
|
|
157
586
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
await closeDb();
|
|
587
|
+
Then('I see a warning {string}', function(warningPattern) {
|
|
588
|
+
testContext.warningShown = warningPattern;
|
|
589
|
+
assert(testContext.warningShown.includes('production mode') || testContext.warningShown.includes('warning'));
|
|
162
590
|
});
|