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,310 +0,0 @@
1
- const decisionsHelpers = require('./decisions-helpers');
2
- const { createTestEnvironment } = require('./test-helpers');
3
- const { getDb, closeDb, resetDb } = require('./database');
4
- const config = require('./config');
5
-
6
- describe('Decisions Helpers', () => {
7
- let testEnv;
8
- let db;
9
-
10
- beforeEach(() => {
11
- resetDb();
12
- testEnv = createTestEnvironment();
13
- process.chdir(testEnv.testDir);
14
- db = getDb();
15
- });
16
-
17
- afterEach(async () => {
18
-
19
-
20
- await closeDb();
21
- testEnv.cleanup();
22
- resetDb();
23
- });
24
-
25
- describe('getProjectDecision', () => {
26
- test('returns null when no project decision exists', () => {
27
- const decision = decisionsHelpers.getProjectDecision();
28
- expect(decision).toBeNull();
29
- });
30
-
31
- test('returns project decision when it exists', () => {
32
- // Mock config with decision
33
- const originalRead = config.read;
34
- config.read = () => ({
35
- project_discovery: {
36
- winner: 'prototypes/test',
37
- rationale: 'Testing approach',
38
- started_date: '2025-10-31T00:00:00.000Z'
39
- }
40
- });
41
-
42
- const decision = decisionsHelpers.getProjectDecision();
43
-
44
- expect(decision).toEqual({
45
- winner: 'prototypes/test',
46
- rationale: 'Testing approach',
47
- started_date: '2025-10-31T00:00:00.000Z'
48
- });
49
-
50
- config.read = originalRead;
51
- });
52
-
53
- test('handles missing rationale and date gracefully', () => {
54
- const originalRead = config.read;
55
- config.read = () => ({
56
- project_discovery: {
57
- winner: 'prototypes/test'
58
- }
59
- });
60
-
61
- const decision = decisionsHelpers.getProjectDecision();
62
-
63
- expect(decision).toEqual({
64
- winner: 'prototypes/test',
65
- rationale: null,
66
- started_date: null
67
- });
68
-
69
- config.read = originalRead;
70
- });
71
-
72
- test('returns null on config read error', () => {
73
- const originalRead = config.read;
74
- config.read = () => {
75
- throw new Error('Config error');
76
- };
77
-
78
- const decision = decisionsHelpers.getProjectDecision();
79
-
80
- expect(decision).toBeNull();
81
-
82
- config.read = originalRead;
83
- });
84
- });
85
-
86
- describe('getDecisionsForEpic', () => {
87
- test('returns empty array when epic has no decisions', async () => {
88
- // Create epic without decisions
89
- await new Promise((resolve) => {
90
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
91
- });
92
-
93
- const decisions = await decisionsHelpers.getDecisionsForEpic(1);
94
-
95
- expect(decisions).toEqual([]);
96
- });
97
-
98
- test('returns decisions for specific epic', async () => {
99
- // Create epic
100
- await new Promise((resolve) => {
101
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
102
- });
103
-
104
- // Add decisions
105
- await new Promise((resolve) => {
106
- db.run(
107
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
108
- [1, 'Architecture', 'REST API', 'Simple and widely understood'],
109
- resolve
110
- );
111
- });
112
-
113
- await new Promise((resolve) => {
114
- db.run(
115
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
116
- [1, 'Database', 'PostgreSQL', 'Robust and feature-rich'],
117
- resolve
118
- );
119
- });
120
-
121
- const decisions = await decisionsHelpers.getDecisionsForEpic(1);
122
-
123
- expect(decisions).toHaveLength(2);
124
- expect(decisions[0].aspect).toBe('Architecture');
125
- expect(decisions[0].decision).toBe('REST API');
126
- expect(decisions[1].aspect).toBe('Database');
127
- expect(decisions[1].decision).toBe('PostgreSQL');
128
- });
129
-
130
- test('only returns decisions for specified epic', async () => {
131
- // Create two epics
132
- await new Promise((resolve) => {
133
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Epic 1'], resolve);
134
- });
135
- await new Promise((resolve) => {
136
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Epic 2'], resolve);
137
- });
138
-
139
- // Add decision to epic 1
140
- await new Promise((resolve) => {
141
- db.run(
142
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
143
- [1, 'Architecture', 'REST API', 'Simple'],
144
- resolve
145
- );
146
- });
147
-
148
- // Add decision to epic 2
149
- await new Promise((resolve) => {
150
- db.run(
151
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
152
- [2, 'Architecture', 'GraphQL', 'Flexible'],
153
- resolve
154
- );
155
- });
156
-
157
- const decisions = await decisionsHelpers.getDecisionsForEpic(1);
158
-
159
- expect(decisions).toHaveLength(1);
160
- expect(decisions[0].decision).toBe('REST API');
161
- });
162
- });
163
-
164
- describe('getAllEpicDecisions', () => {
165
- test('returns empty array when no decisions exist', async () => {
166
- // Ensure db is ready
167
- await new Promise((resolve) => {
168
- db.get('SELECT 1', [], resolve);
169
- });
170
-
171
- const decisions = await decisionsHelpers.getAllEpicDecisions();
172
- expect(decisions).toEqual([]);
173
- });
174
-
175
- test('returns all epic decisions across all epics', async () => {
176
- // Create two epics
177
- await new Promise((resolve) => {
178
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Epic 1'], resolve);
179
- });
180
- await new Promise((resolve) => {
181
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Epic 2'], resolve);
182
- });
183
-
184
- // Add decisions
185
- await new Promise((resolve) => {
186
- db.run(
187
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
188
- [1, 'Architecture', 'REST API', 'Simple'],
189
- resolve
190
- );
191
- });
192
-
193
- await new Promise((resolve) => {
194
- db.run(
195
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
196
- [2, 'Architecture', 'GraphQL', 'Flexible'],
197
- resolve
198
- );
199
- });
200
-
201
- const decisions = await decisionsHelpers.getAllEpicDecisions();
202
-
203
- expect(decisions).toHaveLength(2);
204
- expect(decisions[0].epic_id).toBe(1);
205
- expect(decisions[1].epic_id).toBe(2);
206
- });
207
- });
208
-
209
- describe('getAllDecisions', () => {
210
- test('returns structured object with project and epic decisions', async () => {
211
- // Mock project decision
212
- const originalRead = config.read;
213
- config.read = () => ({
214
- project_discovery: {
215
- winner: 'prototypes/test',
216
- rationale: 'Testing'
217
- }
218
- });
219
-
220
- // Create epic with decision
221
- await new Promise((resolve) => {
222
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
223
- });
224
-
225
- await new Promise((resolve) => {
226
- db.run(
227
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
228
- [1, 'Architecture', 'REST API', 'Simple'],
229
- resolve
230
- );
231
- });
232
-
233
- const allDecisions = await decisionsHelpers.getAllDecisions();
234
-
235
- expect(allDecisions.project).toEqual({
236
- winner: 'prototypes/test',
237
- rationale: 'Testing',
238
- started_date: null
239
- });
240
-
241
- expect(allDecisions.epics).toHaveLength(1);
242
- expect(allDecisions.epics[0].id).toBe(1);
243
- expect(allDecisions.epics[0].title).toBe('Test Epic');
244
- expect(allDecisions.epics[0].decisions).toHaveLength(1);
245
- expect(allDecisions.epics[0].decisions[0].aspect).toBe('Architecture');
246
-
247
- config.read = originalRead;
248
- });
249
-
250
- test('groups decisions by epic', async () => {
251
- // Create epic with multiple decisions
252
- await new Promise((resolve) => {
253
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
254
- });
255
-
256
- await new Promise((resolve) => {
257
- db.run(
258
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
259
- [1, 'Architecture', 'REST API', 'Simple'],
260
- resolve
261
- );
262
- });
263
-
264
- await new Promise((resolve) => {
265
- db.run(
266
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
267
- [1, 'Database', 'PostgreSQL', 'Robust'],
268
- resolve
269
- );
270
- });
271
-
272
- const allDecisions = await decisionsHelpers.getAllDecisions();
273
-
274
- expect(allDecisions.epics).toHaveLength(1);
275
- expect(allDecisions.epics[0].decisions).toHaveLength(2);
276
- });
277
- });
278
-
279
- describe('hasDecisions', () => {
280
- test('returns false when epic has no decisions', async () => {
281
- // Create epic without decisions
282
- await new Promise((resolve) => {
283
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
284
- });
285
-
286
- const result = await decisionsHelpers.hasDecisions(1);
287
-
288
- expect(result).toBe(false);
289
- });
290
-
291
- test('returns true when epic has decisions', async () => {
292
- // Create epic with decision
293
- await new Promise((resolve) => {
294
- db.run('INSERT INTO work_items (type, title) VALUES (?, ?)', ['epic', 'Test Epic'], resolve);
295
- });
296
-
297
- await new Promise((resolve) => {
298
- db.run(
299
- 'INSERT INTO discovery_decisions (work_item_id, aspect, decision, rationale) VALUES (?, ?, ?, ?)',
300
- [1, 'Architecture', 'REST API', 'Simple'],
301
- resolve
302
- );
303
- });
304
-
305
- const result = await decisionsHelpers.hasDecisions(1);
306
-
307
- expect(result).toBe(true);
308
- });
309
- });
310
- });
@@ -1,167 +0,0 @@
1
- const { execSync } = require('child_process');
2
- const { getCurrentBranch } = require('./git');
3
-
4
- /**
5
- * Check if main branch has moved ahead of local branch
6
- * @returns {boolean} True if main has new commits
7
- */
8
- function checkMainAhead() {
9
- try {
10
- // Fetch latest from remote
11
- execSync('git fetch origin main', { stdio: 'pipe' });
12
-
13
- // Get commit count between local and remote main
14
- const result = execSync('git rev-list --count HEAD..origin/main', { encoding: 'utf-8' });
15
- const commitCount = parseInt(result.trim());
16
-
17
- return commitCount > 0;
18
- } catch (err) {
19
- const errorMessage = err.message || err.stderr?.toString() || '';
20
-
21
- // Network error detection
22
- const networkPatterns = [
23
- 'Could not resolve host',
24
- 'Connection refused',
25
- 'Connection timed out',
26
- 'Failed to connect',
27
- 'Network is unreachable',
28
- 'ENOTFOUND',
29
- 'ETIMEDOUT',
30
- 'ECONNREFUSED'
31
- ];
32
-
33
- const isNetworkError = networkPatterns.some(pattern =>
34
- errorMessage.includes(pattern)
35
- );
36
-
37
- if (isNetworkError) {
38
- console.error('❌ Network failure while checking main branch');
39
- console.error(' Cannot connect to remote repository');
40
- console.error('');
41
- console.error('To fix:');
42
- console.error(' 1. Check your internet connection');
43
- console.error(' 2. Verify remote repository is accessible');
44
- console.error(' 3. Try again after network is restored');
45
- console.error('');
46
- console.error('Command: git fetch origin main');
47
- throw new Error('Network failure - cannot check main branch status');
48
- }
49
-
50
- throw new Error(`Failed to check main branch status: ${err.message}`);
51
- }
52
- }
53
-
54
- /**
55
- * Determine if queue should be used for push
56
- * @returns {boolean} True if queue coordination needed
57
- */
58
- function shouldUseQueue() {
59
- return checkMainAhead();
60
- }
61
-
62
- /**
63
- * Push current branch directly to main
64
- */
65
- function pushDirectlyToMain() {
66
- try {
67
- const currentBranch = getCurrentBranch();
68
-
69
- if (currentBranch === 'main') {
70
- // Already on main, just push
71
- execSync('git push origin main', { stdio: 'inherit' });
72
- } else {
73
- // Push current branch to main
74
- execSync(`git push origin ${currentBranch}:main`, { stdio: 'inherit' });
75
- }
76
- } catch (err) {
77
- const errorMessage = err.message || err.stderr?.toString() || '';
78
-
79
- // Push rejection detection
80
- const rejectionPatterns = [
81
- 'rejected',
82
- 'non-fast-forward',
83
- 'Updates were rejected',
84
- 'failed to push',
85
- 'error: failed to push some refs'
86
- ];
87
-
88
- const isRejected = rejectionPatterns.some(pattern =>
89
- errorMessage.includes(pattern)
90
- );
91
-
92
- if (isRejected) {
93
- console.error('❌ Push rejected by remote');
94
- console.error(' Remote has changes that you do not have locally');
95
- console.error('');
96
- console.error('To fix:');
97
- console.error(' 1. Pull latest changes: git pull --rebase');
98
- console.error(' 2. Resolve any conflicts if they occur');
99
- console.error(' 3. Try pushing again');
100
- console.error('');
101
- console.error('Your queue entry will remain for retry.');
102
- throw new Error('Push rejected - remote has newer commits');
103
- }
104
-
105
- throw new Error(`Failed to push to main: ${err.message}`);
106
- }
107
- }
108
-
109
- /**
110
- * Rebase current branch on top of main
111
- */
112
- function rebaseOnMain() {
113
- try {
114
- const currentBranch = getCurrentBranch();
115
-
116
- // Fetch latest main
117
- execSync('git fetch origin main', { stdio: 'pipe' });
118
-
119
- if (currentBranch === 'main') {
120
- // Already on main, just pull with rebase
121
- execSync('git pull --rebase origin main', { stdio: 'inherit' });
122
- } else {
123
- // Rebase current branch on origin/main
124
- execSync('git rebase origin/main', { stdio: 'inherit' });
125
- }
126
- } catch (err) {
127
- const errorMessage = err.message || err.stderr?.toString() || '';
128
- const exitCode = err.status;
129
-
130
- // Rebase conflict detection
131
- const conflictPatterns = [
132
- 'CONFLICT',
133
- 'Merge conflict',
134
- 'conflict',
135
- 'both modified',
136
- 'both added'
137
- ];
138
-
139
- const hasConflict = conflictPatterns.some(pattern =>
140
- errorMessage.includes(pattern)
141
- ) || exitCode === 1;
142
-
143
- if (hasConflict) {
144
- console.error('❌ Rebase conflicts detected');
145
- console.error(' Your changes conflict with changes on main');
146
- console.error('');
147
- console.error('To resolve conflicts:');
148
- console.error(' 1. Run: git status');
149
- console.error(' 2. Edit conflicting files to resolve conflicts');
150
- console.error(' 3. Run: git add <resolved-files>');
151
- console.error(' 4. Run: git rebase --continue');
152
- console.error(' 5. Or abort: git rebase --abort');
153
- console.error('');
154
- console.error('Your queue entry will remain until conflicts are resolved.');
155
- throw new Error('Rebase conflicts - manual resolution required');
156
- }
157
-
158
- throw new Error(`Failed to rebase on main: ${err.message}`);
159
- }
160
- }
161
-
162
- module.exports = {
163
- checkMainAhead,
164
- shouldUseQueue,
165
- pushDirectlyToMain,
166
- rebaseOnMain
167
- };
package/lib/git.test.js DELETED
@@ -1,145 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { execSync } = require('child_process');
4
- const { createTestEnvironment } = require('./test-helpers');
5
- const {
6
- isGitInstalled,
7
- isGitRepo,
8
- createOrCheckoutBranch,
9
- getCurrentBranch,
10
- slugify,
11
- createFeatureBranchName
12
- } = require('./git');
13
-
14
- describe('Git Module', () => {
15
- let testEnv;
16
-
17
- beforeEach(() => {
18
- testEnv = createTestEnvironment();
19
- process.chdir(testEnv.testDir);
20
- });
21
-
22
- afterEach(() => {
23
- testEnv.cleanup();
24
- });
25
-
26
- describe('isGitInstalled()', () => {
27
- test('should return true when git is installed', () => {
28
- expect(isGitInstalled()).toBe(true);
29
- });
30
- });
31
-
32
- describe('isGitRepo()', () => {
33
- test('should return false in non-git directory', () => {
34
- expect(isGitRepo()).toBe(false);
35
- });
36
-
37
- test('should return true in git repository', () => {
38
- execSync('git init', { stdio: 'pipe' });
39
- expect(isGitRepo()).toBe(true);
40
- });
41
- });
42
-
43
- describe('slugify()', () => {
44
- test('should convert text to lowercase with hyphens', () => {
45
- expect(slugify('Hello World')).toBe('hello-world');
46
- });
47
-
48
- test('should remove special characters', () => {
49
- expect(slugify('Fix: Bug #123')).toBe('fix-bug-123');
50
- });
51
-
52
- test('should handle multiple spaces', () => {
53
- expect(slugify('Multiple Spaces')).toBe('multiple-spaces');
54
- });
55
-
56
- test('should throw error for non-string input', () => {
57
- expect(() => slugify(123)).toThrow('slugify requires a string input');
58
- expect(() => slugify(null)).toThrow('slugify requires a string input');
59
- });
60
- });
61
-
62
- describe('createFeatureBranchName()', () => {
63
- test('should create valid branch name', () => {
64
- const branch = createFeatureBranchName(42, 'Add user auth');
65
- expect(branch).toBe('feature/work-42-add-user-auth');
66
- });
67
-
68
- test('should throw error for invalid work item ID', () => {
69
- expect(() => createFeatureBranchName('invalid', 'Title')).toThrow('workItemId must be a positive number');
70
- expect(() => createFeatureBranchName(0, 'Title')).toThrow('workItemId must be a positive number');
71
- expect(() => createFeatureBranchName(-5, 'Title')).toThrow('workItemId must be a positive number');
72
- });
73
-
74
- test('should throw error for invalid title', () => {
75
- expect(() => createFeatureBranchName(1, 123)).toThrow('title must be a non-empty string');
76
- expect(() => createFeatureBranchName(1, '')).toThrow('title must be a non-empty string');
77
- expect(() => createFeatureBranchName(1, ' ')).toThrow('title must be a non-empty string');
78
- });
79
- });
80
-
81
- describe('createOrCheckoutBranch()', () => {
82
- beforeEach(() => {
83
- execSync('git init', { stdio: 'pipe' });
84
- execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
85
- execSync('git config user.name "Test User"', { stdio: 'pipe' });
86
- // Create initial commit
87
- fs.writeFileSync('README.md', '# Test');
88
- execSync('git add .', { stdio: 'pipe' });
89
- execSync('git commit -m "Initial commit"', { stdio: 'pipe' });
90
- });
91
-
92
- test('should create new branch', () => {
93
- const result = createOrCheckoutBranch('feature/test');
94
- expect(result).toBe(true);
95
- expect(getCurrentBranch()).toBe('feature/test');
96
- });
97
-
98
- test('should checkout existing branch', () => {
99
- execSync('git checkout -b feature/existing', { stdio: 'pipe' });
100
- execSync('git checkout main || git checkout master', { stdio: 'pipe' });
101
-
102
- const result = createOrCheckoutBranch('feature/existing');
103
- expect(result).toBe(true);
104
- expect(getCurrentBranch()).toBe('feature/existing');
105
- });
106
-
107
- test('should throw error for invalid branch name', () => {
108
- expect(() => createOrCheckoutBranch('branch name')).toThrow('Branch name cannot contain spaces');
109
- expect(() => createOrCheckoutBranch('-invalid')).toThrow('Branch name cannot start with - or .');
110
- expect(() => createOrCheckoutBranch('invalid~name')).toThrow('Branch name contains invalid characters');
111
- });
112
-
113
- test('should throw error in non-git repository', () => {
114
- testEnv.cleanup();
115
- testEnv = createTestEnvironment();
116
- process.chdir(testEnv.testDir);
117
-
118
- expect(() => createOrCheckoutBranch('feature/test')).toThrow('Not in a git repository');
119
- });
120
- });
121
-
122
- describe('getCurrentBranch()', () => {
123
- beforeEach(() => {
124
- execSync('git init', { stdio: 'pipe' });
125
- execSync('git config user.email "test@test.com"', { stdio: 'pipe' });
126
- execSync('git config user.name "Test User"', { stdio: 'pipe' });
127
- fs.writeFileSync('README.md', '# Test');
128
- execSync('git add .', { stdio: 'pipe' });
129
- execSync('git commit -m "Initial commit"', { stdio: 'pipe' });
130
- });
131
-
132
- test('should return current branch name', () => {
133
- const branch = getCurrentBranch();
134
- expect(['main', 'master']).toContain(branch);
135
- });
136
-
137
- test('should return null in non-git directory', () => {
138
- testEnv.cleanup();
139
- testEnv = createTestEnvironment();
140
- process.chdir(testEnv.testDir);
141
-
142
- expect(getCurrentBranch()).toBe(null);
143
- });
144
- });
145
- });