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
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Unit test generator
3
+ * Automatically scaffolds and generates Jest unit tests
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Scaffold empty unit test file with describe blocks
11
+ * @param {string} jsFilePath - Path to JS file to create tests for
12
+ * @returns {string} Path to created test file
13
+ */
14
+ function scaffoldUnitTestFile(jsFilePath) {
15
+ const testFilePath = getTestFilePath(jsFilePath);
16
+
17
+ // Don't overwrite existing test files
18
+ if (fs.existsSync(testFilePath)) {
19
+ return testFilePath;
20
+ }
21
+
22
+ const fileName = path.basename(jsFilePath);
23
+ const moduleName = path.basename(jsFilePath, '.js');
24
+
25
+ const template = `/**
26
+ * Unit tests for ${fileName}
27
+ * Auto-generated by JettyPod unit-test-generator
28
+ */
29
+
30
+ describe('${moduleName}', () => {
31
+ // TODO: Add test cases
32
+
33
+ it('should be implemented', () => {
34
+ expect(true).toBe(true);
35
+ });
36
+ });
37
+ `;
38
+
39
+ // Ensure test directory exists
40
+ const testDir = path.dirname(testFilePath);
41
+ if (!fs.existsSync(testDir)) {
42
+ fs.mkdirSync(testDir, { recursive: true });
43
+ }
44
+
45
+ fs.writeFileSync(testFilePath, template, 'utf8');
46
+ return testFilePath;
47
+ }
48
+
49
+ /**
50
+ * Generate tests for functions in a JS file
51
+ * @param {string} jsFilePath - Path to JS file
52
+ * @returns {string} Generated test code
53
+ */
54
+ function generateTestsForFunctions(jsFilePath) {
55
+ const functions = extractFunctions(jsFilePath);
56
+
57
+ if (functions.length === 0) {
58
+ return null;
59
+ }
60
+
61
+ const fileName = path.basename(jsFilePath);
62
+ const moduleName = path.basename(jsFilePath, '.js');
63
+ const relativePath = path.relative(path.join(process.cwd(), 'test'), jsFilePath);
64
+
65
+ let testCode = `/**
66
+ * Unit tests for ${fileName}
67
+ * Auto-generated by JettyPod unit-test-generator
68
+ */
69
+
70
+ const ${moduleName} = require('${relativePath.replace(/\\/g, '/')}');
71
+
72
+ describe('${moduleName}', () => {
73
+ `;
74
+
75
+ for (const func of functions) {
76
+ testCode += `
77
+ describe('${func.name}()', () => {
78
+ it('should handle happy path', () => {
79
+ // TODO: Implement happy path test
80
+ expect(${moduleName}.${func.name}).toBeDefined();
81
+ });
82
+
83
+ it('should handle error cases', () => {
84
+ // TODO: Implement error case tests
85
+ });
86
+
87
+ it('should handle edge cases', () => {
88
+ // TODO: Implement edge case tests
89
+ });
90
+ });
91
+ `;
92
+ }
93
+
94
+ testCode += `});
95
+ `;
96
+
97
+ return testCode;
98
+ }
99
+
100
+ /**
101
+ * Extract function names from JS file using regex
102
+ * @param {string} jsFilePath - Path to JS file
103
+ * @returns {Array<{name: string, line: number}>} Function info
104
+ */
105
+ function extractFunctions(jsFilePath) {
106
+ if (!fs.existsSync(jsFilePath)) {
107
+ return [];
108
+ }
109
+
110
+ const content = fs.readFileSync(jsFilePath, 'utf8');
111
+ const lines = content.split('\n');
112
+ const functions = [];
113
+
114
+ // Match function declarations and expressions
115
+ const patterns = [
116
+ /function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/, // function name()
117
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*function/, // const name = function
118
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*\(/, // const name = ()
119
+ /async\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/, // async function name()
120
+ ];
121
+
122
+ for (let i = 0; i < lines.length; i++) {
123
+ const line = lines[i];
124
+
125
+ for (const pattern of patterns) {
126
+ const match = line.match(pattern);
127
+ if (match) {
128
+ functions.push({ name: match[1], line: i + 1 });
129
+ break;
130
+ }
131
+ }
132
+ }
133
+
134
+ return functions;
135
+ }
136
+
137
+ /**
138
+ * Get test file path for a JS file
139
+ * @param {string} jsFilePath - Path to JS file
140
+ * @returns {string} Path to test file
141
+ */
142
+ function getTestFilePath(jsFilePath) {
143
+ const parsed = path.parse(jsFilePath);
144
+ return path.join('test', parsed.dir, `${parsed.name}.test.js`);
145
+ }
146
+
147
+ /**
148
+ * Check if test file exists for a JS file
149
+ * @param {string} jsFilePath - Path to JS file
150
+ * @returns {string|null} Path to test file or null
151
+ */
152
+ function getExistingTestFile(jsFilePath) {
153
+ const testPath = getTestFilePath(jsFilePath);
154
+ return fs.existsSync(testPath) ? testPath : null;
155
+ }
156
+
157
+ /**
158
+ * Generate or update unit tests for new functions after GREEN
159
+ * @param {string} jsFilePath - Path to JS file
160
+ * @returns {{created: boolean, testFile: string, functionsAdded: number}} Result
161
+ */
162
+ function generateTestsAfterGreen(jsFilePath) {
163
+ const testFilePath = getTestFilePath(jsFilePath);
164
+ const functions = extractFunctions(jsFilePath);
165
+
166
+ if (functions.length === 0) {
167
+ return { created: false, testFile: null, functionsAdded: 0 };
168
+ }
169
+
170
+ // If test file doesn't exist, generate full test file
171
+ if (!fs.existsSync(testFilePath)) {
172
+ const testCode = generateTestsForFunctions(jsFilePath);
173
+ const testDir = path.dirname(testFilePath);
174
+
175
+ if (!fs.existsSync(testDir)) {
176
+ fs.mkdirSync(testDir, { recursive: true });
177
+ }
178
+
179
+ fs.writeFileSync(testFilePath, testCode, 'utf8');
180
+ return { created: true, testFile: testFilePath, functionsAdded: functions.length };
181
+ }
182
+
183
+ // Test file exists - check which functions need tests
184
+ const existingTests = fs.readFileSync(testFilePath, 'utf8');
185
+ const missingFunctions = functions.filter(func => {
186
+ const testPattern = new RegExp(`describe\\(['"\`]${func.name}\\(\\)['"\`]`);
187
+ return !testPattern.test(existingTests);
188
+ });
189
+
190
+ if (missingFunctions.length === 0) {
191
+ return { created: false, testFile: testFilePath, functionsAdded: 0 };
192
+ }
193
+
194
+ // Add tests for missing functions (append before closing brace)
195
+ const moduleName = path.basename(jsFilePath, '.js');
196
+ let additions = '';
197
+
198
+ for (const func of missingFunctions) {
199
+ additions += `
200
+ describe('${func.name}()', () => {
201
+ it('should handle happy path', () => {
202
+ // TODO: Implement happy path test
203
+ expect(${moduleName}.${func.name}).toBeDefined();
204
+ });
205
+
206
+ it('should handle error cases', () => {
207
+ // TODO: Implement error case tests
208
+ });
209
+
210
+ it('should handle edge cases', () => {
211
+ // TODO: Implement edge case tests
212
+ });
213
+ });
214
+ `;
215
+ }
216
+
217
+ // Insert before final closing brace
218
+ const updatedTests = existingTests.replace(/}\);[\s]*$/, `${additions}});
219
+ `);
220
+
221
+ fs.writeFileSync(testFilePath, updatedTests, 'utf8');
222
+ return { created: false, testFile: testFilePath, functionsAdded: missingFunctions.length };
223
+ }
224
+
225
+ module.exports = {
226
+ scaffoldUnitTestFile,
227
+ generateTestsForFunctions,
228
+ generateTestsAfterGreen,
229
+ extractFunctions,
230
+ getTestFilePath,
231
+ getExistingTestFile
232
+ };
@@ -0,0 +1,66 @@
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * Generate verification commands from scenario file and line number
5
+ * @param {string} scenarioFile - Path to the feature file
6
+ * @param {number} scenarioLine - Line number where the scenario starts
7
+ * @returns {{command: string, scenarioName: string}} Verification command and scenario name
8
+ */
9
+ function generateVerificationCommands(scenarioFile, scenarioLine) {
10
+ // Handle missing scenario file
11
+ if (!fs.existsSync(scenarioFile)) {
12
+ console.warn(`⚠️ Scenario file not found: ${scenarioFile}`);
13
+ return {
14
+ command: '',
15
+ scenarioName: ''
16
+ };
17
+ }
18
+
19
+ // Validate scenarioLine is a positive number
20
+ if (!scenarioLine || scenarioLine < 1) {
21
+ console.warn(`⚠️ Invalid scenario line number: ${scenarioLine}`);
22
+ scenarioLine = 1;
23
+ }
24
+
25
+ let content;
26
+ try {
27
+ // Read the scenario file to extract scenario name
28
+ content = fs.readFileSync(scenarioFile, 'utf8');
29
+ } catch (err) {
30
+ console.warn(`⚠️ Cannot read scenario file ${scenarioFile}: ${err.message}`);
31
+ return {
32
+ command: '',
33
+ scenarioName: ''
34
+ };
35
+ }
36
+
37
+ const lines = content.split('\n');
38
+
39
+ // Handle line number beyond file length
40
+ if (scenarioLine > lines.length) {
41
+ console.warn(`⚠️ Line number ${scenarioLine} exceeds file length (${lines.length} lines). Using line 1 as fallback.`);
42
+ scenarioLine = 1;
43
+ }
44
+
45
+ // Generate the test command with line number
46
+ const command = `npm run test:bdd -- ${scenarioFile}:${scenarioLine}`;
47
+
48
+ // Find the scenario name at the given line
49
+ let scenarioName = '';
50
+ for (let i = scenarioLine - 1; i < lines.length; i++) {
51
+ const line = lines[i].trim();
52
+ if (line.startsWith('Scenario:')) {
53
+ scenarioName = line.replace('Scenario:', '').trim();
54
+ break;
55
+ }
56
+ }
57
+
58
+ return {
59
+ command,
60
+ scenarioName
61
+ };
62
+ }
63
+
64
+ module.exports = {
65
+ generateVerificationCommands
66
+ };
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Worktree Diagnostics - Health Check and Issue Detection
3
+ *
4
+ * This module provides comprehensive diagnostic capabilities for worktree health
5
+ * and integrity. It detects issues, provides actionable recommendations, and
6
+ * generates diagnostic reports.
7
+ *
8
+ * Diagnostic categories:
9
+ * 1. Database integrity (orphaned entries, missing references)
10
+ * 2. Git state (orphaned worktrees, stale branches)
11
+ * 3. Filesystem state (missing directories, permission issues)
12
+ * 4. Configuration issues (missing .jettypod symlinks, incorrect permissions)
13
+ */
14
+
15
+ const worktreeReconciler = require('./worktree-reconciler');
16
+ const worktreeManager = require('./worktree-manager');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { execSync } = require('child_process');
20
+
21
+ /**
22
+ * Issue severity levels
23
+ */
24
+ const SEVERITY = {
25
+ CRITICAL: 'critical', // Prevents work from starting
26
+ HIGH: 'high', // May cause data loss or corruption
27
+ MEDIUM: 'medium', // May cause confusion or workflow issues
28
+ LOW: 'low', // Minor issues that don't affect functionality
29
+ INFO: 'info' // Informational, not an issue
30
+ };
31
+
32
+ /**
33
+ * Run comprehensive diagnostics on worktree state
34
+ *
35
+ * @param {Object} db - SQLite database connection
36
+ * @param {string} repoPath - Path to main git repository
37
+ * @param {Object} options - Diagnostic options
38
+ * @param {string[]} options.checks - Specific checks to run (defaults to all)
39
+ * @param {string} options.minSeverity - Minimum severity to report (defaults to 'info')
40
+ * @returns {Promise<Object>} Diagnostic report with issues, recommendations, and summary
41
+ */
42
+ async function runDiagnostics(db, repoPath, options = {}) {
43
+ const checks = options.checks || ['all'];
44
+ const minSeverity = options.minSeverity || 'info';
45
+
46
+ const report = {
47
+ timestamp: new Date().toISOString(),
48
+ repoPath: repoPath,
49
+ summary: {
50
+ totalIssues: 0,
51
+ critical: 0,
52
+ high: 0,
53
+ medium: 0,
54
+ low: 0,
55
+ info: 0
56
+ },
57
+ issues: [],
58
+ recommendations: [],
59
+ health: 'unknown' // 'healthy', 'warning', 'critical'
60
+ };
61
+
62
+ // Run reconciliation to detect state issues
63
+ if (checks.includes('all') || checks.includes('state')) {
64
+ try {
65
+ const reconcileResults = await worktreeReconciler.reconcileState(db, repoPath, { cleanup: false });
66
+
67
+ // Orphaned worktrees
68
+ if (reconcileResults.orphanedWorktrees.length > 0) {
69
+ report.issues.push({
70
+ severity: SEVERITY.MEDIUM,
71
+ category: 'state',
72
+ type: 'orphaned_worktrees',
73
+ count: reconcileResults.orphanedWorktrees.length,
74
+ message: `Found ${reconcileResults.orphanedWorktrees.length} orphaned worktree(s) in git but not in database`,
75
+ details: reconcileResults.orphanedWorktrees,
76
+ recommendation: 'Run reconciliation with cleanup=true to remove orphaned worktrees'
77
+ });
78
+ }
79
+
80
+ // Orphaned branches
81
+ if (reconcileResults.orphanedBranches.length > 0) {
82
+ report.issues.push({
83
+ severity: SEVERITY.LOW,
84
+ category: 'state',
85
+ type: 'orphaned_branches',
86
+ count: reconcileResults.orphanedBranches.length,
87
+ message: `Found ${reconcileResults.orphanedBranches.length} orphaned branch(es) with no worktree`,
88
+ details: reconcileResults.orphanedBranches,
89
+ recommendation: 'Run reconciliation with cleanup=true to delete empty branches'
90
+ });
91
+ }
92
+
93
+ // Stale database entries
94
+ if (reconcileResults.staleDbEntries.length > 0) {
95
+ report.issues.push({
96
+ severity: SEVERITY.HIGH,
97
+ category: 'state',
98
+ type: 'stale_db_entries',
99
+ count: reconcileResults.staleDbEntries.length,
100
+ message: `Found ${reconcileResults.staleDbEntries.length} database entries with missing filesystem directories`,
101
+ details: reconcileResults.staleDbEntries,
102
+ recommendation: 'Run reconciliation with cleanup=true to mark as corrupted'
103
+ });
104
+ }
105
+
106
+ // Git-filesystem mismatches
107
+ if (reconcileResults.gitFilesystemMismatches.length > 0) {
108
+ report.issues.push({
109
+ severity: SEVERITY.CRITICAL,
110
+ category: 'state',
111
+ type: 'git_filesystem_mismatch',
112
+ count: reconcileResults.gitFilesystemMismatches.length,
113
+ message: `Found ${reconcileResults.gitFilesystemMismatches.length} directories that exist but git doesn't track`,
114
+ details: reconcileResults.gitFilesystemMismatches,
115
+ recommendation: 'Run reconciliation with cleanup=true to re-register with git'
116
+ });
117
+ }
118
+
119
+ } catch (err) {
120
+ report.issues.push({
121
+ severity: SEVERITY.CRITICAL,
122
+ category: 'state',
123
+ type: 'reconciliation_error',
124
+ message: `Failed to run state reconciliation: ${err.message}`,
125
+ details: { error: err.message, stack: err.stack },
126
+ recommendation: 'Check git repository integrity and database connection'
127
+ });
128
+ }
129
+ }
130
+
131
+ // Check database integrity
132
+ if (checks.includes('all') || checks.includes('database')) {
133
+ try {
134
+ const dbIssues = await checkDatabaseIntegrity(db);
135
+ report.issues.push(...dbIssues);
136
+ } catch (err) {
137
+ report.issues.push({
138
+ severity: SEVERITY.CRITICAL,
139
+ category: 'database',
140
+ type: 'database_check_error',
141
+ message: `Failed to check database integrity: ${err.message}`,
142
+ details: { error: err.message, stack: err.stack },
143
+ recommendation: 'Check database file permissions and integrity'
144
+ });
145
+ }
146
+ }
147
+
148
+ // Check filesystem health
149
+ if (checks.includes('all') || checks.includes('filesystem')) {
150
+ try {
151
+ const fsIssues = await checkFilesystemHealth(db, repoPath);
152
+ report.issues.push(...fsIssues);
153
+ } catch (err) {
154
+ report.issues.push({
155
+ severity: SEVERITY.HIGH,
156
+ category: 'filesystem',
157
+ type: 'filesystem_check_error',
158
+ message: `Failed to check filesystem health: ${err.message}`,
159
+ details: { error: err.message, stack: err.stack },
160
+ recommendation: 'Check filesystem permissions and disk space'
161
+ });
162
+ }
163
+ }
164
+
165
+ // Filter by minimum severity
166
+ report.issues = filterBySeverity(report.issues, minSeverity);
167
+
168
+ // Calculate summary
169
+ for (const issue of report.issues) {
170
+ report.summary.totalIssues++;
171
+ report.summary[issue.severity]++;
172
+ }
173
+
174
+ // Determine overall health
175
+ if (report.summary.critical > 0) {
176
+ report.health = 'critical';
177
+ } else if (report.summary.high > 0 || report.summary.medium > 0) {
178
+ report.health = 'warning';
179
+ } else {
180
+ report.health = 'healthy';
181
+ }
182
+
183
+ // Generate recommendations
184
+ report.recommendations = generateRecommendations(report);
185
+
186
+ return report;
187
+ }
188
+
189
+ /**
190
+ * Check database integrity
191
+ */
192
+ async function checkDatabaseIntegrity(db) {
193
+ const issues = [];
194
+
195
+ // Check for corrupted worktrees
196
+ const corruptedCount = await new Promise((resolve, reject) => {
197
+ db.get('SELECT COUNT(*) as count FROM worktrees WHERE status = ?', ['corrupted'], (err, row) => {
198
+ if (err) reject(err);
199
+ else resolve(row.count);
200
+ });
201
+ });
202
+
203
+ if (corruptedCount > 0) {
204
+ issues.push({
205
+ severity: SEVERITY.INFO,
206
+ category: 'database',
207
+ type: 'corrupted_worktrees',
208
+ count: corruptedCount,
209
+ message: `Found ${corruptedCount} corrupted worktree(s) in database`,
210
+ details: { count: corruptedCount },
211
+ recommendation: 'These worktrees can be safely cleaned up or ignored'
212
+ });
213
+ }
214
+
215
+ // Check for worktrees with missing work items
216
+ const orphanedWorktrees = await new Promise((resolve, reject) => {
217
+ db.all(`
218
+ SELECT w.* FROM worktrees w
219
+ LEFT JOIN work_items wi ON w.work_item_id = wi.id
220
+ WHERE wi.id IS NULL
221
+ `, (err, rows) => {
222
+ if (err) reject(err);
223
+ else resolve(rows);
224
+ });
225
+ });
226
+
227
+ if (orphanedWorktrees.length > 0) {
228
+ issues.push({
229
+ severity: SEVERITY.HIGH,
230
+ category: 'database',
231
+ type: 'orphaned_worktree_entries',
232
+ count: orphanedWorktrees.length,
233
+ message: `Found ${orphanedWorktrees.length} worktree(s) with missing work items`,
234
+ details: orphanedWorktrees,
235
+ recommendation: 'Mark these worktrees as corrupted'
236
+ });
237
+ }
238
+
239
+ return issues;
240
+ }
241
+
242
+ /**
243
+ * Check filesystem health
244
+ */
245
+ async function checkFilesystemHealth(db, repoPath) {
246
+ const issues = [];
247
+
248
+ // Get all active worktrees
249
+ const activeWorktrees = await new Promise((resolve, reject) => {
250
+ db.all('SELECT * FROM worktrees WHERE status = ?', ['active'], (err, rows) => {
251
+ if (err) reject(err);
252
+ else resolve(rows || []);
253
+ });
254
+ });
255
+
256
+ // Check each worktree directory
257
+ for (const worktree of activeWorktrees) {
258
+ const worktreePath = worktree.worktree_path;
259
+
260
+ // Check if directory exists
261
+ if (!fs.existsSync(worktreePath)) {
262
+ issues.push({
263
+ severity: SEVERITY.CRITICAL,
264
+ category: 'filesystem',
265
+ type: 'missing_worktree_directory',
266
+ message: `Active worktree directory missing: ${worktreePath}`,
267
+ details: { worktree },
268
+ recommendation: 'Mark worktree as corrupted and recreate if needed'
269
+ });
270
+ continue;
271
+ }
272
+
273
+ // Check .jettypod symlink
274
+ const jettypodLink = path.join(worktreePath, '.jettypod');
275
+ if (!fs.existsSync(jettypodLink)) {
276
+ issues.push({
277
+ severity: SEVERITY.MEDIUM,
278
+ category: 'filesystem',
279
+ type: 'missing_jettypod_symlink',
280
+ message: `.jettypod symlink missing in worktree: ${worktreePath}`,
281
+ details: { worktree, expectedLink: jettypodLink },
282
+ recommendation: 'Recreate symlink to .jettypod directory'
283
+ });
284
+ }
285
+
286
+ // Check directory permissions
287
+ try {
288
+ fs.accessSync(worktreePath, fs.constants.R_OK | fs.constants.W_OK);
289
+ } catch (err) {
290
+ issues.push({
291
+ severity: SEVERITY.CRITICAL,
292
+ category: 'filesystem',
293
+ type: 'permission_issue',
294
+ message: `Cannot read/write worktree directory: ${worktreePath}`,
295
+ details: { worktree, error: err.message },
296
+ recommendation: 'Fix directory permissions or mark worktree as corrupted'
297
+ });
298
+ }
299
+ }
300
+
301
+ return issues;
302
+ }
303
+
304
+ /**
305
+ * Filter issues by minimum severity
306
+ */
307
+ function filterBySeverity(issues, minSeverity) {
308
+ const severityOrder = ['info', 'low', 'medium', 'high', 'critical'];
309
+ const minIndex = severityOrder.indexOf(minSeverity);
310
+
311
+ return issues.filter(issue => {
312
+ const issueIndex = severityOrder.indexOf(issue.severity);
313
+ return issueIndex >= minIndex;
314
+ });
315
+ }
316
+
317
+ /**
318
+ * Generate actionable recommendations based on issues
319
+ */
320
+ function generateRecommendations(report) {
321
+ const recommendations = [];
322
+
323
+ if (report.summary.critical > 0) {
324
+ recommendations.push({
325
+ priority: 'urgent',
326
+ action: 'Run worktree reconciliation with cleanup enabled',
327
+ reason: 'Critical issues detected that may prevent work from starting'
328
+ });
329
+ }
330
+
331
+ if (report.summary.high > 0) {
332
+ recommendations.push({
333
+ priority: 'high',
334
+ action: 'Review and fix database integrity issues',
335
+ reason: 'High-severity issues may cause data loss or corruption'
336
+ });
337
+ }
338
+
339
+ if (report.issues.some(i => i.type === 'missing_jettypod_symlink')) {
340
+ recommendations.push({
341
+ priority: 'medium',
342
+ action: 'Recreate .jettypod symlinks in affected worktrees',
343
+ reason: 'Missing symlinks may cause configuration and database issues'
344
+ });
345
+ }
346
+
347
+ if (report.issues.some(i => i.type === 'corrupted_worktrees')) {
348
+ recommendations.push({
349
+ priority: 'low',
350
+ action: 'Clean up corrupted worktree entries from database',
351
+ reason: 'Keeping database clean improves query performance'
352
+ });
353
+ }
354
+
355
+ if (recommendations.length === 0) {
356
+ recommendations.push({
357
+ priority: 'none',
358
+ action: 'No action required',
359
+ reason: 'All worktree systems are healthy'
360
+ });
361
+ }
362
+
363
+ return recommendations;
364
+ }
365
+
366
+ /**
367
+ * Quick health check (lightweight version of full diagnostics)
368
+ *
369
+ * @param {Object} db - SQLite database connection
370
+ * @param {string} repoPath - Path to main git repository
371
+ * @returns {Promise<Object>} Health status object
372
+ */
373
+ async function quickHealthCheck(db, repoPath) {
374
+ const health = {
375
+ status: 'unknown', // 'healthy', 'degraded', 'critical'
376
+ message: '',
377
+ issueCount: 0
378
+ };
379
+
380
+ try {
381
+ // Just check for critical issues
382
+ const report = await runDiagnostics(db, repoPath, {
383
+ checks: ['state'],
384
+ minSeverity: 'high'
385
+ });
386
+
387
+ health.issueCount = report.summary.totalIssues;
388
+
389
+ if (report.summary.critical > 0) {
390
+ health.status = 'critical';
391
+ health.message = `Found ${report.summary.critical} critical issue(s)`;
392
+ } else if (report.summary.high > 0) {
393
+ health.status = 'degraded';
394
+ health.message = `Found ${report.summary.high} high-severity issue(s)`;
395
+ } else {
396
+ health.status = 'healthy';
397
+ health.message = 'All systems operational';
398
+ }
399
+
400
+ } catch (err) {
401
+ health.status = 'critical';
402
+ health.message = `Health check failed: ${err.message}`;
403
+ health.error = err.message;
404
+ }
405
+
406
+ return health;
407
+ }
408
+
409
+ module.exports = {
410
+ runDiagnostics,
411
+ quickHealthCheck,
412
+ SEVERITY
413
+ };