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,164 +0,0 @@
1
- const {
2
- WORK_TYPES,
3
- WORK_STATUSES,
4
- TYPE_EMOJIS,
5
- STATUS_EMOJIS,
6
- VALID_STATUSES,
7
- VALID_TYPES,
8
- isValidType,
9
- isValidStatus,
10
- getTypeEmoji,
11
- getStatusEmoji
12
- } = require('./constants');
13
-
14
- describe('Constants Module', () => {
15
- describe('WORK_TYPES', () => {
16
- test('should have all required types', () => {
17
- expect(WORK_TYPES.EPIC).toBe('epic');
18
- expect(WORK_TYPES.FEATURE).toBe('feature');
19
- expect(WORK_TYPES.BUG).toBe('bug');
20
- expect(WORK_TYPES.CHORE).toBe('chore');
21
- });
22
-
23
- test('should be frozen (immutable)', () => {
24
- expect(Object.isFrozen(WORK_TYPES)).toBe(true);
25
- });
26
- });
27
-
28
- describe('WORK_STATUSES', () => {
29
- test('should have all required statuses', () => {
30
- expect(WORK_STATUSES.BACKLOG).toBe('backlog');
31
- expect(WORK_STATUSES.TODO).toBe('todo');
32
- expect(WORK_STATUSES.IN_PROGRESS).toBe('in_progress');
33
- expect(WORK_STATUSES.BLOCKED).toBe('blocked');
34
- expect(WORK_STATUSES.DONE).toBe('done');
35
- expect(WORK_STATUSES.CANCELLED).toBe('cancelled');
36
- });
37
-
38
- test('should be frozen (immutable)', () => {
39
- expect(Object.isFrozen(WORK_STATUSES)).toBe(true);
40
- });
41
- });
42
-
43
- describe('TYPE_EMOJIS', () => {
44
- test('should have emojis for all types', () => {
45
- expect(TYPE_EMOJIS[WORK_TYPES.EPIC]).toBe('🎯');
46
- expect(TYPE_EMOJIS[WORK_TYPES.FEATURE]).toBe('✨');
47
- expect(TYPE_EMOJIS[WORK_TYPES.BUG]).toBe('🐛');
48
- expect(TYPE_EMOJIS[WORK_TYPES.CHORE]).toBe('🔧');
49
- });
50
-
51
- test('should be frozen (immutable)', () => {
52
- expect(Object.isFrozen(TYPE_EMOJIS)).toBe(true);
53
- });
54
- });
55
-
56
- describe('STATUS_EMOJIS', () => {
57
- test('should have emojis for all statuses', () => {
58
- expect(STATUS_EMOJIS[WORK_STATUSES.BACKLOG]).toBe('⏳');
59
- expect(STATUS_EMOJIS[WORK_STATUSES.TODO]).toBe('📋');
60
- expect(STATUS_EMOJIS[WORK_STATUSES.IN_PROGRESS]).toBe('🔄');
61
- expect(STATUS_EMOJIS[WORK_STATUSES.DONE]).toBe('✅');
62
- expect(STATUS_EMOJIS[WORK_STATUSES.CANCELLED]).toBe('❌');
63
- });
64
-
65
- test('should be frozen (immutable)', () => {
66
- expect(Object.isFrozen(STATUS_EMOJIS)).toBe(true);
67
- });
68
- });
69
-
70
- describe('VALID_TYPES', () => {
71
- test('should contain all type values', () => {
72
- expect(VALID_TYPES).toHaveLength(4);
73
- expect(VALID_TYPES).toContain('epic');
74
- expect(VALID_TYPES).toContain('feature');
75
- expect(VALID_TYPES).toContain('bug');
76
- expect(VALID_TYPES).toContain('chore');
77
- });
78
-
79
- test('should be frozen (immutable)', () => {
80
- expect(Object.isFrozen(VALID_TYPES)).toBe(true);
81
- });
82
- });
83
-
84
- describe('VALID_STATUSES', () => {
85
- test('should contain all status values', () => {
86
- expect(VALID_STATUSES).toHaveLength(6);
87
- expect(VALID_STATUSES).toContain('backlog');
88
- expect(VALID_STATUSES).toContain('todo');
89
- expect(VALID_STATUSES).toContain('in_progress');
90
- expect(VALID_STATUSES).toContain('blocked');
91
- expect(VALID_STATUSES).toContain('done');
92
- expect(VALID_STATUSES).toContain('cancelled');
93
- });
94
-
95
- test('should be frozen (immutable)', () => {
96
- expect(Object.isFrozen(VALID_STATUSES)).toBe(true);
97
- });
98
- });
99
-
100
- describe('isValidType()', () => {
101
- test('should return true for valid types', () => {
102
- expect(isValidType('epic')).toBe(true);
103
- expect(isValidType('feature')).toBe(true);
104
- expect(isValidType('bug')).toBe(true);
105
- expect(isValidType('chore')).toBe(true);
106
- });
107
-
108
- test('should return false for invalid types', () => {
109
- expect(isValidType('invalid')).toBe(false);
110
- expect(isValidType('')).toBe(false);
111
- expect(isValidType(null)).toBe(false);
112
- expect(isValidType(undefined)).toBe(false);
113
- });
114
- });
115
-
116
- describe('isValidStatus()', () => {
117
- test('should return true for valid statuses', () => {
118
- expect(isValidStatus('backlog')).toBe(true);
119
- expect(isValidStatus('todo')).toBe(true);
120
- expect(isValidStatus('in_progress')).toBe(true);
121
- expect(isValidStatus('blocked')).toBe(true);
122
- expect(isValidStatus('done')).toBe(true);
123
- expect(isValidStatus('cancelled')).toBe(true);
124
- });
125
-
126
- test('should return false for invalid statuses', () => {
127
- expect(isValidStatus('invalid')).toBe(false);
128
- expect(isValidStatus('')).toBe(false);
129
- expect(isValidStatus(null)).toBe(false);
130
- expect(isValidStatus(undefined)).toBe(false);
131
- });
132
- });
133
-
134
- describe('getTypeEmoji()', () => {
135
- test('should return correct emoji for valid types', () => {
136
- expect(getTypeEmoji('epic')).toBe('🎯');
137
- expect(getTypeEmoji('feature')).toBe('✨');
138
- expect(getTypeEmoji('bug')).toBe('🐛');
139
- expect(getTypeEmoji('chore')).toBe('🔧');
140
- });
141
-
142
- test('should return default emoji for invalid types', () => {
143
- expect(getTypeEmoji('invalid')).toBe('📋');
144
- expect(getTypeEmoji(null)).toBe('📋');
145
- expect(getTypeEmoji(undefined)).toBe('📋');
146
- });
147
- });
148
-
149
- describe('getStatusEmoji()', () => {
150
- test('should return correct emoji for valid statuses', () => {
151
- expect(getStatusEmoji('backlog')).toBe('⏳');
152
- expect(getStatusEmoji('todo')).toBe('📋');
153
- expect(getStatusEmoji('in_progress')).toBe('🔄');
154
- expect(getStatusEmoji('done')).toBe('✅');
155
- expect(getStatusEmoji('cancelled')).toBe('❌');
156
- });
157
-
158
- test('should return default emoji for invalid statuses', () => {
159
- expect(getStatusEmoji('invalid')).toBe('❓');
160
- expect(getStatusEmoji(null)).toBe('❓');
161
- expect(getStatusEmoji(undefined)).toBe('❓');
162
- });
163
- });
164
- });
@@ -1,146 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { createTestEnvironment } = require('./test-helpers');
4
- const {
5
- getCurrentWork,
6
- setCurrentWork,
7
- clearCurrentWork,
8
- getCurrentWorkPath
9
- } = require('./current-work');
10
-
11
- describe('Current Work Module', () => {
12
- let testEnv;
13
-
14
- beforeEach(() => {
15
- testEnv = createTestEnvironment();
16
- process.chdir(testEnv.testDir);
17
- });
18
-
19
- afterEach(() => {
20
- testEnv.cleanup();
21
- });
22
-
23
- const validWorkItem = {
24
- id: 1,
25
- title: 'Test Feature',
26
- type: 'feature',
27
- status: 'in_progress',
28
- parent_id: null,
29
- parent_title: null,
30
- epic_id: null,
31
- epic_title: null
32
- };
33
-
34
- describe('getCurrentWorkPath()', () => {
35
- test('should return path to current-work.json', () => {
36
- const workPath = getCurrentWorkPath();
37
- expect(workPath).toContain('.jettypod');
38
- expect(workPath).toContain('current-work.json');
39
- });
40
- });
41
-
42
- describe('getCurrentWork()', () => {
43
- test('should return null if file does not exist', () => {
44
- expect(getCurrentWork()).toBeNull();
45
- });
46
-
47
- test('should return work item if file exists', () => {
48
- setCurrentWork(validWorkItem);
49
- const result = getCurrentWork();
50
- expect(result).toEqual(validWorkItem);
51
- });
52
-
53
- test('should return null for corrupted JSON', () => {
54
- const jettypodDir = path.join(testEnv.testDir, '.jettypod');
55
- fs.mkdirSync(jettypodDir, { recursive: true });
56
- fs.writeFileSync(getCurrentWorkPath(), 'not valid json');
57
-
58
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
59
- const result = getCurrentWork();
60
-
61
- expect(result).toBeNull();
62
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Corrupted current work file'));
63
- consoleSpy.mockRestore();
64
- });
65
-
66
- test('should return null for work item missing required fields', () => {
67
- const jettypodDir = path.join(testEnv.testDir, '.jettypod');
68
- fs.mkdirSync(jettypodDir, { recursive: true });
69
- fs.writeFileSync(getCurrentWorkPath(), JSON.stringify({ id: 1 })); // Missing title, type, status
70
-
71
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
72
- const result = getCurrentWork();
73
-
74
- expect(result).toBeNull();
75
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('missing required fields'));
76
- consoleSpy.mockRestore();
77
- });
78
- });
79
-
80
- describe('setCurrentWork()', () => {
81
- test('should create .jettypod directory if it does not exist', () => {
82
- setCurrentWork(validWorkItem);
83
- expect(fs.existsSync(path.join(testEnv.testDir, '.jettypod'))).toBe(true);
84
- });
85
-
86
- test('should write work item to file', () => {
87
- setCurrentWork(validWorkItem);
88
-
89
- const content = fs.readFileSync(getCurrentWorkPath(), 'utf-8');
90
- expect(JSON.parse(content)).toEqual(validWorkItem);
91
- });
92
-
93
- test('should throw error for invalid work item - not an object', () => {
94
- expect(() => setCurrentWork(null)).toThrow('Work item must be an object');
95
- expect(() => setCurrentWork('string')).toThrow('Work item must be an object');
96
- });
97
-
98
- test('should throw error for missing id', () => {
99
- const invalid = { ...validWorkItem, id: null };
100
- expect(() => setCurrentWork(invalid)).toThrow('Work item must have a numeric id');
101
- });
102
-
103
- test('should throw error for non-numeric id', () => {
104
- const invalid = { ...validWorkItem, id: 'not-a-number' };
105
- expect(() => setCurrentWork(invalid)).toThrow('Work item must have a numeric id');
106
- });
107
-
108
- test('should throw error for missing title', () => {
109
- const invalid = { ...validWorkItem, title: null };
110
- expect(() => setCurrentWork(invalid)).toThrow('Work item must have a string title');
111
- });
112
-
113
- test('should throw error for missing type', () => {
114
- const invalid = { ...validWorkItem, type: null };
115
- expect(() => setCurrentWork(invalid)).toThrow('Work item must have a string type');
116
- });
117
-
118
- test('should throw error for missing status', () => {
119
- const invalid = { ...validWorkItem, status: null };
120
- expect(() => setCurrentWork(invalid)).toThrow('Work item must have a string status');
121
- });
122
- });
123
-
124
- describe('clearCurrentWork()', () => {
125
- test('should do nothing if file does not exist', () => {
126
- expect(() => clearCurrentWork()).not.toThrow();
127
- });
128
-
129
- test('should delete current work file', () => {
130
- setCurrentWork(validWorkItem);
131
- expect(fs.existsSync(getCurrentWorkPath())).toBe(true);
132
-
133
- clearCurrentWork();
134
- expect(fs.existsSync(getCurrentWorkPath())).toBe(false);
135
- });
136
-
137
- test('should allow setting work again after clearing', () => {
138
- setCurrentWork(validWorkItem);
139
- clearCurrentWork();
140
- setCurrentWork(validWorkItem);
141
-
142
- const result = getCurrentWork();
143
- expect(result).toEqual(validWorkItem);
144
- });
145
- });
146
- });
@@ -1,111 +0,0 @@
1
- const { getDb, closeDb } = require('./database');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- describe('Project Config Table', () => {
6
- let db;
7
- const originalCwd = process.cwd();
8
- const testDir = path.join('/tmp', 'jettypod-project-config-test-' + Date.now());
9
-
10
- beforeAll(() => {
11
- // Use test database
12
- // SAFETY: Only delete if testDir is in /tmp
13
- if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
14
- fs.rmSync(testDir, { recursive: true });
15
- }
16
- fs.mkdirSync(testDir, { recursive: true });
17
- process.chdir(testDir);
18
- db = getDb();
19
- });
20
-
21
- afterEach((done) => {
22
- // Clean up data between tests
23
- db.run("DELETE FROM project_config", () => {
24
- done();
25
- });
26
- });
27
-
28
- afterAll(() => {
29
- closeDb();
30
- try {
31
- process.chdir(originalCwd);
32
- } catch (err) {
33
- // Directory may have been deleted by another test, ignore
34
- }
35
- // SAFETY: Only delete if testDir is in /tmp
36
- if (fs.existsSync(testDir) && testDir.startsWith('/tmp/')) {
37
- fs.rmSync(testDir, { recursive: true });
38
- }
39
- });
40
-
41
- describe('Schema', () => {
42
- it('should create project_config table', (done) => {
43
- db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='project_config'", (err, row) => {
44
- expect(err).toBeNull();
45
- expect(row).toBeDefined();
46
- expect(row.name).toBe('project_config');
47
- done();
48
- });
49
- });
50
-
51
- it('should have correct columns', (done) => {
52
- db.all("PRAGMA table_info(project_config)", (err, columns) => {
53
- expect(err).toBeNull();
54
- const columnNames = columns.map(c => c.name);
55
- expect(columnNames).toContain('id');
56
- expect(columnNames).toContain('project_state');
57
- expect(columnNames).toContain('updated_at');
58
- done();
59
- });
60
- });
61
-
62
- it('should default project_state to "internal"', (done) => {
63
- db.run("INSERT INTO project_config (id) VALUES (1)", (err) => {
64
- expect(err).toBeNull();
65
- db.get("SELECT project_state FROM project_config WHERE id = 1", (err, row) => {
66
- expect(err).toBeNull();
67
- expect(row.project_state).toBe('internal');
68
- done();
69
- });
70
- });
71
- });
72
-
73
- it('should enforce singleton constraint', (done) => {
74
- db.run("INSERT INTO project_config (id, project_state) VALUES (1, 'internal')", (err) => {
75
- expect(err).toBeNull();
76
- db.run("INSERT INTO project_config (id, project_state) VALUES (2, 'external')", (err) => {
77
- expect(err).toBeDefined(); // Should fail due to CHECK constraint
78
- done();
79
- });
80
- });
81
- });
82
- });
83
-
84
- describe('Operations', () => {
85
- it('should insert and read project state', (done) => {
86
- db.run("INSERT INTO project_config (id, project_state) VALUES (1, 'external')", (err) => {
87
- expect(err).toBeNull();
88
- db.get("SELECT * FROM project_config WHERE id = 1", (err, row) => {
89
- expect(err).toBeNull();
90
- expect(row.project_state).toBe('external');
91
- expect(row.id).toBe(1);
92
- done();
93
- });
94
- });
95
- });
96
-
97
- it('should update project state', (done) => {
98
- db.run("INSERT INTO project_config (id, project_state) VALUES (1, 'internal')", (err) => {
99
- expect(err).toBeNull();
100
- db.run("UPDATE project_config SET project_state = 'external' WHERE id = 1", (err) => {
101
- expect(err).toBeNull();
102
- db.get("SELECT project_state FROM project_config WHERE id = 1", (err, row) => {
103
- expect(err).toBeNull();
104
- expect(row.project_state).toBe('external');
105
- done();
106
- });
107
- });
108
- });
109
- });
110
- });
111
- });
@@ -1,106 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { createTestEnvironment } = require('./test-helpers');
4
- const { getDb, closeDb, resetDb, getDbPath, getJettypodDir } = require('./database');
5
-
6
- describe('Database Module', () => {
7
- let testEnv;
8
-
9
- beforeEach(() => {
10
- resetDb(); // Clear singleton before test
11
- testEnv = createTestEnvironment();
12
- process.chdir(testEnv.testDir);
13
- });
14
-
15
- afterEach(async () => {
16
-
17
-
18
- await closeDb();
19
- testEnv.cleanup();
20
- resetDb(); // Clear singleton after test
21
- });
22
-
23
- describe('getDb()', () => {
24
- test('should create .jettypod directory if it does not exist', () => {
25
- const db = getDb();
26
- expect(fs.existsSync(getJettypodDir())).toBe(true);
27
- expect(db).toBeTruthy();
28
- });
29
-
30
- test('should create database file', (done) => {
31
- const db = getDb();
32
- // Wait for database to be fully initialized
33
- db.get("SELECT 1", (err) => {
34
- expect(err).toBeNull();
35
- expect(fs.existsSync(getDbPath())).toBe(true);
36
- expect(db).toBeTruthy();
37
- done();
38
- });
39
- });
40
-
41
- test('should create work_items table', (done) => {
42
- const db = getDb();
43
- db.get("SELECT name FROM sqlite_master WHERE type='table' AND name='work_items'", (err, row) => {
44
- expect(err).toBeNull();
45
- expect(row).toBeTruthy();
46
- expect(row.name).toBe('work_items');
47
- done();
48
- });
49
- });
50
-
51
- test('should return same database instance (singleton)', () => {
52
- const db1 = getDb();
53
- const db2 = getDb();
54
- expect(db1).toBe(db2);
55
- });
56
-
57
- test('should have correct schema columns', (done) => {
58
- const db = getDb();
59
- db.all("PRAGMA table_info(work_items)", (err, columns) => {
60
- expect(err).toBeNull();
61
-
62
- const columnNames = columns.map(col => col.name);
63
- expect(columnNames).toContain('id');
64
- expect(columnNames).toContain('type');
65
- expect(columnNames).toContain('title');
66
- expect(columnNames).toContain('description');
67
- expect(columnNames).toContain('status');
68
- expect(columnNames).toContain('parent_id');
69
- expect(columnNames).toContain('epic_id');
70
- expect(columnNames).toContain('branch_name');
71
- expect(columnNames).toContain('file_paths');
72
- expect(columnNames).toContain('commit_sha');
73
- expect(columnNames).toContain('mode');
74
- expect(columnNames).toContain('current');
75
- expect(columnNames).toContain('created_at');
76
- done();
77
- });
78
- });
79
- });
80
-
81
- describe('closeDb()', () => {
82
- test('should close database connection', async () => {
83
- const db = getDb();
84
- expect(db).toBeTruthy();
85
-
86
- await closeDb();
87
-
88
- // After close, getDb should create a new instance
89
- const db2 = getDb();
90
- expect(db2).toBeTruthy();
91
- expect(db2).not.toBe(db);
92
- });
93
-
94
- test('should not throw when called without active connection', async () => {
95
- await expect(closeDb()).resolves.not.toThrow();
96
- });
97
- });
98
-
99
- describe('getDbPath() and getJettypodDir()', () => {
100
- test('should return correct paths', () => {
101
- expect(getDbPath()).toContain('.jettypod');
102
- expect(getDbPath()).toContain('work.db');
103
- expect(getJettypodDir()).toContain('.jettypod');
104
- });
105
- });
106
- });