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
package/lib/push-queue.js DELETED
@@ -1,417 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- /**
5
- * Get path to push queue file
6
- * @returns {string} Absolute path to push-queue.json
7
- */
8
- function getQueueFilePath() {
9
- return path.join(process.cwd(), '.jettypod', 'push-queue.json');
10
- }
11
-
12
- /**
13
- * Ensure .jettypod directory exists
14
- */
15
- function ensureJettypodDir() {
16
- const dir = path.join(process.cwd(), '.jettypod');
17
- if (!fs.existsSync(dir)) {
18
- fs.mkdirSync(dir, { recursive: true });
19
- }
20
- }
21
-
22
- /**
23
- * Initialize empty queue file if it doesn't exist
24
- */
25
- function initializeQueueFile() {
26
- const queuePath = getQueueFilePath();
27
- if (!fs.existsSync(queuePath)) {
28
- ensureJettypodDir();
29
- fs.writeFileSync(queuePath, JSON.stringify({ queue: [] }, null, 2));
30
- }
31
- }
32
-
33
- /**
34
- * Read queue from file
35
- * @returns {Object} Queue object with queue array
36
- */
37
- function readQueue() {
38
- initializeQueueFile();
39
- const queuePath = getQueueFilePath();
40
- try {
41
- const content = fs.readFileSync(queuePath, 'utf-8');
42
- return JSON.parse(content);
43
- } catch (err) {
44
- // Handle corrupted JSON - reinitialize and continue
45
- if (err instanceof SyntaxError) {
46
- console.warn('⚠️ Push queue file corrupted - reinitializing with empty queue');
47
- const emptyQueue = { queue: [] };
48
- try {
49
- fs.writeFileSync(queuePath, JSON.stringify(emptyQueue, null, 2));
50
- return emptyQueue;
51
- } catch (writeErr) {
52
- throw new Error(`Cannot recover corrupted queue: ${writeErr.message}`);
53
- }
54
- }
55
- throw new Error(`Failed to read push queue: ${err.message}`);
56
- }
57
- }
58
-
59
- /**
60
- * Write queue to file
61
- * @param {Object} queueData - Queue object to write
62
- */
63
- function writeQueue(queueData) {
64
- const queuePath = getQueueFilePath();
65
- try {
66
- fs.writeFileSync(queuePath, JSON.stringify(queueData, null, 2));
67
- } catch (err) {
68
- // Handle permission errors
69
- if (err.code === 'EACCES' || err.code === 'EPERM') {
70
- console.error('❌ Insufficient permissions to write push queue');
71
- console.error(` File: ${queuePath}`);
72
- console.error('');
73
- console.error('To fix permissions:');
74
- console.error(` chmod u+w ${path.dirname(queuePath)}`);
75
- console.error(` chmod u+w ${queuePath}`);
76
- throw new Error('Insufficient permissions - see guidance above');
77
- }
78
-
79
- // Handle disk full errors
80
- if (err.code === 'ENOSPC') {
81
- console.error('❌ Disk full - cannot write push queue');
82
- console.error(' Free up disk space and try again');
83
- throw new Error('Disk full - cannot write queue file');
84
- }
85
-
86
- throw new Error(`Failed to write push queue: ${err.message}`);
87
- }
88
- }
89
-
90
- /**
91
- * Get current queue
92
- * @returns {Array} Array of queue entries
93
- */
94
- function getQueue() {
95
- const data = readQueue();
96
-
97
- // Filter out invalid entries
98
- const validEntries = [];
99
- const invalidEntries = [];
100
-
101
- for (const entry of data.queue) {
102
- // Check if entry has required fields
103
- if (!entry.instanceId || typeof entry.instanceId !== 'string' || entry.instanceId.trim() === '') {
104
- invalidEntries.push(entry);
105
- continue;
106
- }
107
-
108
- if (!entry.commitSha || typeof entry.commitSha !== 'string' || entry.commitSha.trim() === '') {
109
- invalidEntries.push(entry);
110
- continue;
111
- }
112
-
113
- validEntries.push(entry);
114
- }
115
-
116
- // Log warning about invalid entries
117
- if (invalidEntries.length > 0) {
118
- console.warn(`⚠️ Found ${invalidEntries.length} invalid queue entries - removing them`);
119
- console.warn(' Invalid entries detected with missing instanceId or commitSha');
120
-
121
- // Write back cleaned queue if we found invalid entries
122
- data.queue = validEntries;
123
- writeQueue(data);
124
- }
125
-
126
- return validEntries;
127
- }
128
-
129
- /**
130
- * Check if queue is empty
131
- * @returns {boolean} True if queue has no entries
132
- */
133
- function isEmpty() {
134
- const queue = getQueue();
135
- return queue.length === 0;
136
- }
137
-
138
- /**
139
- * Add instance to queue
140
- * @param {string} instanceId - Unique identifier for Claude instance
141
- * @param {string} commitSha - SHA of commit to push
142
- * @returns {number} Position in queue (1-indexed)
143
- */
144
- function addToQueue(instanceId, commitSha) {
145
- // Validate instanceId
146
- if (!instanceId || typeof instanceId !== 'string' || instanceId.trim() === '') {
147
- console.error('❌ Invalid instance ID provided to queue');
148
- console.error(' Instance ID must be a non-empty string');
149
- throw new Error('Invalid instance ID - cannot add to queue');
150
- }
151
-
152
- // Validate commitSha
153
- if (!commitSha || typeof commitSha !== 'string' || commitSha.trim() === '') {
154
- console.error('❌ Invalid commit SHA provided to queue');
155
- console.error(' Commit SHA must be a non-empty string');
156
- console.error('');
157
- console.error('To fix:');
158
- console.error(' Ensure you have committed your changes before pushing');
159
- console.error(' Run: git rev-parse HEAD');
160
- throw new Error('Invalid commit SHA - cannot add to queue');
161
- }
162
-
163
- const data = readQueue();
164
- const position = data.queue.length + 1;
165
-
166
- const entry = {
167
- instanceId,
168
- commitSha,
169
- position,
170
- timestamp: Date.now(),
171
- status: 'waiting'
172
- };
173
-
174
- data.queue.push(entry);
175
- writeQueue(data);
176
-
177
- return position;
178
- }
179
-
180
- /**
181
- * Validate queue integrity
182
- * @param {Array} queue - Queue entries to validate
183
- * @returns {Object} Validation result with isValid and issues array
184
- */
185
- function validateQueueIntegrity(queue) {
186
- const issues = [];
187
-
188
- // Check positions are sequential and match array indices
189
- const positionSet = new Set();
190
-
191
- queue.forEach((entry, index) => {
192
- const expectedPosition = index + 1;
193
-
194
- // Check if position matches index
195
- if (entry.position !== expectedPosition) {
196
- issues.push({
197
- type: 'position_mismatch',
198
- instanceId: entry.instanceId,
199
- expected: expectedPosition,
200
- actual: entry.position
201
- });
202
- }
203
-
204
- // Check for duplicate positions
205
- if (positionSet.has(entry.position)) {
206
- issues.push({
207
- type: 'duplicate_position',
208
- position: entry.position,
209
- instanceId: entry.instanceId
210
- });
211
- }
212
- positionSet.add(entry.position);
213
- });
214
-
215
- // Check for gaps in positions (should be 1, 2, 3... without skips)
216
- const positions = queue.map(e => e.position).sort((a, b) => a - b);
217
- for (let i = 0; i < positions.length; i++) {
218
- if (positions[i] !== i + 1) {
219
- issues.push({
220
- type: 'position_gap',
221
- expected: i + 1,
222
- found: positions[i]
223
- });
224
- }
225
- }
226
-
227
- return {
228
- isValid: issues.length === 0,
229
- issues
230
- };
231
- }
232
-
233
- /**
234
- * Detect position changes between old and new queue states
235
- * @param {Array} oldQueue - Previous queue state
236
- * @param {Array} newQueue - New queue state
237
- * @returns {Array} List of position changes
238
- */
239
- function detectPositionChanges(oldQueue, newQueue) {
240
- const changes = [];
241
-
242
- // Build maps for quick lookup
243
- const oldPositions = new Map();
244
- oldQueue.forEach(entry => {
245
- oldPositions.set(entry.instanceId, entry.position);
246
- });
247
-
248
- const newPositions = new Map();
249
- newQueue.forEach(entry => {
250
- newPositions.set(entry.instanceId, entry.position);
251
- });
252
-
253
- // Check each instance in new queue for position changes
254
- newQueue.forEach(entry => {
255
- const oldPosition = oldPositions.get(entry.instanceId);
256
-
257
- if (oldPosition !== undefined && oldPosition !== entry.position) {
258
- changes.push({
259
- instanceId: entry.instanceId,
260
- oldPosition,
261
- newPosition: entry.position,
262
- change: oldPosition - entry.position // Positive means moved up
263
- });
264
- }
265
- });
266
-
267
- return changes;
268
- }
269
-
270
- /**
271
- * Remove instance from queue
272
- * @param {string} instanceId - Instance to remove
273
- */
274
- function removeFromQueue(instanceId) {
275
- const data = readQueue();
276
-
277
- // Capture old queue state for position change detection
278
- const oldQueue = [...data.queue];
279
-
280
- // Remove entry
281
- data.queue = data.queue.filter(entry => entry.instanceId !== instanceId);
282
-
283
- // Recompute positions
284
- data.queue.forEach((entry, index) => {
285
- entry.position = index + 1;
286
- });
287
-
288
- // Detect position changes
289
- const positionChanges = detectPositionChanges(oldQueue, data.queue);
290
-
291
- // Log position changes
292
- if (positionChanges.length > 0) {
293
- console.log(`📊 Queue position changes after removing ${instanceId}:`);
294
- positionChanges.forEach(change => {
295
- console.log(` • Instance ${change.instanceId}: position ${change.oldPosition} → ${change.newPosition} (moved up ${change.change} positions)`);
296
- });
297
- }
298
-
299
- // Validate queue integrity
300
- const validation = validateQueueIntegrity(data.queue);
301
- if (!validation.isValid) {
302
- console.error('⚠️ Queue integrity issues detected:');
303
- validation.issues.forEach(issue => {
304
- console.error(` • ${issue.type}:`, issue);
305
- });
306
- }
307
-
308
- writeQueue(data);
309
- }
310
-
311
- /**
312
- * Get position of instance in queue
313
- * @param {string} instanceId - Instance to check
314
- * @returns {number|null} Position (1-indexed) or null if not in queue
315
- */
316
- function getPosition(instanceId) {
317
- const queue = getQueue();
318
- const entry = queue.find(e => e.instanceId === instanceId);
319
- return entry ? entry.position : null;
320
- }
321
-
322
- /**
323
- * Get number of entries ahead in queue
324
- * @param {string} instanceId - Instance to check
325
- * @returns {number} Number of entries ahead
326
- */
327
- function getEntriesAhead(instanceId) {
328
- const position = getPosition(instanceId);
329
- if (!position) return 0;
330
- return position - 1;
331
- }
332
-
333
- /**
334
- * Clean up stale entries from queue
335
- * @param {number} timeoutMs - Timeout in milliseconds (default 1 hour)
336
- * @returns {number} Number of stale entries removed
337
- */
338
- function cleanupStaleEntries(timeoutMs = 3600000) {
339
- const data = readQueue();
340
- const now = Date.now();
341
- const oldQueue = [...data.queue];
342
-
343
- // Filter out stale entries
344
- const staleEntries = [];
345
- data.queue = data.queue.filter(entry => {
346
- // Handle missing or invalid timestamps
347
- if (!entry.timestamp || typeof entry.timestamp !== 'number') {
348
- staleEntries.push(entry);
349
- return false; // Remove entries with invalid timestamps
350
- }
351
-
352
- const age = now - entry.timestamp;
353
-
354
- // Handle negative age (clock skew)
355
- if (age < 0) {
356
- console.warn(`⚠️ Entry ${entry.instanceId} has future timestamp - keeping but may indicate clock skew`);
357
- return true;
358
- }
359
-
360
- if (age > timeoutMs) {
361
- staleEntries.push(entry);
362
- return false; // Remove stale entry
363
- }
364
- return true; // Keep fresh entry
365
- });
366
-
367
- // If entries were removed, recalculate positions and log
368
- if (staleEntries.length > 0) {
369
- // Recompute positions
370
- data.queue.forEach((entry, index) => {
371
- entry.position = index + 1;
372
- });
373
-
374
- // Detect position changes
375
- const changes = detectPositionChanges(oldQueue, data.queue);
376
-
377
- // Log stale entry removal
378
- console.log(`🧹 Removed ${staleEntries.length} stale queue entries`);
379
- staleEntries.forEach(entry => {
380
- const waitTime = entry.timestamp ? Math.floor((now - entry.timestamp) / 60000) : 'unknown';
381
- console.log(` • Instance ${entry.instanceId} (waiting ${waitTime} minutes)`);
382
- });
383
-
384
- // Log position changes
385
- if (changes.length > 0) {
386
- console.log(`📊 Queue position changes after cleanup:`);
387
- changes.forEach(change => {
388
- console.log(` • Instance ${change.instanceId}: position ${change.oldPosition} → ${change.newPosition} (moved up ${change.change} positions)`);
389
- });
390
- }
391
-
392
- // Validate queue integrity
393
- const validation = validateQueueIntegrity(data.queue);
394
- if (!validation.isValid) {
395
- console.error('⚠️ Queue integrity issues detected after cleanup:');
396
- validation.issues.forEach(issue => {
397
- console.error(` • ${issue.type}:`, issue);
398
- });
399
- }
400
-
401
- writeQueue(data);
402
- }
403
-
404
- return staleEntries.length;
405
- }
406
-
407
- module.exports = {
408
- getQueue,
409
- isEmpty,
410
- addToQueue,
411
- removeFromQueue,
412
- getPosition,
413
- getEntriesAhead,
414
- validateQueueIntegrity,
415
- detectPositionChanges,
416
- cleanupStaleEntries
417
- };
@@ -1,74 +0,0 @@
1
- const { getQueue, removeFromQueue, getPosition, getEntriesAhead } = require('./push-queue');
2
- const { rebaseOnMain, pushDirectlyToMain } = require('./git-coordinator');
3
-
4
- /**
5
- * Check if it's this instance's turn to push
6
- * @param {string} instanceId - Instance to check
7
- * @returns {boolean} True if at position 1
8
- */
9
- function isMyTurn(instanceId) {
10
- const position = getPosition(instanceId);
11
- return position === 1;
12
- }
13
-
14
- /**
15
- * Wait for turn by polling queue
16
- * @param {string} instanceId - Instance to wait for
17
- * @param {number} pollInterval - Milliseconds between checks (default: 5000)
18
- * @returns {Promise<void>} Resolves when it's this instance's turn
19
- */
20
- async function waitForTurn(instanceId, pollInterval = 5000) {
21
- return new Promise((resolve) => {
22
- const checkQueue = () => {
23
- if (isMyTurn(instanceId)) {
24
- resolve();
25
- } else {
26
- const entriesAhead = getEntriesAhead(instanceId);
27
- console.log(`Waiting in queue (${entriesAhead} ahead)...`);
28
- setTimeout(checkQueue, pollInterval);
29
- }
30
- };
31
-
32
- checkQueue();
33
- });
34
- }
35
-
36
- /**
37
- * Monitor current position in queue
38
- * @param {string} instanceId - Instance to monitor
39
- * @returns {number|null} Current position or null if not in queue
40
- */
41
- function monitorPosition(instanceId) {
42
- return getPosition(instanceId);
43
- }
44
-
45
- /**
46
- * Process queue entry: wait for turn, rebase, push, remove entry
47
- * @param {string} instanceId - Instance processing queue
48
- * @returns {Promise<void>}
49
- */
50
- async function processQueue(instanceId) {
51
- const initialPosition = getPosition(instanceId);
52
- console.log(`Added to push queue (position ${initialPosition})`);
53
-
54
- // Wait until it's our turn
55
- await waitForTurn(instanceId);
56
-
57
- // Now at position 1 - rebase and push
58
- console.log('Your turn! Rebasing on main...');
59
- rebaseOnMain();
60
-
61
- console.log('Pushing to main...');
62
- pushDirectlyToMain();
63
-
64
- // Remove from queue
65
- removeFromQueue(instanceId);
66
- console.log('Push complete, removed from queue');
67
- }
68
-
69
- module.exports = {
70
- isMyTurn,
71
- waitForTurn,
72
- monitorPosition,
73
- processQueue
74
- };
@@ -1,202 +0,0 @@
1
- const { execSync } = require('child_process');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const os = require('os');
5
-
6
- /**
7
- * Creates an isolated test directory for testing
8
- * @returns {Object} Test context with testDir and originalCwd
9
- * @throws {Error} If temporary directory cannot be created
10
- */
11
- function createTestEnvironment() {
12
- // Get original cwd, handling case where current directory was deleted
13
- let originalCwd;
14
- try {
15
- originalCwd = process.cwd();
16
- } catch (err) {
17
- // If current directory doesn't exist, use the project root
18
- // This can happen when previous tests deleted their test directory
19
- originalCwd = path.resolve(__dirname, '..');
20
- try {
21
- process.chdir(originalCwd);
22
- } catch (chdirErr) {
23
- throw new Error(`Failed to restore to valid directory: ${chdirErr.message}`);
24
- }
25
- }
26
-
27
- let testDir;
28
- try {
29
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jettypod-test-'));
30
- } catch (err) {
31
- throw new Error(`Failed to create test directory: ${err.message}`);
32
- }
33
-
34
- return {
35
- originalCwd,
36
- testDir,
37
- cleanup: () => {
38
- try {
39
- process.chdir(originalCwd);
40
- } catch (err) {
41
- // Directory may have been deleted, try project root
42
- try {
43
- process.chdir(path.resolve(__dirname, '..'));
44
- } catch (chdirErr) {
45
- // Ignore if we can't change directories
46
- }
47
- }
48
- if (fs.existsSync(testDir)) {
49
- try {
50
- fs.rmSync(testDir, { recursive: true, force: true });
51
- } catch (err) {
52
- console.warn(`Warning: Failed to cleanup test directory: ${err.message}`);
53
- }
54
- }
55
- }
56
- };
57
- }
58
-
59
- /**
60
- * Copies directory recursively, excluding test files
61
- * @param {string} src - Source directory
62
- * @param {string} dest - Destination directory
63
- * @throws {Error} If src doesn't exist, is not a directory, or copy fails
64
- */
65
- function copyDirRecursive(src, dest) {
66
- if (!src || typeof src !== 'string') {
67
- throw new Error('Source path must be a non-empty string');
68
- }
69
- if (!dest || typeof dest !== 'string') {
70
- throw new Error('Destination path must be a non-empty string');
71
- }
72
-
73
- if (!fs.existsSync(src)) {
74
- throw new Error(`Source directory does not exist: ${src}`);
75
- }
76
-
77
- const srcStat = fs.statSync(src);
78
- if (!srcStat.isDirectory()) {
79
- throw new Error(`Source is not a directory: ${src}`);
80
- }
81
-
82
- try {
83
- if (!fs.existsSync(dest)) {
84
- fs.mkdirSync(dest, { recursive: true });
85
- }
86
-
87
- fs.readdirSync(src).forEach(item => {
88
- const srcPath = path.join(src, item);
89
- const destPath = path.join(dest, item);
90
-
91
- if (fs.statSync(srcPath).isDirectory()) {
92
- copyDirRecursive(srcPath, destPath);
93
- } else {
94
- // Skip test files when copying
95
- if (!item.endsWith('.test.js')) {
96
- fs.copyFileSync(srcPath, destPath);
97
- }
98
- }
99
- });
100
- } catch (err) {
101
- throw new Error(`Failed to copy directory from ${src} to ${dest}: ${err.message}`);
102
- }
103
- }
104
-
105
- /**
106
- * Runs a JettyPod command in test environment
107
- * @param {string} cmd - Command to run (e.g., 'jettypod init')
108
- * @param {Object} context - Test context with originalCwd
109
- * @returns {string} Command output
110
- * @throws {Error} If cmd is invalid or context is missing
111
- */
112
- function runCommand(cmd, context) {
113
- if (!cmd || typeof cmd !== 'string') {
114
- throw new Error('Command must be a non-empty string');
115
- }
116
- if (!context || typeof context !== 'object') {
117
- throw new Error('Context must be an object');
118
- }
119
-
120
- try {
121
- const productionJettypodPath = context.originalCwd ?
122
- path.join(context.originalCwd, 'jettypod.js') :
123
- path.join(process.cwd(), '..', '..', 'jettypod.js');
124
-
125
- if (!fs.existsSync(productionJettypodPath)) {
126
- throw new Error(`JettyPod script not found at: ${productionJettypodPath}`);
127
- }
128
-
129
- const fullCmd = cmd.replace(/^jettypod/, `node ${productionJettypodPath}`);
130
- return execSync(fullCmd, {
131
- encoding: 'utf-8',
132
- stdio: 'pipe'
133
- });
134
- } catch (error) {
135
- return error.stdout || error.message;
136
- }
137
- }
138
-
139
- /**
140
- * Reads config.json from .jettypod directory
141
- * @returns {Object|null} Config object or null if not found
142
- * @throws {Error} If JSON is corrupted or file cannot be read
143
- */
144
- function readConfig() {
145
- const configPath = '.jettypod/config.json';
146
-
147
- if (!fs.existsSync(configPath)) {
148
- return null;
149
- }
150
-
151
- try {
152
- const content = fs.readFileSync(configPath, 'utf-8');
153
- return JSON.parse(content);
154
- } catch (err) {
155
- if (err instanceof SyntaxError) {
156
- throw new Error(`Config file contains invalid JSON: ${err.message}`);
157
- }
158
- throw new Error(`Failed to read config file: ${err.message}`);
159
- }
160
- }
161
-
162
- /**
163
- * Reads CLAUDE.md from current directory
164
- * @returns {string} CLAUDE.md content or empty string
165
- * @throws {Error} If file exists but cannot be read
166
- */
167
- function readClaude() {
168
- if (!fs.existsSync('CLAUDE.md')) {
169
- return '';
170
- }
171
-
172
- try {
173
- return fs.readFileSync('CLAUDE.md', 'utf-8');
174
- } catch (err) {
175
- throw new Error(`Failed to read CLAUDE.md: ${err.message}`);
176
- }
177
- }
178
-
179
- /**
180
- * Initializes tracking for created files/directories
181
- * @param {Object} context - Test context to initialize
182
- * @throws {Error} If context is invalid
183
- */
184
- function initializeTracking(context) {
185
- if (!context || typeof context !== 'object') {
186
- throw new Error('Context must be an object');
187
- }
188
-
189
- context.createdFiles = [];
190
- context.createdDirs = [];
191
- context.modifiedFiles = new Map();
192
- context.createdTestDirs = [];
193
- }
194
-
195
- module.exports = {
196
- createTestEnvironment,
197
- copyDirRecursive,
198
- runCommand,
199
- readConfig,
200
- readClaude,
201
- initializeTracking
202
- };