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,196 +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 sqlite3 = require('sqlite3').verbose();
|
|
7
|
-
|
|
8
|
-
const testDir = path.join('/tmp', 'jettypod-hooks-test-' + Date.now());
|
|
9
|
-
const dbFileName = process.env.NODE_ENV === 'test' ? 'test-work.db' : 'work.db';
|
|
10
|
-
let originalDir;
|
|
11
|
-
|
|
12
|
-
// Helper to ensure NODE_ENV=test is always set for test execSync calls
|
|
13
|
-
function testExecSync(command, options = {}) {
|
|
14
|
-
const env = { ...process.env, NODE_ENV: 'test', ...options.env };
|
|
15
|
-
return execSync(command, { ...options, env });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
Given('I have initialized jettypod with git', function () {
|
|
19
|
-
originalDir = process.cwd();
|
|
20
|
-
// SAFETY: Only delete if testDir is in /tmp
|
|
21
|
-
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
22
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
23
|
-
}
|
|
24
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
25
|
-
process.chdir(testDir);
|
|
26
|
-
|
|
27
|
-
execSync('git init', { stdio: 'pipe' });
|
|
28
|
-
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
29
|
-
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
30
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
31
|
-
|
|
32
|
-
// Verify hooks installed
|
|
33
|
-
const hookPath = path.join(testDir, '.git', 'hooks', 'post-commit');
|
|
34
|
-
this.hooksInstalled = fs.existsSync(hookPath);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
Given('I create a work item via work commands', function () {
|
|
38
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test Feature"`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
39
|
-
this.workItemId = 1;
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
Given('I start work on the item', function () {
|
|
43
|
-
const jettypodPath = path.join(__dirname, '../../jettypod.js');
|
|
44
|
-
const workDir = process.cwd();
|
|
45
|
-
const workItemId = this.workItemId || 1;
|
|
46
|
-
|
|
47
|
-
testExecSync(`node ${jettypodPath} work start ${workItemId}`, { cwd: workDir, stdio: 'pipe' });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
When('I commit changes', function () {
|
|
51
|
-
fs.writeFileSync('test.txt', 'test content');
|
|
52
|
-
execSync('git add .', { stdio: 'pipe' });
|
|
53
|
-
execSync('git commit -m "test commit"', { stdio: 'pipe' });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
Then('the work item status updates automatically', function () {
|
|
57
|
-
const dbPath = path.join(testDir, '.jettypod', dbFileName);
|
|
58
|
-
const db = new sqlite3.Database(dbPath);
|
|
59
|
-
|
|
60
|
-
return new Promise((resolve) => {
|
|
61
|
-
db.get(`SELECT status FROM work_items WHERE id = ?`, [this.workItemId], (err, row) => {
|
|
62
|
-
db.close();
|
|
63
|
-
// Should be in_progress (updated by post-commit hook)
|
|
64
|
-
assert(row.status === 'in_progress' || row.status === 'backlog',
|
|
65
|
-
`Expected in_progress or backlog, got ${row.status}`);
|
|
66
|
-
resolve();
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
Then('the current work file still exists', function () {
|
|
72
|
-
const currentWorkPath = path.join(testDir, '.jettypod', 'current-work.json');
|
|
73
|
-
assert(fs.existsSync(currentWorkPath), 'Current work file should still exist');
|
|
74
|
-
|
|
75
|
-
// Cleanup
|
|
76
|
-
if (fs.existsSync(testDir)) {
|
|
77
|
-
process.chdir(originalDir);
|
|
78
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
Given('I have a work item with status {string}', function (status) {
|
|
83
|
-
originalDir = process.cwd();
|
|
84
|
-
// SAFETY: Only delete if testDir is in /tmp
|
|
85
|
-
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
86
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
87
|
-
}
|
|
88
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
89
|
-
process.chdir(testDir);
|
|
90
|
-
|
|
91
|
-
execSync('git init', { stdio: 'pipe' });
|
|
92
|
-
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
93
|
-
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
94
|
-
|
|
95
|
-
// Make initial commit so branch exists
|
|
96
|
-
fs.writeFileSync('README.md', '# Test');
|
|
97
|
-
execSync('git add .', { stdio: 'pipe' });
|
|
98
|
-
execSync('git commit -m "Initial commit"', { stdio: 'pipe' });
|
|
99
|
-
|
|
100
|
-
// Store default branch name
|
|
101
|
-
this.defaultBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
|
|
102
|
-
|
|
103
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
104
|
-
|
|
105
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test"`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
106
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work status 1 ${status}`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
107
|
-
|
|
108
|
-
this.workItemId = 1;
|
|
109
|
-
this.initialStatus = status;
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
Given('the work item is set as current work', function () {
|
|
113
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work start ${this.workItemId}`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
Given('I am on a feature branch', function () {
|
|
117
|
-
// Make initial commit on main so it exists
|
|
118
|
-
fs.writeFileSync('init.txt', 'init');
|
|
119
|
-
execSync('git add .', { stdio: 'pipe' });
|
|
120
|
-
execSync('git commit -m "initial commit"', { stdio: 'pipe' });
|
|
121
|
-
|
|
122
|
-
// Now create feature branch
|
|
123
|
-
execSync('git checkout -b feature/test', { stdio: 'pipe' });
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
When('I make my first commit', function () {
|
|
127
|
-
fs.writeFileSync('test.txt', 'test');
|
|
128
|
-
execSync('git add .', { stdio: 'pipe' });
|
|
129
|
-
execSync('git commit -m "first commit"', { stdio: 'pipe' });
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
When('I merge to main', function () {
|
|
133
|
-
// Commit on feature branch
|
|
134
|
-
fs.writeFileSync('feature.txt', 'feature');
|
|
135
|
-
execSync('git add .', { stdio: 'pipe' });
|
|
136
|
-
execSync('git commit -m "feature commit"', { stdio: 'pipe' });
|
|
137
|
-
|
|
138
|
-
// Switch to default branch and merge
|
|
139
|
-
execSync(`git checkout ${this.defaultBranch}`, { stdio: 'pipe' });
|
|
140
|
-
execSync('git merge feature/test --no-edit', { stdio: 'pipe' });
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
Then('the work item status should be {string}', function (expectedStatus) {
|
|
144
|
-
const dbPath = path.join(testDir, '.jettypod', dbFileName);
|
|
145
|
-
|
|
146
|
-
return new Promise((resolve) => {
|
|
147
|
-
// Give the post-merge hook time to complete
|
|
148
|
-
setTimeout(() => {
|
|
149
|
-
const db = new sqlite3.Database(dbPath);
|
|
150
|
-
db.get(`SELECT status FROM work_items WHERE id = ?`, [this.workItemId], (err, row) => {
|
|
151
|
-
db.close();
|
|
152
|
-
assert.strictEqual(row.status, expectedStatus);
|
|
153
|
-
|
|
154
|
-
// Cleanup
|
|
155
|
-
if (fs.existsSync(testDir)) {
|
|
156
|
-
process.chdir(originalDir);
|
|
157
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
resolve();
|
|
161
|
-
});
|
|
162
|
-
}, 50); // Small delay to ensure hook completes
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
Given('no work item is set as current', function () {
|
|
167
|
-
originalDir = process.cwd();
|
|
168
|
-
// SAFETY: Only delete if testDir is in /tmp
|
|
169
|
-
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
170
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
171
|
-
}
|
|
172
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
173
|
-
process.chdir(testDir);
|
|
174
|
-
|
|
175
|
-
execSync('git init', { stdio: 'pipe' });
|
|
176
|
-
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
177
|
-
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
178
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
When('I make a commit', function () {
|
|
182
|
-
fs.writeFileSync('test.txt', 'test');
|
|
183
|
-
execSync('git add .', { stdio: 'pipe' });
|
|
184
|
-
execSync('git commit -m "test"', { stdio: 'pipe' });
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
Then('no errors occur', function () {
|
|
188
|
-
// If we got here, no errors occurred
|
|
189
|
-
assert(true);
|
|
190
|
-
|
|
191
|
-
// Cleanup
|
|
192
|
-
if (fs.existsSync(testDir)) {
|
|
193
|
-
process.chdir(originalDir);
|
|
194
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
195
|
-
}
|
|
196
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
Feature: JettyPod update command
|
|
2
|
-
Self-updating jettypod and skills refresh with a single command
|
|
3
|
-
|
|
4
|
-
Epic: JettyPod and Skills Auto-Update
|
|
5
|
-
Approach: Full self-update - Check npm, update jettypod, refresh skills
|
|
6
|
-
|
|
7
|
-
Scenario: User runs jettypod update when new version is available
|
|
8
|
-
Given jettypod version 3.0.0 is installed
|
|
9
|
-
And npm registry has version 3.1.0 available
|
|
10
|
-
When I run update command "jettypod update"
|
|
11
|
-
Then jettypod checks npm registry for latest version
|
|
12
|
-
And jettypod shows "New version available: 3.1.0 (current: 3.0.0)"
|
|
13
|
-
And jettypod downloads and installs version 3.1.0
|
|
14
|
-
And skills are refreshed in current project
|
|
15
|
-
And jettypod shows "✅ JettyPod updated to 3.1.0"
|
|
16
|
-
|
|
17
|
-
Scenario: User runs jettypod update when already on latest version
|
|
18
|
-
Given jettypod version 3.1.0 is installed
|
|
19
|
-
And npm registry has version 3.1.0 available
|
|
20
|
-
When I run update command "jettypod update"
|
|
21
|
-
Then jettypod checks npm registry for latest version
|
|
22
|
-
And jettypod shows "Already on latest version: 3.1.0"
|
|
23
|
-
And skills are refreshed in current project
|
|
24
|
-
|
|
25
|
-
Scenario: User runs jettypod update without internet connection
|
|
26
|
-
Given jettypod is installed
|
|
27
|
-
And there is no internet connection
|
|
28
|
-
When I run update command "jettypod update"
|
|
29
|
-
Then jettypod shows "Cannot check for updates: network error"
|
|
30
|
-
And skills are still refreshed in current project using existing jettypod
|
|
31
|
-
|
|
32
|
-
Scenario: Command rename - jettypod without init creates new project
|
|
33
|
-
Given I am in a directory without .claude/
|
|
34
|
-
When I run update command "jettypod"
|
|
35
|
-
Then jettypod initializes the project
|
|
36
|
-
And .claude/ directory is created
|
|
37
|
-
And CLAUDE.md is created
|
|
38
|
-
And skills are installed
|
|
39
|
-
|
|
40
|
-
Scenario: Backwards compatibility - jettypod init still works
|
|
41
|
-
Given I am in a directory without .claude/
|
|
42
|
-
When I run update command "jettypod init"
|
|
43
|
-
Then jettypod initializes the project
|
|
44
|
-
And .claude/ directory is created
|
|
45
|
-
And CLAUDE.md is created
|
|
46
|
-
And skills are installed
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
const readline = require('readline');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const sqlite3 = require('sqlite3').verbose();
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Prompt user for work item status update
|
|
8
|
-
* @param {Object} currentWork - Current work item with id, title, and status
|
|
9
|
-
* @returns {Promise<string|null>} New status or null if skipped
|
|
10
|
-
* @throws {Error} If currentWork is invalid or missing required fields
|
|
11
|
-
*/
|
|
12
|
-
function promptForStatusUpdate(currentWork) {
|
|
13
|
-
// Validate input
|
|
14
|
-
if (!currentWork || typeof currentWork !== 'object') {
|
|
15
|
-
return Promise.reject(new Error('Invalid currentWork: must be an object'));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (!currentWork.id || !currentWork.title || !currentWork.status) {
|
|
19
|
-
return Promise.reject(new Error('Invalid currentWork: missing id, title, or status'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return new Promise((resolve, reject) => {
|
|
23
|
-
const rl = readline.createInterface({
|
|
24
|
-
input: process.stdin,
|
|
25
|
-
output: process.stdout
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
console.log(`\nCurrent work: [#${currentWork.id}] ${currentWork.title} (${currentWork.status})`);
|
|
29
|
-
rl.question('Update status? [in_progress/blocked/done/skip]: ', (answer) => {
|
|
30
|
-
rl.close();
|
|
31
|
-
|
|
32
|
-
const status = answer.trim();
|
|
33
|
-
if (status === 'skip' || status === '') {
|
|
34
|
-
resolve(null);
|
|
35
|
-
} else if (['in_progress', 'blocked', 'done', 'todo', 'backlog'].includes(status)) {
|
|
36
|
-
resolve(status);
|
|
37
|
-
} else {
|
|
38
|
-
console.log('Invalid status, skipping update');
|
|
39
|
-
resolve(null);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Update work item status in database and current work file
|
|
47
|
-
* Uses shared updateStatus from work-tracking for consistency and epic auto-close logic
|
|
48
|
-
* @param {number} workItemId - ID of work item to update
|
|
49
|
-
* @param {string} newStatus - New status value
|
|
50
|
-
* @returns {Promise<void>}
|
|
51
|
-
* @throws {Error} If workItemId is invalid, newStatus is invalid, database not found, or file operations fail
|
|
52
|
-
*/
|
|
53
|
-
async function updateWorkItemStatus(workItemId, newStatus) {
|
|
54
|
-
// Validate inputs
|
|
55
|
-
if (!workItemId || isNaN(workItemId) || workItemId < 1) {
|
|
56
|
-
return Promise.reject(new Error('Invalid work item ID'));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const validStatuses = ['in_progress', 'blocked', 'done', 'todo', 'backlog', 'cancelled'];
|
|
60
|
-
if (!newStatus || !validStatuses.includes(newStatus)) {
|
|
61
|
-
return Promise.reject(new Error(`Invalid status: ${newStatus}`));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const jettypodDir = path.join(process.cwd(), '.jettypod');
|
|
65
|
-
const { getDbPath } = require('../../lib/database');
|
|
66
|
-
const dbPath = getDbPath();
|
|
67
|
-
const currentWorkPath = path.join(jettypodDir, 'current-work.json');
|
|
68
|
-
|
|
69
|
-
// Check database exists
|
|
70
|
-
if (!fs.existsSync(dbPath)) {
|
|
71
|
-
return Promise.reject(new Error('Work database not found. Run: jettypod init'));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Use shared updateStatus from work-tracking (includes epic auto-close logic)
|
|
75
|
-
const { updateStatus } = require('../work-tracking');
|
|
76
|
-
await updateStatus(workItemId, newStatus);
|
|
77
|
-
|
|
78
|
-
// Update current work file if it exists
|
|
79
|
-
if (fs.existsSync(currentWorkPath)) {
|
|
80
|
-
try {
|
|
81
|
-
const currentWork = JSON.parse(fs.readFileSync(currentWorkPath, 'utf-8'));
|
|
82
|
-
currentWork.status = newStatus;
|
|
83
|
-
fs.writeFileSync(currentWorkPath, JSON.stringify(currentWork, null, 2));
|
|
84
|
-
} catch (fileErr) {
|
|
85
|
-
return Promise.reject(new Error(`Failed to update current work file: ${fileErr.message}`));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
console.log(`✓ Work item #${workItemId} status updated to ${newStatus}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
module.exports = {
|
|
93
|
-
promptForStatusUpdate,
|
|
94
|
-
updateWorkItemStatus
|
|
95
|
-
};
|
|
@@ -1,44 +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
|
-
|
|
7
|
-
const testDir = path.join('/tmp', 'mode-prompts-' + Date.now());
|
|
8
|
-
let originalDir;
|
|
9
|
-
|
|
10
|
-
Given('I have jettypod with a work item in progress', function () {
|
|
11
|
-
originalDir = process.cwd();
|
|
12
|
-
// SAFETY: Only delete if testDir is in /tmp
|
|
13
|
-
if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
|
|
14
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
15
|
-
}
|
|
16
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
17
|
-
process.chdir(testDir);
|
|
18
|
-
|
|
19
|
-
execSync('git init', { stdio: 'pipe' });
|
|
20
|
-
execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
|
|
21
|
-
execSync('git config user.name "Test"', { stdio: 'pipe' });
|
|
22
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} init`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
23
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work create feature "Test"`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
24
|
-
execSync(`node ${path.join(originalDir, 'jettypod.js')} work start 1`, { stdio: 'pipe', env: { ...process.env, NODE_ENV: 'test' } });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
When('I check current work exists', function () {
|
|
28
|
-
const currentWorkPath = path.join(testDir, '.jettypod', 'current-work.json');
|
|
29
|
-
this.currentWorkExists = fs.existsSync(currentWorkPath);
|
|
30
|
-
if (this.currentWorkExists) {
|
|
31
|
-
this.currentWork = JSON.parse(fs.readFileSync(currentWorkPath, 'utf-8'));
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
Then('the work item has a status', function () {
|
|
36
|
-
assert(this.currentWorkExists, 'Current work file should exist');
|
|
37
|
-
assert(this.currentWork.status, 'Work item should have a status');
|
|
38
|
-
|
|
39
|
-
// Cleanup
|
|
40
|
-
if (fs.existsSync(testDir)) {
|
|
41
|
-
process.chdir(originalDir);
|
|
42
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Feature: Mode Change Prompts
|
|
2
|
-
As a developer
|
|
3
|
-
I want to update work status when switching modes
|
|
4
|
-
So my work state stays current
|
|
5
|
-
|
|
6
|
-
Scenario: Integration - mode change with work item present
|
|
7
|
-
Given I have jettypod with a work item in progress
|
|
8
|
-
When I check current work exists
|
|
9
|
-
Then the work item has a status
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// Unit tests for mode-prompts validation logic
|
|
2
|
-
// These test the error handling and validation without user interaction
|
|
3
|
-
|
|
4
|
-
const { VALID_STATUSES } = require('../../lib/constants');
|
|
5
|
-
|
|
6
|
-
describe('Mode Prompts - Validation', () => {
|
|
7
|
-
describe('promptForStatusUpdate validation', () => {
|
|
8
|
-
test('should validate currentWork is an object', () => {
|
|
9
|
-
const invalidInputs = [null, undefined, 'string', 123, [], true];
|
|
10
|
-
invalidInputs.forEach(input => {
|
|
11
|
-
const isValid = !!(input && typeof input === 'object' && !Array.isArray(input));
|
|
12
|
-
expect(isValid).toBe(false);
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('should validate currentWork has required fields', () => {
|
|
17
|
-
const invalidObjects = [
|
|
18
|
-
{},
|
|
19
|
-
{ id: 1 },
|
|
20
|
-
{ id: 1, title: 'Test' },
|
|
21
|
-
{ title: 'Test', status: 'todo' },
|
|
22
|
-
{ id: 1, status: 'todo' }
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
invalidObjects.forEach(obj => {
|
|
26
|
-
const hasRequired = !!(obj.id && obj.title && obj.status);
|
|
27
|
-
expect(hasRequired).toBe(false);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('should accept valid currentWork objects', () => {
|
|
32
|
-
const validObjects = [
|
|
33
|
-
{ id: 1, title: 'Test', status: 'todo' },
|
|
34
|
-
{ id: 2, title: 'Feature', status: 'in_progress', type: 'feature' }
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
validObjects.forEach(obj => {
|
|
38
|
-
const hasRequired = !!(obj.id && obj.title && obj.status);
|
|
39
|
-
expect(hasRequired).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('should validate status values', () => {
|
|
44
|
-
const validStatuses = ['in_progress', 'blocked', 'done', 'todo', 'backlog'];
|
|
45
|
-
const invalidStatuses = ['invalid', 'foo', '', null, undefined];
|
|
46
|
-
|
|
47
|
-
validStatuses.forEach(status => {
|
|
48
|
-
expect(validStatuses.includes(status)).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
invalidStatuses.forEach(status => {
|
|
52
|
-
const isValid = !!(status && validStatuses.includes(status));
|
|
53
|
-
expect(isValid).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('should accept skip or empty as null', () => {
|
|
58
|
-
const nullInputs = ['skip', ''];
|
|
59
|
-
nullInputs.forEach(input => {
|
|
60
|
-
const shouldBeNull = input === 'skip' || input === '';
|
|
61
|
-
expect(shouldBeNull).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('updateWorkItemStatus validation', () => {
|
|
67
|
-
test('should validate work item ID is numeric', () => {
|
|
68
|
-
const invalidIds = [null, undefined, 'abc', '', {}, []];
|
|
69
|
-
invalidIds.forEach(id => {
|
|
70
|
-
const isValid = !!(id && !isNaN(id) && id >= 1);
|
|
71
|
-
expect(isValid).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('should validate work item ID is positive', () => {
|
|
76
|
-
expect(!!(0 && !isNaN(0) && 0 >= 1)).toBe(false);
|
|
77
|
-
expect(!!(-1 && !isNaN(-1) && -1 >= 1)).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('should accept valid work item IDs', () => {
|
|
81
|
-
const validIds = [1, 2, 100, 999];
|
|
82
|
-
validIds.forEach(id => {
|
|
83
|
-
const isValid = id && !isNaN(id) && id >= 1;
|
|
84
|
-
expect(isValid).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('should validate status is in valid statuses', () => {
|
|
89
|
-
const validStatuses = ['in_progress', 'blocked', 'done', 'todo', 'backlog', 'cancelled'];
|
|
90
|
-
const invalidStatuses = ['invalid', 'foo', '', null, undefined];
|
|
91
|
-
|
|
92
|
-
validStatuses.forEach(status => {
|
|
93
|
-
expect(validStatuses.includes(status)).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
invalidStatuses.forEach(status => {
|
|
97
|
-
const isValid = !!(status && validStatuses.includes(status));
|
|
98
|
-
expect(isValid).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('Database path validation', () => {
|
|
104
|
-
test('should check jettypod directory exists', () => {
|
|
105
|
-
const fs = require('fs');
|
|
106
|
-
const path = require('path');
|
|
107
|
-
|
|
108
|
-
const nonExistentPath = '/nonexistent/path/.jettypod';
|
|
109
|
-
expect(fs.existsSync(nonExistentPath)).toBe(false);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('should check database file exists', () => {
|
|
113
|
-
const fs = require('fs');
|
|
114
|
-
const path = require('path');
|
|
115
|
-
|
|
116
|
-
const nonExistentDb = '/nonexistent/path/.jettypod/work.db';
|
|
117
|
-
expect(fs.existsSync(nonExistentDb)).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
@multiple-instances
|
|
2
|
-
Feature: Multiple Claude Code instances coordination
|
|
3
|
-
Commit queue system ensures safe, ordered pushes to main when multiple
|
|
4
|
-
Claude Code instances work in the same directory simultaneously
|
|
5
|
-
|
|
6
|
-
Approach: Commit Queue System
|
|
7
|
-
|
|
8
|
-
Scenario: Single Claude instance pushes to main without queue
|
|
9
|
-
Given I am a Claude Code instance with completed work
|
|
10
|
-
And I have committed my changes locally
|
|
11
|
-
And the push queue is empty
|
|
12
|
-
And main branch has no new commits
|
|
13
|
-
When I attempt to push to main
|
|
14
|
-
Then I push directly to main
|
|
15
|
-
And I do not create a queue entry
|
|
16
|
-
|
|
17
|
-
Scenario: First Claude instance creates queue entry when main is ahead
|
|
18
|
-
Given I am a Claude Code instance with completed work
|
|
19
|
-
And I have committed my changes locally
|
|
20
|
-
And the push queue is empty
|
|
21
|
-
And main branch has 2 new commits since my work started
|
|
22
|
-
When I attempt to push to main
|
|
23
|
-
Then I create a queue entry at position 1
|
|
24
|
-
And I see "Added to push queue (position 1)"
|
|
25
|
-
And I rebase my commits on top of main
|
|
26
|
-
And I push to main
|
|
27
|
-
And I remove my queue entry
|
|
28
|
-
|
|
29
|
-
Scenario: Second Claude waits for first to complete
|
|
30
|
-
Given Claude instance A is at position 1 in the push queue
|
|
31
|
-
And I am a Claude Code instance with completed work
|
|
32
|
-
And I have committed my changes locally
|
|
33
|
-
When I attempt to push to main
|
|
34
|
-
Then I create a queue entry at position 2
|
|
35
|
-
And I see "Added to push queue (position 2, waiting for 1 commit ahead)"
|
|
36
|
-
And I do not push yet
|
|
37
|
-
And I wait for position 1 to complete
|
|
38
|
-
|
|
39
|
-
Scenario: Queue processes sequentially when first completes
|
|
40
|
-
Given Claude instance A is at position 1 and is pushing
|
|
41
|
-
And I am at position 2 in the queue
|
|
42
|
-
When Claude instance A completes their push
|
|
43
|
-
Then I move to position 1
|
|
44
|
-
And I rebase my commits on top of the updated main
|
|
45
|
-
And I push to main
|
|
46
|
-
And I remove my queue entry
|
|
47
|
-
|
|
48
|
-
# SPEED MODE: Only happy path above
|
|
49
|
-
# STABLE MODE: Error handling, edge cases, validation scenarios below
|
|
50
|
-
|
|
51
|
-
Scenario: Queue file corrupted or contains invalid JSON
|
|
52
|
-
Given the push queue file exists but contains invalid JSON
|
|
53
|
-
When I attempt to push to main
|
|
54
|
-
Then I should see an error message about corrupted queue file
|
|
55
|
-
And the system should initialize a new empty queue
|
|
56
|
-
And I should be able to push successfully
|
|
57
|
-
|
|
58
|
-
Scenario: Queue file cannot be written due to permissions
|
|
59
|
-
Given the .jettypod directory has no write permissions
|
|
60
|
-
When I attempt to push to main
|
|
61
|
-
Then I should see an error about insufficient permissions
|
|
62
|
-
And the system should provide guidance to fix permissions
|
|
63
|
-
And the push should not proceed
|
|
64
|
-
|
|
65
|
-
Scenario: Git rebase fails with conflicts
|
|
66
|
-
Given I am at position 1 in the queue
|
|
67
|
-
And my changes conflict with changes on main
|
|
68
|
-
When I attempt to rebase on main
|
|
69
|
-
Then I should see a clear error about rebase conflicts
|
|
70
|
-
And I should receive guidance on how to resolve conflicts
|
|
71
|
-
And my queue entry should remain until conflicts are resolved
|
|
72
|
-
|
|
73
|
-
Scenario: Git fetch fails due to network issues
|
|
74
|
-
Given I am checking if main has moved ahead
|
|
75
|
-
When git fetch fails due to network connectivity
|
|
76
|
-
Then I should see an error about network failure
|
|
77
|
-
And the system should suggest retry or check connectivity
|
|
78
|
-
And the push should not proceed
|
|
79
|
-
|
|
80
|
-
Scenario: Git push fails after successful rebase
|
|
81
|
-
Given I have successfully rebased on main
|
|
82
|
-
When git push fails due to remote rejection
|
|
83
|
-
Then I should see a clear error message
|
|
84
|
-
And my queue entry should remain for retry
|
|
85
|
-
And the system should not corrupt the queue state
|
|
86
|
-
|
|
87
|
-
Scenario: Invalid queue entry with missing fields
|
|
88
|
-
Given the queue contains an entry with missing instanceId
|
|
89
|
-
When the queue processor attempts to process entries
|
|
90
|
-
Then invalid entries should be removed from queue
|
|
91
|
-
And valid entries should continue processing
|
|
92
|
-
And a warning should be logged about invalid entries
|
|
93
|
-
|
|
94
|
-
Scenario: Stale queue entry from crashed instance
|
|
95
|
-
Given a queue entry has been waiting for over 1 hour
|
|
96
|
-
And the instance that created it is no longer running
|
|
97
|
-
When queue cleanup runs
|
|
98
|
-
Then the stale entry should be removed
|
|
99
|
-
And subsequent entries should advance in position
|
|
100
|
-
And a notification should be logged
|
|
101
|
-
|
|
102
|
-
Scenario: Multiple instances try to write queue simultaneously
|
|
103
|
-
Given two Claude instances both try to add queue entries at same time
|
|
104
|
-
When concurrent queue writes occur
|
|
105
|
-
Then both entries should be successfully added
|
|
106
|
-
And no queue corruption should occur
|
|
107
|
-
And positions should be correctly assigned
|
|
108
|
-
|
|
109
|
-
Scenario: Empty commit SHA provided to queue
|
|
110
|
-
Given I attempt to add a queue entry with empty commit SHA
|
|
111
|
-
When validation runs
|
|
112
|
-
Then I should see an error about invalid commit SHA
|
|
113
|
-
And no queue entry should be created
|
|
114
|
-
And guidance should be provided
|
|
115
|
-
|
|
116
|
-
Scenario: Queue position monitoring with removed entries
|
|
117
|
-
Given I am at position 3 in the queue
|
|
118
|
-
When entry at position 1 is removed (not completed normally)
|
|
119
|
-
Then I should move to position 2
|
|
120
|
-
And I should be notified of position change
|
|
121
|
-
And queue integrity should be maintained
|