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.
Files changed (179) hide show
  1. package/.nvmrc +1 -0
  2. package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
  3. package/docs/DECISIONS.md +10 -12
  4. package/docs/NODE_VERSION.md +83 -0
  5. package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
  6. package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
  7. package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
  8. package/hooks/post-checkout +17 -0
  9. package/hooks/post-merge +17 -0
  10. package/hooks/pre-commit +30 -0
  11. package/jettypod.js +259 -120
  12. package/lib/coverage-tracker.js +218 -0
  13. package/lib/database.js +2 -0
  14. package/lib/db-export.js +192 -0
  15. package/lib/db-import.js +193 -0
  16. package/lib/external-transition-handler.js +32 -0
  17. package/lib/git-hook-helpers.js +174 -0
  18. package/lib/git-root.js +90 -0
  19. package/lib/infrastructure-chore-generator.js +45 -0
  20. package/lib/install-hooks.js +52 -0
  21. package/lib/jettypod-backup.js +238 -0
  22. package/lib/merge-lock.js +193 -0
  23. package/lib/migrations/012-add-worktree-path.js +38 -0
  24. package/lib/migrations/013-worktrees-table.js +86 -0
  25. package/lib/migrations/014-migrate-worktree-data.js +161 -0
  26. package/lib/migrations/015-merge-locks-table.js +67 -0
  27. package/lib/pattern-finder.js +152 -0
  28. package/lib/process-manager.js +140 -0
  29. package/lib/production-standards-reader.js +13 -2
  30. package/lib/production-standards-writer.js +85 -0
  31. package/lib/skills/feature-planning/dry-run-validator.js +135 -0
  32. package/lib/skills/feature-planning/validation-formatter.js +160 -0
  33. package/lib/smart-conflict-detection.js +168 -0
  34. package/lib/smart-fetch-rebase.js +614 -0
  35. package/lib/step-definition-parser.js +76 -0
  36. package/lib/unit-test-generator.js +232 -0
  37. package/lib/verification-command-generator.js +66 -0
  38. package/lib/worktree-diagnostics.js +413 -0
  39. package/lib/worktree-facade.js +174 -0
  40. package/lib/worktree-manager.js +636 -0
  41. package/lib/worktree-reconciler.js +429 -0
  42. package/package.json +30 -3
  43. package/skills-templates/external-transition/SKILL.md +34 -3
  44. package/skills-templates/feature-planning/SKILL.md +190 -24
  45. package/skills-templates/production-mode/SKILL.md +127 -9
  46. package/skills-templates/speed-mode/SKILL.md +454 -51
  47. package/skills-templates/stable-mode/SKILL.md +285 -76
  48. package/.claude/PROTECT_SKILLS.md +0 -28
  49. package/.claude/settings.json +0 -24
  50. package/.claude/settings.local.json +0 -16
  51. package/.claude/skills/epic-planning/SKILL.md +0 -297
  52. package/.claude/skills/external-transition/SKILL.md +0 -384
  53. package/.claude/skills/feature-planning/SKILL.md +0 -464
  54. package/.claude/skills/production-mode/SKILL.md +0 -369
  55. package/.claude/skills/speed-mode/SKILL.md +0 -481
  56. package/.claude/skills/stable-mode/SKILL.md +0 -713
  57. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
  58. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
  59. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
  60. package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
  61. package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
  62. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
  63. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
  64. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
  65. package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
  66. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
  67. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
  68. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
  69. package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
  70. package/.devpod/current-work.json +0 -10
  71. package/.devpod/work.db +0 -0
  72. package/.github/workflows/test-safety.yml +0 -85
  73. package/.jettypod/config.json +0 -5
  74. package/.jettypod/current-work.json +0 -10
  75. package/.jettypod/hooks/README.md +0 -77
  76. package/.jettypod/hooks/protect-claude-md.js +0 -338
  77. package/.jettypod/test-work.db +0 -0
  78. package/.jettypod/work.db +0 -0
  79. package/CLAUDE.md +0 -49
  80. package/SPEED-STABLE-AUDIT.md +0 -853
  81. package/SYSTEM-BEHAVIOR.md +0 -2199
  82. package/TEST_SAFETY_AUDIT.md +0 -314
  83. package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
  84. package/cucumber-report.html +0 -45
  85. package/dist/devpod-linux +0 -0
  86. package/dist/devpod-macos +0 -0
  87. package/dist/devpod-win.exe +0 -0
  88. package/docs/features/jettypod-standards-explained.md +0 -543
  89. package/docs/features/standards-inventory.md +0 -257
  90. package/features/auto-generate-production-chores.feature +0 -13
  91. package/features/backlog-command.feature +0 -26
  92. package/features/backlog-filtering-production.feature +0 -10
  93. package/features/claude-md-protection/steps.js +0 -498
  94. package/features/decisions/index.js +0 -490
  95. package/features/decisions/index.test.js +0 -208
  96. package/features/fix-text-wrapping.feature +0 -42
  97. package/features/git-hooks/git-hooks.feature +0 -30
  98. package/features/git-hooks/index.js +0 -93
  99. package/features/git-hooks/index.test.js +0 -137
  100. package/features/git-hooks/post-commit +0 -56
  101. package/features/git-hooks/post-merge +0 -47
  102. package/features/git-hooks/pre-commit +0 -28
  103. package/features/git-hooks/simple-steps.js +0 -53
  104. package/features/git-hooks/simple-test.feature +0 -10
  105. package/features/git-hooks/steps.js +0 -196
  106. package/features/jettypod-update-command.feature +0 -46
  107. package/features/mode-prompts/index.js +0 -95
  108. package/features/mode-prompts/simple-steps.js +0 -44
  109. package/features/mode-prompts/simple-test.feature +0 -9
  110. package/features/mode-prompts/validation.test.js +0 -120
  111. package/features/multiple-claude-instances.feature +0 -121
  112. package/features/production-mode-skill.feature +0 -121
  113. package/features/refactor-mode/steps.js +0 -217
  114. package/features/refactor-mode.feature +0 -49
  115. package/features/simplify-external-transition.feature +0 -166
  116. package/features/skills-update/index.test.js +0 -216
  117. package/features/step_definitions/backlog-command.steps.js +0 -37
  118. package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
  119. package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
  120. package/features/step_definitions/production-mode-skill.steps.js +0 -862
  121. package/features/step_definitions/simplify-external-transition.steps.js +0 -370
  122. package/features/step_definitions/terminal-logo.steps.js +0 -145
  123. package/features/step_definitions/update-command.steps.js +0 -183
  124. package/features/support/hooks.js +0 -9
  125. package/features/terminal-logo/index.js +0 -39
  126. package/features/terminal-logo/terminal-logo.feature +0 -30
  127. package/features/update-command/index.js +0 -181
  128. package/features/update-command/index.test.js +0 -225
  129. package/features/work-commands/bug-workflow-display.feature +0 -22
  130. package/features/work-commands/index.js +0 -498
  131. package/features/work-commands/simple-steps.js +0 -69
  132. package/features/work-commands/stable-tests.feature +0 -57
  133. package/features/work-commands/steps.js +0 -1174
  134. package/features/work-commands/validation.test.js +0 -88
  135. package/features/work-commands/work-commands.feature +0 -13
  136. package/features/work-tracking/discovery-validation.test.js +0 -228
  137. package/features/work-tracking/index.js +0 -1921
  138. package/features/work-tracking/mode-required.feature +0 -112
  139. package/features/work-tracking/phase-tracking.test.js +0 -482
  140. package/features/work-tracking/prototype-tracking.test.js +0 -485
  141. package/features/work-tracking/tree-view.test.js +0 -310
  142. package/features/work-tracking/work-set-mode.feature +0 -71
  143. package/features/work-tracking/work-start-mode.feature +0 -88
  144. package/full-test.txt +0 -0
  145. package/lib/bug-workflow.test.js +0 -177
  146. package/lib/claudemd.test.js +0 -195
  147. package/lib/config.test.js +0 -511
  148. package/lib/constants.test.js +0 -164
  149. package/lib/current-work.test.js +0 -146
  150. package/lib/database-project-config.test.js +0 -111
  151. package/lib/database.test.js +0 -106
  152. package/lib/decisions-generator.test.js +0 -457
  153. package/lib/decisions-helpers.test.js +0 -310
  154. package/lib/git-coordinator.js +0 -167
  155. package/lib/git.test.js +0 -145
  156. package/lib/migrations/002-default-work-item-modes.test.js +0 -351
  157. package/lib/production-chore-generator.test.js +0 -432
  158. package/lib/production-context-detector.test.js +0 -277
  159. package/lib/production-scenario-appender.test.js +0 -235
  160. package/lib/production-scenario-validator.test.js +0 -246
  161. package/lib/production-standards-reader.test.js +0 -270
  162. package/lib/project-state.test.js +0 -92
  163. package/lib/push-queue.js +0 -417
  164. package/lib/queue-processor.js +0 -74
  165. package/lib/test-helpers.js +0 -202
  166. package/lib/test-helpers.test.js +0 -255
  167. package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
  168. package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
  169. package/prototypes/2025-01-11-production-mode-guided.js +0 -217
  170. package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
  171. package/prototypes/2025-01-11-production-standards-example.md +0 -204
  172. package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
  173. package/prototypes/test/index.html +0 -1
  174. package/setup-dist-repo.sh +0 -68
  175. package/test-production-standards-engine.js +0 -130
  176. package/test-results.json +0 -2195
  177. package/test-safety-check.sh +0 -80
  178. package/work-item-tracking-plan.md +0 -199
  179. /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