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,195 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { createTestEnvironment } = require('./test-helpers');
4
- const { updateCurrentWork, getClaudeMdPath } = require('./claudemd');
5
- const config = require('./config');
6
-
7
- describe('CLAUDE.md Updater Module', () => {
8
- let testEnv;
9
-
10
- beforeEach(() => {
11
- testEnv = createTestEnvironment();
12
- process.chdir(testEnv.testDir);
13
-
14
- // Initialize config
15
- const jettypodDir = path.join(testEnv.testDir, '.jettypod');
16
- fs.mkdirSync(jettypodDir);
17
- config.write({ mode: 'speed', stage: 'starting' });
18
- });
19
-
20
- afterEach(() => {
21
- testEnv.cleanup();
22
- });
23
-
24
- const validWorkItem = {
25
- id: 1,
26
- title: 'Test Feature',
27
- type: 'feature',
28
- status: 'in_progress',
29
- epic_id: 10,
30
- epic_title: 'Test Epic'
31
- };
32
-
33
- describe('getClaudeMdPath()', () => {
34
- test('should return path to CLAUDE.md in current directory', () => {
35
- const claudePath = getClaudeMdPath();
36
- expect(claudePath).toContain('CLAUDE.md');
37
- expect(claudePath).toBe(path.join(process.cwd(), 'CLAUDE.md'));
38
- });
39
- });
40
-
41
- describe('updateCurrentWork()', () => {
42
- const basicClaudeContent = `<claude_context project="test">
43
- <project_summary>Test project</project_summary>
44
- </claude_context>`;
45
-
46
- test('should do nothing if CLAUDE.md does not exist', () => {
47
- expect(() => updateCurrentWork(validWorkItem)).not.toThrow();
48
- });
49
-
50
- test('should add current_work section if it does not exist', () => {
51
- fs.writeFileSync('CLAUDE.md', basicClaudeContent);
52
-
53
- updateCurrentWork(validWorkItem);
54
-
55
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
56
- expect(content).toContain('<current_work>');
57
- expect(content).toContain('Working on: [#1] Test Feature (feature)');
58
- expect(content).toContain('Epic: [#10] Test Epic');
59
- expect(content).toContain('Mode: speed');
60
- expect(content).toContain('Status: in_progress');
61
- expect(content).toContain('</current_work>');
62
- });
63
-
64
- test('should replace existing current_work section', () => {
65
- const existingContent = `<claude_context project="test">
66
- <current_work>
67
- Working on: [#999] Old Feature (bug)
68
- Status: done
69
- </current_work>
70
- <project_summary>Test project</project_summary>
71
- </claude_context>`;
72
-
73
- fs.writeFileSync('CLAUDE.md', existingContent);
74
-
75
- updateCurrentWork(validWorkItem);
76
-
77
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
78
- expect(content).not.toContain('[#999] Old Feature');
79
- expect(content).toContain('[#1] Test Feature');
80
- expect(content).toContain('Status: in_progress');
81
- });
82
-
83
- test('should not include epic if work item is the epic itself', () => {
84
- const epicWorkItem = {
85
- id: 10,
86
- title: 'Test Epic',
87
- type: 'epic',
88
- status: 'in_progress',
89
- epic_id: 10,
90
- epic_title: 'Test Epic'
91
- };
92
-
93
- fs.writeFileSync('CLAUDE.md', basicClaudeContent);
94
-
95
- updateCurrentWork(epicWorkItem);
96
-
97
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
98
- expect(content).not.toContain('Epic: [#10]');
99
- expect(content).toContain('[#10] Test Epic (epic)');
100
- });
101
-
102
- test('should not include epic if epic_title is missing', () => {
103
- const workItemNoEpic = {
104
- id: 1,
105
- title: 'Test Feature',
106
- type: 'feature',
107
- status: 'in_progress'
108
- };
109
-
110
- fs.writeFileSync('CLAUDE.md', basicClaudeContent);
111
-
112
- updateCurrentWork(workItemNoEpic);
113
-
114
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
115
- expect(content).not.toContain('Epic:');
116
- });
117
-
118
- test('should use mode from config', () => {
119
- config.write({ mode: 'stable', stage: 'building' });
120
- fs.writeFileSync('CLAUDE.md', basicClaudeContent);
121
-
122
- updateCurrentWork(validWorkItem);
123
-
124
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
125
- expect(content).toContain('Mode: stable');
126
- });
127
-
128
- test('should use mode from config even if malformed', () => {
129
- // Config module caches config, so we just verify it uses whatever mode is set
130
- const currentConfig = config.read();
131
- fs.writeFileSync('CLAUDE.md', basicClaudeContent);
132
-
133
- updateCurrentWork(validWorkItem);
134
-
135
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
136
- expect(content).toContain(`Mode: ${currentConfig.mode || 'speed'}`);
137
- });
138
-
139
- test('should throw error for invalid work item - not an object', () => {
140
- expect(() => updateCurrentWork(null)).toThrow('Current work must be an object');
141
- expect(() => updateCurrentWork('string')).toThrow('Current work must be an object');
142
- });
143
-
144
- test('should throw error for missing id', () => {
145
- const invalid = { ...validWorkItem, id: null };
146
- expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a numeric id');
147
- });
148
-
149
- test('should throw error for non-numeric id', () => {
150
- const invalid = { ...validWorkItem, id: 'not-a-number' };
151
- expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a numeric id');
152
- });
153
-
154
- test('should throw error for missing title', () => {
155
- const invalid = { ...validWorkItem, title: null };
156
- expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a string title');
157
- });
158
-
159
- test('should throw error for missing type', () => {
160
- const invalid = { ...validWorkItem, type: null };
161
- expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a string type');
162
- });
163
-
164
- test('should throw error for missing status', () => {
165
- const invalid = { ...validWorkItem, status: null };
166
- expect(() => updateCurrentWork(invalid)).toThrow('Current work must have a string status');
167
- });
168
-
169
- test('should throw error if CLAUDE.md does not contain claude_context tag', () => {
170
- fs.writeFileSync('CLAUDE.md', 'Just plain text, no tags');
171
-
172
- expect(() => updateCurrentWork(validWorkItem)).toThrow('CLAUDE.md does not contain <claude_context> tag');
173
- });
174
-
175
- test('should preserve other sections in CLAUDE.md', () => {
176
- const fullContent = `<claude_context project="test">
177
- <current_work>
178
- Working on: [#999] Old Feature (bug)
179
- </current_work>
180
- <project_summary>Test project</project_summary>
181
- <tech_stack>Node.js, Jest</tech_stack>
182
- <mode>speed</mode>
183
- </claude_context>`;
184
-
185
- fs.writeFileSync('CLAUDE.md', fullContent);
186
-
187
- updateCurrentWork(validWorkItem);
188
-
189
- const content = fs.readFileSync('CLAUDE.md', 'utf-8');
190
- expect(content).toContain('<project_summary>Test project</project_summary>');
191
- expect(content).toContain('<tech_stack>Node.js, Jest</tech_stack>');
192
- expect(content).toContain('<mode>speed</mode>');
193
- });
194
- });
195
- });
@@ -1,511 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const config = require('./config');
4
- const { createTestEnvironment } = require('./test-helpers');
5
-
6
- describe('Config Module', () => {
7
- let testEnv;
8
-
9
- beforeEach(() => {
10
- testEnv = createTestEnvironment();
11
- process.chdir(testEnv.testDir);
12
- });
13
-
14
- afterEach(() => {
15
- testEnv.cleanup();
16
- });
17
-
18
- describe('read()', () => {
19
- test('should return default config if config file does not exist', () => {
20
- const result = config.read();
21
- expect(result).toEqual({
22
- name: path.basename(testEnv.testDir),
23
- stage: 'empty',
24
- bundles: ['core'],
25
- project_state: 'external',
26
- project_discovery: {
27
- status: 'not_started',
28
- prototypes: [],
29
- winner: null,
30
- rationale: null,
31
- started_date: null,
32
- completed_date: null,
33
- checkpoint: {
34
- step: 1,
35
- user_journey: null,
36
- ux_approach: null,
37
- epics_created: false
38
- }
39
- }
40
- });
41
- });
42
-
43
- test('should read existing config file', () => {
44
- const testConfig = { name: 'test-project', mode: 'speed' };
45
- fs.mkdirSync('.jettypod', { recursive: true });
46
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
47
-
48
- const result = config.read();
49
- expect(result).toEqual({
50
- ...testConfig,
51
- project_state: 'external',
52
- project_discovery: {
53
- status: 'not_started',
54
- prototypes: [],
55
- winner: null,
56
- rationale: null,
57
- started_date: null,
58
- completed_date: null,
59
- checkpoint: {
60
- step: 1,
61
- user_journey: null,
62
- ux_approach: null,
63
- epics_created: false
64
- }
65
- }
66
- });
67
- });
68
-
69
- test('should handle malformed JSON gracefully', () => {
70
- fs.mkdirSync('.jettypod', { recursive: true });
71
- fs.writeFileSync('.jettypod/config.json', 'not valid json');
72
-
73
- const result = config.read();
74
- expect(result).toEqual({
75
- name: path.basename(testEnv.testDir),
76
- stage: 'empty',
77
- bundles: ['core'],
78
- project_state: 'external',
79
- project_discovery: {
80
- status: 'not_started',
81
- prototypes: [],
82
- winner: null,
83
- rationale: null,
84
- started_date: null,
85
- completed_date: null,
86
- checkpoint: {
87
- step: 1,
88
- user_journey: null,
89
- ux_approach: null,
90
- epics_created: false
91
- }
92
- }
93
- });
94
- });
95
- });
96
-
97
- describe('write()', () => {
98
- test('should create .jettypod directory if it does not exist', () => {
99
- const testConfig = { name: 'test-project' };
100
-
101
- config.write(testConfig);
102
-
103
- expect(fs.existsSync('.jettypod')).toBe(true);
104
- expect(fs.existsSync('.jettypod/config.json')).toBe(true);
105
- });
106
-
107
- test('should write config as formatted JSON', () => {
108
- const testConfig = { name: 'test-project', mode: 'speed' };
109
-
110
- config.write(testConfig);
111
-
112
- const written = fs.readFileSync('.jettypod/config.json', 'utf-8');
113
- expect(JSON.parse(written)).toEqual(testConfig);
114
- // Check it's formatted (has newlines and indentation)
115
- expect(written).toContain('\n');
116
- expect(written).toContain(' ');
117
- });
118
- });
119
-
120
- describe('update()', () => {
121
- test('should merge updates with existing config', () => {
122
- const initial = { name: 'test-project', mode: 'discovery' };
123
- fs.mkdirSync('.jettypod', { recursive: true });
124
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(initial));
125
-
126
- config.update({ mode: 'speed', stage: 'growing' });
127
-
128
- const result = config.read();
129
- expect(result).toEqual({
130
- name: 'test-project',
131
- mode: 'speed',
132
- stage: 'growing',
133
- project_state: 'external',
134
- project_discovery: {
135
- status: 'not_started',
136
- prototypes: [],
137
- winner: null,
138
- rationale: null,
139
- started_date: null,
140
- completed_date: null,
141
- checkpoint: {
142
- step: 1,
143
- user_journey: null,
144
- ux_approach: null,
145
- epics_created: false
146
- }
147
- }
148
- });
149
- });
150
-
151
- test('should create config if it does not exist', () => {
152
- config.update({ mode: 'speed' });
153
-
154
- const result = config.read();
155
- expect(result).toEqual({
156
- name: path.basename(testEnv.testDir),
157
- mode: 'speed', // Updated value
158
- stage: 'empty', // Default value
159
- bundles: ['core'], // Default value
160
- project_state: 'external', // Default value
161
- project_discovery: {
162
- status: 'not_started',
163
- prototypes: [],
164
- winner: null,
165
- rationale: null,
166
- started_date: null,
167
- completed_date: null,
168
- checkpoint: {
169
- step: 1,
170
- user_journey: null,
171
- ux_approach: null,
172
- epics_created: false
173
- }
174
- }
175
- });
176
- });
177
- });
178
-
179
- describe('exists()', () => {
180
- test('should return false if config does not exist', () => {
181
- expect(config.exists()).toBe(false);
182
- });
183
-
184
- test('should return true if config exists', () => {
185
- fs.mkdirSync('.jettypod', { recursive: true });
186
- fs.writeFileSync('.jettypod/config.json', '{}');
187
-
188
- expect(config.exists()).toBe(true);
189
- });
190
- });
191
-
192
- describe('project_discovery', () => {
193
- test('should include default project_discovery in new config', () => {
194
- const result = config.read();
195
- expect(result.project_discovery).toEqual({
196
- status: 'not_started',
197
- prototypes: [],
198
- winner: null,
199
- rationale: null,
200
- started_date: null,
201
- completed_date: null,
202
- checkpoint: {
203
- step: 1,
204
- user_journey: null,
205
- ux_approach: null,
206
- epics_created: false
207
- }
208
- });
209
- });
210
-
211
- test('should add project_discovery to existing config without it', () => {
212
- const testConfig = { name: 'test-project' };
213
- fs.mkdirSync('.jettypod', { recursive: true });
214
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
215
-
216
- const result = config.read();
217
- expect(result.project_discovery).toBeDefined();
218
- expect(result.project_discovery.status).toBe('not_started');
219
- });
220
-
221
- test('should preserve existing project_discovery data', () => {
222
- const testConfig = {
223
- name: 'test-project',
224
- project_discovery: {
225
- status: 'in_progress',
226
- prototypes: ['proto1', 'proto2'],
227
- winner: null,
228
- rationale: null,
229
- started_date: '2025-10-29',
230
- completed_date: null,
231
- checkpoint: {
232
- step: 3,
233
- user_journey: 'test journey',
234
- ux_approach: 'test approach',
235
- epics_created: false
236
- }
237
- }
238
- };
239
- fs.mkdirSync('.jettypod', { recursive: true });
240
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
241
-
242
- const result = config.read();
243
- expect(result.project_discovery).toEqual(testConfig.project_discovery);
244
- });
245
-
246
- test('should fix invalid discovery status', () => {
247
- const testConfig = {
248
- name: 'test-project',
249
- project_discovery: {
250
- status: 'invalid_status',
251
- prototypes: [],
252
- winner: null,
253
- rationale: null,
254
- started_date: null,
255
- completed_date: null
256
- }
257
- };
258
- fs.mkdirSync('.jettypod', { recursive: true });
259
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
260
-
261
- const result = config.read();
262
- expect(result.project_discovery.status).toBe('not_started');
263
- });
264
-
265
- test('should validate discovery status', () => {
266
- expect(config.isValidDiscoveryStatus('not_started')).toBe(true);
267
- expect(config.isValidDiscoveryStatus('in_progress')).toBe(true);
268
- expect(config.isValidDiscoveryStatus('completed')).toBe(true);
269
- expect(config.isValidDiscoveryStatus('invalid')).toBe(false);
270
- });
271
-
272
- test('should update project_discovery via update()', () => {
273
- config.update({
274
- project_discovery: {
275
- status: 'in_progress',
276
- prototypes: ['prototype1'],
277
- winner: null,
278
- rationale: null,
279
- started_date: '2025-10-29T10:00:00Z',
280
- completed_date: null
281
- }
282
- });
283
-
284
- const result = config.read();
285
- expect(result.project_discovery.status).toBe('in_progress');
286
- expect(result.project_discovery.prototypes).toEqual(['prototype1']);
287
- });
288
- });
289
-
290
- describe('project_discovery edge cases', () => {
291
- test('should handle project_discovery as array (not object)', () => {
292
- const testConfig = {
293
- name: 'test-project',
294
- project_discovery: [] // Invalid: array not object
295
- };
296
- fs.mkdirSync('.jettypod', { recursive: true });
297
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
298
-
299
- const result = config.read();
300
- expect(result.project_discovery).toEqual(config.getDefaultProjectDiscovery());
301
- });
302
-
303
- test('should handle project_discovery with prototypes as non-array', () => {
304
- const testConfig = {
305
- name: 'test-project',
306
- project_discovery: {
307
- status: 'in_progress',
308
- prototypes: { bad: 'value' }, // Invalid: object not array
309
- winner: null,
310
- rationale: null
311
- }
312
- };
313
- fs.mkdirSync('.jettypod', { recursive: true });
314
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
315
-
316
- const result = config.read();
317
- expect(Array.isArray(result.project_discovery.prototypes)).toBe(true);
318
- expect(result.project_discovery.prototypes).toEqual([]);
319
- });
320
-
321
- test('should handle project_discovery with winner as number', () => {
322
- const testConfig = {
323
- name: 'test-project',
324
- project_discovery: {
325
- status: 'completed',
326
- prototypes: [],
327
- winner: 123, // Invalid: number not string
328
- rationale: 'test'
329
- }
330
- };
331
- fs.mkdirSync('.jettypod', { recursive: true });
332
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
333
-
334
- const result = config.read();
335
- expect(result.project_discovery.winner).toBe(null);
336
- });
337
-
338
- test('should handle project_discovery with rationale as number', () => {
339
- const testConfig = {
340
- name: 'test-project',
341
- project_discovery: {
342
- status: 'completed',
343
- prototypes: [],
344
- winner: 'proto.js',
345
- rationale: 456 // Invalid: number not string
346
- }
347
- };
348
- fs.mkdirSync('.jettypod', { recursive: true });
349
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
350
-
351
- const result = config.read();
352
- expect(result.project_discovery.rationale).toBe(null);
353
- });
354
-
355
- test('should handle project_discovery with invalid date types', () => {
356
- const testConfig = {
357
- name: 'test-project',
358
- project_discovery: {
359
- status: 'in_progress',
360
- prototypes: [],
361
- winner: null,
362
- rationale: null,
363
- started_date: 12345, // Invalid: number not string
364
- completed_date: true // Invalid: boolean not string
365
- }
366
- };
367
- fs.mkdirSync('.jettypod', { recursive: true });
368
- fs.writeFileSync('.jettypod/config.json', JSON.stringify(testConfig));
369
-
370
- const result = config.read();
371
- expect(result.project_discovery.started_date).toBe(null);
372
- expect(result.project_discovery.completed_date).toBe(null);
373
- });
374
- });
375
-
376
- describe('project_discovery update() validation', () => {
377
- test('should reject project_discovery as non-object', () => {
378
- expect(() => {
379
- config.update({ project_discovery: 'not an object' });
380
- }).toThrow('must be an object');
381
- });
382
-
383
- test('should reject project_discovery as array', () => {
384
- expect(() => {
385
- config.update({ project_discovery: [] });
386
- }).toThrow('must be an object');
387
- });
388
-
389
- test('should reject invalid discovery status', () => {
390
- expect(() => {
391
- config.update({
392
- project_discovery: {
393
- status: 'invalid_status',
394
- prototypes: [],
395
- winner: null,
396
- rationale: null
397
- }
398
- });
399
- }).toThrow("Must be 'not_started', 'in_progress', or 'completed'");
400
- });
401
-
402
- test('should reject prototypes as non-array', () => {
403
- expect(() => {
404
- config.update({
405
- project_discovery: {
406
- status: 'in_progress',
407
- prototypes: 'not an array',
408
- winner: null,
409
- rationale: null
410
- }
411
- });
412
- }).toThrow('prototypes: must be an array');
413
- });
414
-
415
- test('should reject winner as non-string (when not null)', () => {
416
- expect(() => {
417
- config.update({
418
- project_discovery: {
419
- status: 'completed',
420
- prototypes: [],
421
- winner: 123,
422
- rationale: 'test'
423
- }
424
- });
425
- }).toThrow('winner: must be a string or null');
426
- });
427
-
428
- test('should reject rationale as non-string (when not null)', () => {
429
- expect(() => {
430
- config.update({
431
- project_discovery: {
432
- status: 'completed',
433
- prototypes: [],
434
- winner: 'proto.js',
435
- rationale: 456
436
- }
437
- });
438
- }).toThrow('rationale: must be a string or null');
439
- });
440
-
441
- test('should accept valid project_discovery update', () => {
442
- expect(() => {
443
- config.update({
444
- project_discovery: {
445
- status: 'in_progress',
446
- prototypes: ['proto1.js', 'proto2.js'],
447
- winner: null,
448
- rationale: null,
449
- started_date: '2025-10-29T10:00:00Z',
450
- completed_date: null
451
- }
452
- });
453
- }).not.toThrow();
454
-
455
- const result = config.read();
456
- expect(result.project_discovery.status).toBe('in_progress');
457
- expect(result.project_discovery.prototypes).toHaveLength(2);
458
- });
459
- });
460
-
461
- describe('project_discovery workflow', () => {
462
- test('should handle complete discovery workflow', () => {
463
- // Start discovery
464
- config.update({
465
- project_discovery: {
466
- status: 'in_progress',
467
- prototypes: [],
468
- winner: null,
469
- rationale: null,
470
- started_date: '2025-10-29T10:00:00Z',
471
- completed_date: null
472
- }
473
- });
474
-
475
- let result = config.read();
476
- expect(result.project_discovery.status).toBe('in_progress');
477
-
478
- // Add prototypes
479
- config.update({
480
- project_discovery: {
481
- status: 'in_progress',
482
- prototypes: ['proto1.js', 'proto2.js', 'proto3.js'],
483
- winner: null,
484
- rationale: null,
485
- started_date: '2025-10-29T10:00:00Z',
486
- completed_date: null
487
- }
488
- });
489
-
490
- result = config.read();
491
- expect(result.project_discovery.prototypes).toHaveLength(3);
492
-
493
- // Complete discovery
494
- config.update({
495
- project_discovery: {
496
- status: 'completed',
497
- prototypes: ['proto1.js', 'proto2.js', 'proto3.js'],
498
- winner: 'proto2.js',
499
- rationale: 'Balanced approach won - best mix of features and simplicity',
500
- started_date: '2025-10-29T10:00:00Z',
501
- completed_date: '2025-10-29T15:30:00Z'
502
- }
503
- });
504
-
505
- result = config.read();
506
- expect(result.project_discovery.status).toBe('completed');
507
- expect(result.project_discovery.winner).toBe('proto2.js');
508
- expect(result.project_discovery.rationale).toContain('Balanced approach');
509
- });
510
- });
511
- });