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,498 +0,0 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { getDb, getDbPath, getJettypodDir } = require('../../lib/database');
|
|
5
|
-
const { getCurrentWork, setCurrentWork, clearCurrentWork, getCurrentWorkPath } = require('../../lib/current-work');
|
|
6
|
-
const { VALID_STATUSES } = require('../../lib/constants');
|
|
7
|
-
const { updateCurrentWork } = require('../../lib/claudemd');
|
|
8
|
-
const { createFeatureBranchName, createOrCheckoutBranch } = require('../../lib/git');
|
|
9
|
-
const config = require('../../lib/config');
|
|
10
|
-
const { getBugWorkflowForTerminal } = require('../../lib/bug-workflow');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get paths to JettyPod files and directories
|
|
14
|
-
* @returns {Object} Paths object with jettypodDir, currentWorkPath, dbPath, claudePath
|
|
15
|
-
*/
|
|
16
|
-
function getPaths() {
|
|
17
|
-
return {
|
|
18
|
-
jettypodDir: getJettypodDir(),
|
|
19
|
-
currentWorkPath: getCurrentWorkPath(),
|
|
20
|
-
dbPath: getDbPath(),
|
|
21
|
-
claudePath: path.join(process.cwd(), 'CLAUDE.md')
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Start work on a work item
|
|
27
|
-
* @param {number} id - Work item ID to start
|
|
28
|
-
* @returns {Promise<Object>} Work item, current work, and branch name
|
|
29
|
-
* @throws {Error} If ID is invalid, JettyPod not initialized, database missing, or work item not found
|
|
30
|
-
*/
|
|
31
|
-
function startWork(id) {
|
|
32
|
-
// Input validation
|
|
33
|
-
if (!id || isNaN(id) || id < 1) {
|
|
34
|
-
return Promise.reject(new Error('Invalid work item ID'));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const paths = getPaths();
|
|
38
|
-
|
|
39
|
-
// Check jettypod directory exists
|
|
40
|
-
if (!fs.existsSync(paths.jettypodDir)) {
|
|
41
|
-
return Promise.reject(new Error('JettyPod not initialized. Run: jettypod init'));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check database exists
|
|
45
|
-
if (!fs.existsSync(paths.dbPath)) {
|
|
46
|
-
return Promise.reject(new Error('Work database not found. Run: jettypod init'));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const db = getDb();
|
|
50
|
-
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
52
|
-
// Get work item
|
|
53
|
-
db.get(`
|
|
54
|
-
SELECT w.*,
|
|
55
|
-
p.title as parent_title, p.id as parent_id, p.scenario_file as parent_scenario_file, p.mode as parent_mode,
|
|
56
|
-
e.title as epic_title, e.id as epic_id
|
|
57
|
-
FROM work_items w
|
|
58
|
-
LEFT JOIN work_items p ON w.parent_id = p.id
|
|
59
|
-
LEFT JOIN work_items e ON w.epic_id = e.id AND w.epic_id != w.id
|
|
60
|
-
WHERE w.id = ?
|
|
61
|
-
`, [id], (err, workItem) => {
|
|
62
|
-
if (err) {
|
|
63
|
-
return reject(new Error(`Database error: ${err.message}`));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!workItem) {
|
|
67
|
-
return reject(new Error(`Work item #${id} not found`));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Update status to in_progress if currently todo
|
|
71
|
-
const finalStatus = workItem.status === 'todo' ? 'in_progress' : workItem.status;
|
|
72
|
-
|
|
73
|
-
const updateAndContinue = () => {
|
|
74
|
-
// Create current work file
|
|
75
|
-
const currentWork = {
|
|
76
|
-
id: workItem.id,
|
|
77
|
-
title: workItem.title,
|
|
78
|
-
type: workItem.type,
|
|
79
|
-
status: finalStatus,
|
|
80
|
-
parent_id: workItem.parent_id,
|
|
81
|
-
parent_title: workItem.parent_title,
|
|
82
|
-
epic_id: workItem.epic_id,
|
|
83
|
-
epic_title: workItem.epic_title
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
setCurrentWork(currentWork);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
return reject(new Error(`Failed to write current work file: ${err.message}`));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Create feature branch
|
|
93
|
-
let branchName = null;
|
|
94
|
-
try {
|
|
95
|
-
branchName = createFeatureBranchName(id, workItem.title);
|
|
96
|
-
createOrCheckoutBranch(branchName);
|
|
97
|
-
} catch (gitError) {
|
|
98
|
-
// Git operations failed, but continue - work tracking still works
|
|
99
|
-
console.warn(`Warning: ${gitError.message}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Update CLAUDE.md with work item's mode
|
|
103
|
-
// For chores, inherit the parent feature's mode
|
|
104
|
-
const modeToUse = (workItem.type === 'chore' && workItem.parent_mode)
|
|
105
|
-
? workItem.parent_mode
|
|
106
|
-
: workItem.mode;
|
|
107
|
-
updateCurrentWork(currentWork, modeToUse);
|
|
108
|
-
|
|
109
|
-
// Display output
|
|
110
|
-
let output = `Working on: [#${workItem.id}] ${workItem.title} (${workItem.type})`;
|
|
111
|
-
if (workItem.parent_title) {
|
|
112
|
-
output = `Working on: [#${workItem.id}] ${workItem.title} (${workItem.type} of #${workItem.parent_id} ${workItem.parent_title})`;
|
|
113
|
-
}
|
|
114
|
-
console.log(output);
|
|
115
|
-
|
|
116
|
-
// Display bug workflow guidance for bugs
|
|
117
|
-
// Only display if workItem has a type (defensive check)
|
|
118
|
-
if (workItem && workItem.type === 'bug') {
|
|
119
|
-
try {
|
|
120
|
-
console.log(getBugWorkflowForTerminal());
|
|
121
|
-
} catch (err) {
|
|
122
|
-
// Silently fail if workflow display fails - don't block work start
|
|
123
|
-
// This is a non-critical feature
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Display epic discovery guidance for epics with needs_discovery
|
|
128
|
-
if (workItem && workItem.type === 'epic' && workItem.needs_discovery) {
|
|
129
|
-
// Check if decisions have been recorded
|
|
130
|
-
db.all(
|
|
131
|
-
`SELECT * FROM discovery_decisions WHERE work_item_id = ?`,
|
|
132
|
-
[workItem.id],
|
|
133
|
-
(err, decisions) => {
|
|
134
|
-
if (err || !decisions || decisions.length === 0) {
|
|
135
|
-
console.log('');
|
|
136
|
-
console.log('⚠️ This epic needs architectural discovery before building features.');
|
|
137
|
-
console.log('');
|
|
138
|
-
console.log('💬 Recommended: Talk to Claude Code');
|
|
139
|
-
console.log(` Say: "Let's do epic discovery for #${workItem.id}"`);
|
|
140
|
-
console.log('');
|
|
141
|
-
console.log(' Claude Code will guide you through:');
|
|
142
|
-
console.log(' • Suggesting 3 architectural options');
|
|
143
|
-
console.log(' • Building prototypes');
|
|
144
|
-
console.log(' • Recording your decisions');
|
|
145
|
-
console.log('');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Auto-trigger feature discovery for features in discovery phase
|
|
152
|
-
if (workItem && workItem.type === 'feature' && workItem.phase === 'discovery') {
|
|
153
|
-
console.log('');
|
|
154
|
-
console.log('✨ Feature Discovery Mode');
|
|
155
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
156
|
-
console.log('');
|
|
157
|
-
console.log('This feature is in discovery phase. Claude Code will help you:');
|
|
158
|
-
console.log(' 1. Explore 3 different UX approaches');
|
|
159
|
-
console.log(' 2. (Optional) Build throwaway prototypes');
|
|
160
|
-
console.log(' 3. Choose the winning approach');
|
|
161
|
-
console.log(' 4. Generate BDD scenarios for the happy path');
|
|
162
|
-
console.log(' 5. Transition to implementation (speed mode)');
|
|
163
|
-
console.log('');
|
|
164
|
-
console.log('💬 Claude Code is ready to guide you through feature discovery.');
|
|
165
|
-
console.log('');
|
|
166
|
-
console.log('📋 The feature-planning skill will automatically activate.');
|
|
167
|
-
console.log('');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Auto-trigger mode skills for chores in speed or stable mode
|
|
171
|
-
if (workItem && workItem.type === 'chore' && workItem.parent_id) {
|
|
172
|
-
// Validate parent feature exists
|
|
173
|
-
if (!workItem.parent_title) {
|
|
174
|
-
console.log('');
|
|
175
|
-
console.log('⚠️ Warning: Parent feature not found');
|
|
176
|
-
console.log('');
|
|
177
|
-
console.log(`This chore references parent feature #${workItem.parent_id}, but that feature`);
|
|
178
|
-
console.log('does not exist in the database.');
|
|
179
|
-
console.log('');
|
|
180
|
-
console.log('Suggestion: Check the parent_id or create the missing feature.');
|
|
181
|
-
console.log('');
|
|
182
|
-
}
|
|
183
|
-
// Validate scenario file exists
|
|
184
|
-
else if (!workItem.parent_scenario_file) {
|
|
185
|
-
console.log('');
|
|
186
|
-
console.log('⚠️ Warning: Parent feature has no scenario file');
|
|
187
|
-
console.log('');
|
|
188
|
-
console.log(`Parent feature #${workItem.parent_id} "${workItem.parent_title}" does not have`);
|
|
189
|
-
console.log('a scenario file set.');
|
|
190
|
-
console.log('');
|
|
191
|
-
console.log('Suggestion: Create a BDD scenario file for the feature and update scenario_file.');
|
|
192
|
-
console.log('');
|
|
193
|
-
}
|
|
194
|
-
else if (!fs.existsSync(path.join(process.cwd(), workItem.parent_scenario_file))) {
|
|
195
|
-
console.log('');
|
|
196
|
-
console.log('⚠️ Warning: Scenario file not found');
|
|
197
|
-
console.log('');
|
|
198
|
-
console.log(`Parent feature references scenario file: ${workItem.parent_scenario_file}`);
|
|
199
|
-
console.log('but the file does not exist on disk.');
|
|
200
|
-
console.log('');
|
|
201
|
-
console.log('Suggestion: Create the scenario file or update the feature.scenario_file path.');
|
|
202
|
-
console.log('');
|
|
203
|
-
}
|
|
204
|
-
else if (workItem.mode === 'speed') {
|
|
205
|
-
console.log('');
|
|
206
|
-
console.log('🚀 Speed Mode Skill Activated');
|
|
207
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
208
|
-
console.log('');
|
|
209
|
-
console.log('Claude Code will autonomously:');
|
|
210
|
-
console.log(' 1. Analyze the BDD scenario for this feature');
|
|
211
|
-
console.log(' 2. Analyze the codebase to understand patterns');
|
|
212
|
-
console.log(' 3. Propose an implementation approach');
|
|
213
|
-
console.log(' 4. Implement until the happy path scenario passes');
|
|
214
|
-
console.log(' 5. Generate stable mode chores for comprehensive testing');
|
|
215
|
-
console.log('');
|
|
216
|
-
console.log('💬 The speed-mode skill is now active.');
|
|
217
|
-
console.log(' Claude Code will guide you through implementation.');
|
|
218
|
-
console.log('');
|
|
219
|
-
} else if (workItem.mode === 'stable') {
|
|
220
|
-
console.log('');
|
|
221
|
-
console.log('🧪 Stable Mode Skill Activated');
|
|
222
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
223
|
-
console.log('');
|
|
224
|
-
console.log('Claude Code will autonomously:');
|
|
225
|
-
console.log(' 1. Analyze the BDD scenario to implement');
|
|
226
|
-
console.log(' 2. Review the existing speed mode implementation');
|
|
227
|
-
console.log(' 3. Propose comprehensive error handling approach');
|
|
228
|
-
console.log(' 4. Implement with proper validation and edge case coverage');
|
|
229
|
-
console.log(' 5. Ensure all BDD scenarios pass');
|
|
230
|
-
console.log('');
|
|
231
|
-
console.log('💬 The stable-mode skill is now active.');
|
|
232
|
-
console.log(' Claude Code will guide you through comprehensive testing.');
|
|
233
|
-
console.log('');
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
resolve({ workItem, currentWork, branchName });
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
// Update status to in_progress if currently todo
|
|
241
|
-
if (workItem.status === 'todo') {
|
|
242
|
-
db.run(`UPDATE work_items SET status = 'in_progress' WHERE id = ?`, [id], updateAndContinue);
|
|
243
|
-
} else {
|
|
244
|
-
updateAndContinue();
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Stop work on current work item
|
|
252
|
-
* @param {string|null} newStatus - Optional status to set (e.g., 'done', 'blocked')
|
|
253
|
-
* @returns {Promise<Object|null>} Work item ID and status, or null if no active work
|
|
254
|
-
* @throws {Error} If status is invalid, database missing, or file operations fail
|
|
255
|
-
*/
|
|
256
|
-
function stopWork(newStatus = null) {
|
|
257
|
-
const paths = getPaths();
|
|
258
|
-
|
|
259
|
-
const currentWork = getCurrentWork();
|
|
260
|
-
if (!currentWork) {
|
|
261
|
-
console.log('No active work item');
|
|
262
|
-
return Promise.resolve(null);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Validate status if provided
|
|
266
|
-
if (newStatus && !VALID_STATUSES.includes(newStatus)) {
|
|
267
|
-
return Promise.reject(new Error(`Invalid status: ${newStatus}`));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return new Promise((resolve, reject) => {
|
|
271
|
-
if (newStatus) {
|
|
272
|
-
if (!fs.existsSync(paths.dbPath)) {
|
|
273
|
-
return reject(new Error('Work database not found'));
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const db = getDb();
|
|
277
|
-
db.run(`UPDATE work_items SET status = ? WHERE id = ?`, [newStatus, currentWork.id], (err) => {
|
|
278
|
-
if (err) {
|
|
279
|
-
return reject(new Error(`Database error: ${err.message}`));
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Check if this completes all stable mode chores for the parent feature
|
|
283
|
-
checkStableModeCompletion(db, currentWork, newStatus, (completionErr) => {
|
|
284
|
-
if (completionErr) {
|
|
285
|
-
console.warn(`Warning: ${completionErr.message}`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
clearCurrentWork();
|
|
290
|
-
} catch (unlinkErr) {
|
|
291
|
-
return reject(new Error(`Failed to remove current work file: ${unlinkErr.message}`));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
console.log(`Stopped work on #${currentWork.id}, status set to ${newStatus}`);
|
|
295
|
-
resolve({ id: currentWork.id, status: newStatus });
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
} else {
|
|
299
|
-
try {
|
|
300
|
-
clearCurrentWork();
|
|
301
|
-
} catch (err) {
|
|
302
|
-
return reject(new Error(`Failed to remove current work file: ${err.message}`));
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
console.log(`Stopped work on #${currentWork.id}`);
|
|
306
|
-
resolve({ id: currentWork.id });
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Check if stable mode is complete for a feature and trigger production chore generation
|
|
313
|
-
* @param {Object} db - Database connection
|
|
314
|
-
* @param {Object} currentWork - Current work item that was just completed
|
|
315
|
-
* @param {string} newStatus - Status that was just set
|
|
316
|
-
* @param {Function} callback - Callback function
|
|
317
|
-
*/
|
|
318
|
-
async function checkStableModeCompletion(db, currentWork, newStatus, callback) {
|
|
319
|
-
// Only check if:
|
|
320
|
-
// 1. Current work is a chore
|
|
321
|
-
// 2. Status was just set to 'done'
|
|
322
|
-
// 3. Current work has a parent (feature)
|
|
323
|
-
if (currentWork.type !== 'chore' || newStatus !== 'done' || !currentWork.parent_id) {
|
|
324
|
-
return callback(null);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Get parent feature details and check its mode
|
|
328
|
-
db.get(`
|
|
329
|
-
SELECT id, title, mode, type
|
|
330
|
-
FROM work_items
|
|
331
|
-
WHERE id = ?
|
|
332
|
-
`, [currentWork.parent_id], async (err, parent) => {
|
|
333
|
-
if (err) {
|
|
334
|
-
return callback(new Error(`Failed to get parent feature: ${err.message}`));
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (!parent) {
|
|
338
|
-
return callback(new Error(`Parent feature #${currentWork.parent_id} not found`));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Only proceed if parent is a feature in stable mode
|
|
342
|
-
if (parent.type !== 'feature' || parent.mode !== 'stable') {
|
|
343
|
-
return callback(null);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Check if all stable chores for this feature are done
|
|
347
|
-
db.get(`
|
|
348
|
-
SELECT COUNT(*) as incomplete_count
|
|
349
|
-
FROM work_items
|
|
350
|
-
WHERE parent_id = ?
|
|
351
|
-
AND type = 'chore'
|
|
352
|
-
AND mode = 'stable'
|
|
353
|
-
AND status != 'done'
|
|
354
|
-
`, [parent.id], async (countErr, result) => {
|
|
355
|
-
if (countErr) {
|
|
356
|
-
return callback(new Error(`Failed to check stable chore completion: ${countErr.message}`));
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// If all stable chores are done, stable mode is complete
|
|
360
|
-
if (result.incomplete_count === 0) {
|
|
361
|
-
console.log('');
|
|
362
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
363
|
-
console.log('✅ STABLE MODE COMPLETE');
|
|
364
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
365
|
-
console.log('');
|
|
366
|
-
console.log(`All stable chores for feature #${parent.id} "${parent.title}" are done.`);
|
|
367
|
-
console.log('');
|
|
368
|
-
|
|
369
|
-
// NOTE: Production mode transition is handled by the stable-mode skill
|
|
370
|
-
// The skill will:
|
|
371
|
-
// 1. Ask user if they want to add production scenarios
|
|
372
|
-
// 2. Add production scenarios to the feature file (BDD hot context)
|
|
373
|
-
// 3. Create production chores FROM those scenarios
|
|
374
|
-
// 4. Elevate feature to production mode
|
|
375
|
-
//
|
|
376
|
-
// Production chores are NOT auto-generated from git analysis.
|
|
377
|
-
// They come from BDD scenarios in the feature file.
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
callback(null);
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Generate production chores and get user confirmation
|
|
387
|
-
* @param {Object} feature - Feature object with id and title
|
|
388
|
-
*/
|
|
389
|
-
async function generateAndConfirmProductionChores(feature) {
|
|
390
|
-
const { analyzeImplementation, generateProductionChores } = require('../../lib/production-chore-generator');
|
|
391
|
-
const readline = require('readline');
|
|
392
|
-
|
|
393
|
-
console.log('🔍 Analyzing implementation for production gaps...');
|
|
394
|
-
console.log('');
|
|
395
|
-
|
|
396
|
-
// Analyze implementation
|
|
397
|
-
let analysisResult;
|
|
398
|
-
try {
|
|
399
|
-
analysisResult = await analyzeImplementation(feature.id);
|
|
400
|
-
} catch (analysisErr) {
|
|
401
|
-
console.error(`Failed to analyze implementation: ${analysisErr.message}`);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Display warning if no git commits found
|
|
406
|
-
if (analysisResult.warning) {
|
|
407
|
-
console.log(`⚠️ ${analysisResult.warning}`);
|
|
408
|
-
console.log('');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
console.log(`✅ Analyzed ${analysisResult.filesAnalyzed.length} implementation files`);
|
|
412
|
-
console.log('');
|
|
413
|
-
|
|
414
|
-
// Generate production chore proposals
|
|
415
|
-
const proposedChores = generateProductionChores(analysisResult, feature.title);
|
|
416
|
-
|
|
417
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
418
|
-
console.log('📋 PROPOSED PRODUCTION CHORES');
|
|
419
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
420
|
-
console.log('');
|
|
421
|
-
console.log('These production chores will be created:');
|
|
422
|
-
console.log('');
|
|
423
|
-
|
|
424
|
-
proposedChores.forEach((chore, index) => {
|
|
425
|
-
console.log(`${index + 1}. ${chore.title}`);
|
|
426
|
-
console.log(` ${chore.description.split('\n')[0]}`);
|
|
427
|
-
console.log('');
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// Get user confirmation
|
|
431
|
-
return new Promise((resolve) => {
|
|
432
|
-
const rl = readline.createInterface({
|
|
433
|
-
input: process.stdin,
|
|
434
|
-
output: process.stdout
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
rl.question('Create these production chores? (yes/skip): ', async (answer) => {
|
|
438
|
-
rl.close();
|
|
439
|
-
|
|
440
|
-
const response = answer.trim().toLowerCase();
|
|
441
|
-
|
|
442
|
-
if (response === 'yes' || response === 'y') {
|
|
443
|
-
console.log('');
|
|
444
|
-
console.log('Creating production chores...');
|
|
445
|
-
|
|
446
|
-
try {
|
|
447
|
-
await createProductionChoresAndElevate(feature, proposedChores);
|
|
448
|
-
console.log('');
|
|
449
|
-
console.log(`✅ Created ${proposedChores.length} production chores. Feature elevated to production mode.`);
|
|
450
|
-
console.log('');
|
|
451
|
-
} catch (createErr) {
|
|
452
|
-
console.error(`Failed to create production chores: ${createErr.message}`);
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
console.log('');
|
|
456
|
-
console.log('⏭️ Skipped production chore creation');
|
|
457
|
-
console.log(' You can generate them later by talking to Claude Code');
|
|
458
|
-
console.log('');
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
resolve();
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Create production chores and elevate feature to production mode (if external)
|
|
468
|
-
* @param {Object} feature - Feature object
|
|
469
|
-
* @param {Array} proposedChores - Array of chore proposals
|
|
470
|
-
*/
|
|
471
|
-
async function createProductionChoresAndElevate(feature, proposedChores) {
|
|
472
|
-
const { create } = require('../work-tracking');
|
|
473
|
-
const projectConfig = config.read();
|
|
474
|
-
|
|
475
|
-
// Create production chores
|
|
476
|
-
for (const chore of proposedChores) {
|
|
477
|
-
await create('chore', chore.title, chore.description, feature.id, null, false);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Always elevate feature to production mode
|
|
481
|
-
// (Feature #611 controls visibility in backlog, not mode elevation)
|
|
482
|
-
const db = getDb();
|
|
483
|
-
await new Promise((resolve, reject) => {
|
|
484
|
-
db.run('UPDATE work_items SET mode = ? WHERE id = ?', ['production', feature.id], (err) => {
|
|
485
|
-
if (err) return reject(err);
|
|
486
|
-
resolve();
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Re-export getCurrentWork from shared module for backwards compatibility
|
|
492
|
-
// (used by jettypod.js)
|
|
493
|
-
|
|
494
|
-
module.exports = {
|
|
495
|
-
startWork,
|
|
496
|
-
stopWork,
|
|
497
|
-
getCurrentWork
|
|
498
|
-
};
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
const { Given, When, Then } = require('@cucumber/cucumber');
|
|
2
|
-
const assert = require('assert');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
const { resetDb } = require('../../lib/database');
|
|
7
|
-
|
|
8
|
-
const testDir = path.join('/tmp', 'jettypod-test-' + Date.now());
|
|
9
|
-
let originalDir;
|
|
10
|
-
|
|
11
|
-
Given('I have initialized jettypod', function () {
|
|
12
|
-
resetDb(); // Clear singleton db to avoid stale connections
|
|
13
|
-
|
|
14
|
-
originalDir = process.cwd();
|
|
15
|
-
// SAFETY: Only delete if testDir is in /tmp
|
|
16
|
-
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
17
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
18
|
-
}
|
|
19
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
20
|
-
process.chdir(testDir);
|
|
21
|
-
|
|
22
|
-
execSync('git init', { stdio: 'pipe' });
|
|
23
|
-
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
24
|
-
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
25
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
Given('I create an epic with id {int}', function (id) {
|
|
29
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create epic "Test Epic"`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
Given('I create a feature with id {int} under epic {int}', function (featureId, epicId) {
|
|
33
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test Feature" "" --parent=${epicId}`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
When('I start work on item {int}', function (id) {
|
|
37
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work start ${id}`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
Then('the current work file exists', function () {
|
|
41
|
-
const currentWorkPath = path.join(testDir, '.jettypod', 'current-work.json');
|
|
42
|
-
assert(fs.existsSync(currentWorkPath), 'Current work file does not exist');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
Then('the current work contains item {int}', function (id) {
|
|
46
|
-
const currentWorkPath = path.join(testDir, '.jettypod', 'current-work.json');
|
|
47
|
-
const currentWork = JSON.parse(fs.readFileSync(currentWorkPath, 'utf-8'));
|
|
48
|
-
assert.strictEqual(currentWork.id, id);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
Then('item {int} status is in_progress or backlog', function (id) {
|
|
52
|
-
// Use CLI to check status to avoid database connection issues
|
|
53
|
-
try {
|
|
54
|
-
const output = execSync(`node ${path.join(originalDir, 'jettypod.js')} work show ${id}`, { encoding: 'utf-8', env: { ...process.env, NODE_ENV: 'test' } });
|
|
55
|
-
const statusMatch = output.match(/Status:\s+(\w+)/);
|
|
56
|
-
if (!statusMatch) {
|
|
57
|
-
throw new Error('Could not find status in output');
|
|
58
|
-
}
|
|
59
|
-
const status = statusMatch[1];
|
|
60
|
-
assert(status === 'in_progress' || status === 'backlog', `Status is ${status}`);
|
|
61
|
-
} finally {
|
|
62
|
-
// SAFETY: Only delete if testDir is in /tmp
|
|
63
|
-
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
64
|
-
process.chdir(originalDir);
|
|
65
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
66
|
-
}
|
|
67
|
-
resetDb(); // Clear singleton after cleanup
|
|
68
|
-
}
|
|
69
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
Feature: Work Commands - Stable Mode
|
|
2
|
-
Edge cases and error handling for work commands
|
|
3
|
-
|
|
4
|
-
Scenario: Start work with invalid ID
|
|
5
|
-
Given jettypod is initialized
|
|
6
|
-
When I try to start work with ID "invalid"
|
|
7
|
-
Then I get an error "Invalid work item ID"
|
|
8
|
-
|
|
9
|
-
Scenario: Start work with negative ID
|
|
10
|
-
Given jettypod is initialized
|
|
11
|
-
When I try to start work with ID "-1"
|
|
12
|
-
Then I get an error "Invalid work item ID"
|
|
13
|
-
|
|
14
|
-
Scenario: Start work when not initialized
|
|
15
|
-
Given jettypod is not initialized
|
|
16
|
-
When I try to start work with ID "1"
|
|
17
|
-
Then I get an error "JettyPod not initialized"
|
|
18
|
-
|
|
19
|
-
Scenario: Start work on non-existent item
|
|
20
|
-
Given jettypod is initialized
|
|
21
|
-
When I try to start work with ID "999"
|
|
22
|
-
Then I get an error "Work item #999 not found"
|
|
23
|
-
|
|
24
|
-
Scenario: Stop work with invalid status
|
|
25
|
-
Given I have current work
|
|
26
|
-
When I try to stop work with status "invalid"
|
|
27
|
-
Then I get an error "Invalid status"
|
|
28
|
-
|
|
29
|
-
Scenario: Stop work when no current work
|
|
30
|
-
Given jettypod is initialized
|
|
31
|
-
And no work is active
|
|
32
|
-
When I try to stop work
|
|
33
|
-
Then operation succeeds with no changes
|
|
34
|
-
|
|
35
|
-
Scenario: Get current work with corrupted file
|
|
36
|
-
Given jettypod is initialized
|
|
37
|
-
And current work file is corrupted
|
|
38
|
-
When I get current work
|
|
39
|
-
Then it returns null
|
|
40
|
-
|
|
41
|
-
Scenario: Start work without git repo
|
|
42
|
-
Given jettypod is initialized without git
|
|
43
|
-
And I have a work item
|
|
44
|
-
When I start work on the item
|
|
45
|
-
Then it succeeds without creating branch
|
|
46
|
-
|
|
47
|
-
Scenario: Multiple start work calls
|
|
48
|
-
Given I have a work item
|
|
49
|
-
And I start work on it
|
|
50
|
-
When I start work on a different item
|
|
51
|
-
Then the first item stops being current
|
|
52
|
-
And the second item becomes current
|
|
53
|
-
|
|
54
|
-
Scenario: Start work preserves existing status
|
|
55
|
-
Given I have a work item with status "in_progress"
|
|
56
|
-
When I start work on it
|
|
57
|
-
Then the status remains "in_progress"
|