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,218 @@
1
+ /**
2
+ * Coverage tracker for unit tests
3
+ *
4
+ * Tracks and reports unit test coverage after stable mode completion.
5
+ * Uses Jest's built-in coverage capabilities.
6
+ */
7
+
8
+ const { execSync } = require('child_process');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ /**
13
+ * Run Jest with coverage for specific test files
14
+ *
15
+ * @param {string[]} testFiles - Array of test file paths
16
+ * @param {Object} options - Coverage options
17
+ * @param {number} options.coverageThreshold - Minimum coverage percentage (default: 80)
18
+ * @param {boolean} options.verbose - Show detailed coverage report (default: false)
19
+ * @returns {{success: boolean, coverage: Object, error?: string}}
20
+ */
21
+ function runCoverageForTests(testFiles, options = {}) {
22
+ const {
23
+ coverageThreshold = 80,
24
+ verbose = false
25
+ } = options;
26
+
27
+ if (!testFiles || testFiles.length === 0) {
28
+ return {
29
+ success: true,
30
+ coverage: { statements: 0, branches: 0, functions: 0, lines: 0 },
31
+ message: 'No test files to run coverage on'
32
+ };
33
+ }
34
+
35
+ try {
36
+ // Build Jest command with coverage
37
+ const testPattern = testFiles.join('|');
38
+ const jestCommand = [
39
+ 'npx jest',
40
+ `--testMatch="**/{${testPattern}}"`,
41
+ '--coverage',
42
+ '--coverageReporters=json-summary',
43
+ '--coverageReporters=text',
44
+ verbose ? '--verbose' : '--silent',
45
+ '--bail=false' // Run all tests even if some fail
46
+ ].join(' ');
47
+
48
+ const output = execSync(jestCommand, {
49
+ cwd: process.cwd(),
50
+ encoding: 'utf8',
51
+ stdio: 'pipe'
52
+ });
53
+
54
+ // Parse coverage summary
55
+ const coverageSummaryPath = path.join(process.cwd(), 'coverage', 'coverage-summary.json');
56
+
57
+ if (!fs.existsSync(coverageSummaryPath)) {
58
+ return {
59
+ success: false,
60
+ error: 'Coverage summary not generated'
61
+ };
62
+ }
63
+
64
+ const coverageSummary = JSON.parse(fs.readFileSync(coverageSummaryPath, 'utf8'));
65
+ const totalCoverage = coverageSummary.total;
66
+
67
+ const coverage = {
68
+ statements: totalCoverage.statements.pct,
69
+ branches: totalCoverage.branches.pct,
70
+ functions: totalCoverage.functions.pct,
71
+ lines: totalCoverage.lines.pct
72
+ };
73
+
74
+ // Check if coverage meets threshold
75
+ const meetsThreshold = Object.values(coverage).every(pct => pct >= coverageThreshold);
76
+
77
+ return {
78
+ success: meetsThreshold,
79
+ coverage,
80
+ threshold: coverageThreshold,
81
+ output: verbose ? output : undefined
82
+ };
83
+
84
+ } catch (error) {
85
+ // Jest exits with non-zero if tests fail or coverage is low
86
+ // Parse coverage even on failure
87
+ const coverageSummaryPath = path.join(process.cwd(), 'coverage', 'coverage-summary.json');
88
+
89
+ if (fs.existsSync(coverageSummaryPath)) {
90
+ const coverageSummary = JSON.parse(fs.readFileSync(coverageSummaryPath, 'utf8'));
91
+ const totalCoverage = coverageSummary.total;
92
+
93
+ const coverage = {
94
+ statements: totalCoverage.statements.pct,
95
+ branches: totalCoverage.branches.pct,
96
+ functions: totalCoverage.functions.pct,
97
+ lines: totalCoverage.lines.pct
98
+ };
99
+
100
+ return {
101
+ success: false,
102
+ coverage,
103
+ threshold: coverageThreshold,
104
+ error: error.message,
105
+ output: error.stdout || error.stderr
106
+ };
107
+ }
108
+
109
+ return {
110
+ success: false,
111
+ error: `Coverage execution failed: ${error.message}`
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get test files for implementation files
118
+ *
119
+ * @param {string[]} implementationFiles - Array of implementation file paths
120
+ * @returns {string[]} Array of test file paths
121
+ */
122
+ function getTestFilesForImplementation(implementationFiles) {
123
+ const { getTestFilePath } = require('./unit-test-generator');
124
+
125
+ const testFiles = [];
126
+
127
+ for (const implFile of implementationFiles) {
128
+ const testFile = getTestFilePath(implFile);
129
+
130
+ if (fs.existsSync(testFile)) {
131
+ testFiles.push(testFile);
132
+ }
133
+ }
134
+
135
+ return testFiles;
136
+ }
137
+
138
+ /**
139
+ * Format coverage report for display
140
+ *
141
+ * @param {Object} coverageResult - Result from runCoverageForTests
142
+ * @returns {string} Formatted coverage report
143
+ */
144
+ function formatCoverageReport(coverageResult) {
145
+ if (!coverageResult.coverage) {
146
+ return 'No coverage data available';
147
+ }
148
+
149
+ const { coverage, threshold, success } = coverageResult;
150
+
151
+ const lines = [
152
+ '\n📊 Unit Test Coverage Report',
153
+ '─────────────────────────────',
154
+ `Statements: ${formatPercentage(coverage.statements, threshold)}`,
155
+ `Branches: ${formatPercentage(coverage.branches, threshold)}`,
156
+ `Functions: ${formatPercentage(coverage.functions, threshold)}`,
157
+ `Lines: ${formatPercentage(coverage.lines, threshold)}`,
158
+ '─────────────────────────────',
159
+ success
160
+ ? `✅ All coverage metrics meet ${threshold}% threshold`
161
+ : `⚠️ Some metrics below ${threshold}% threshold`
162
+ ];
163
+
164
+ return lines.join('\n');
165
+ }
166
+
167
+ /**
168
+ * Format percentage with color indicator
169
+ *
170
+ * @param {number} percentage - Coverage percentage
171
+ * @param {number} threshold - Threshold percentage
172
+ * @returns {string} Formatted string
173
+ */
174
+ function formatPercentage(percentage, threshold) {
175
+ const pct = percentage.toFixed(2);
176
+ const status = percentage >= threshold ? '✅' : '⚠️ ';
177
+ return `${status} ${pct}%`;
178
+ }
179
+
180
+ /**
181
+ * Track coverage for feature implementation
182
+ *
183
+ * @param {string[]} implementationFiles - Files modified during implementation
184
+ * @param {Object} options - Coverage options
185
+ * @returns {{success: boolean, report: string, coverage: Object}}
186
+ */
187
+ function trackFeatureCoverage(implementationFiles, options = {}) {
188
+ // Get test files for implementation
189
+ const testFiles = getTestFilesForImplementation(implementationFiles);
190
+
191
+ if (testFiles.length === 0) {
192
+ return {
193
+ success: true,
194
+ report: 'No unit test files found for implementation',
195
+ coverage: null
196
+ };
197
+ }
198
+
199
+ // Run coverage
200
+ const result = runCoverageForTests(testFiles, options);
201
+
202
+ // Format report
203
+ const report = formatCoverageReport(result);
204
+
205
+ return {
206
+ success: result.success,
207
+ report,
208
+ coverage: result.coverage,
209
+ testFiles
210
+ };
211
+ }
212
+
213
+ module.exports = {
214
+ runCoverageForTests,
215
+ getTestFilesForImplementation,
216
+ formatCoverageReport,
217
+ trackFeatureCoverage
218
+ };
package/lib/database.js CHANGED
@@ -190,6 +190,8 @@ function initSchema() {
190
190
  // Do NOT add it here - the migration includes important data migration logic
191
191
  // NOTE: completed_at column is handled by migration 011-add-completed-at.js
192
192
  // Do NOT add it here - the migration system handles it properly
193
+ // NOTE: worktree_path column is handled by migration 012-add-worktree-path.js
194
+ db.run(`ALTER TABLE work_items ADD COLUMN worktree_path TEXT`, () => {});
193
195
  db.run(`ALTER TABLE work_items ADD COLUMN architectural_decision TEXT`, async () => {
194
196
  // Run data migrations after all schema operations complete (skip in test environments)
195
197
  if (process.env.NODE_ENV !== 'test') {
@@ -0,0 +1,192 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { getDb, getJettypodDir } = require('./database');
4
+
5
+ /**
6
+ * Ensure .jettypod/snapshots directory exists
7
+ */
8
+ function ensureSnapshotsDir() {
9
+ const snapshotsDir = path.join(getJettypodDir(), 'snapshots');
10
+ if (!fs.existsSync(snapshotsDir)) {
11
+ fs.mkdirSync(snapshotsDir, { recursive: true });
12
+ }
13
+ return snapshotsDir;
14
+ }
15
+
16
+ /**
17
+ * Export all tables from a database to a JSON structure
18
+ * @param {sqlite3.Database} db - Database connection
19
+ * @param {string[]} tables - Array of table names to export
20
+ * @returns {Promise<Object>} Object with table names as keys and row arrays as values
21
+ */
22
+ function exportTables(db, tables) {
23
+ return new Promise((resolve, reject) => {
24
+ const result = {};
25
+ let completed = 0;
26
+
27
+ if (tables.length === 0) {
28
+ resolve(result);
29
+ return;
30
+ }
31
+
32
+ tables.forEach(tableName => {
33
+ db.all(`SELECT * FROM ${tableName}`, [], (err, rows) => {
34
+ if (err) {
35
+ reject(new Error(`Failed to export table ${tableName}: ${err.message}`));
36
+ return;
37
+ }
38
+
39
+ result[tableName] = rows;
40
+ completed++;
41
+
42
+ if (completed === tables.length) {
43
+ resolve(result);
44
+ }
45
+ });
46
+ });
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Get list of all tables in a database
52
+ * @param {sqlite3.Database} db - Database connection
53
+ * @returns {Promise<string[]>} Array of table names
54
+ */
55
+ function getTables(db) {
56
+ return new Promise((resolve, reject) => {
57
+ db.all(
58
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
59
+ [],
60
+ (err, rows) => {
61
+ if (err) {
62
+ reject(new Error(`Failed to get table list: ${err.message}`));
63
+ return;
64
+ }
65
+ resolve(rows.map(row => row.name));
66
+ }
67
+ );
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Export work.db to .jettypod/snapshots/work.json
73
+ * @returns {Promise<string>} Path to exported file
74
+ */
75
+ async function exportWorkDb() {
76
+ const snapshotsDir = ensureSnapshotsDir();
77
+ const outputPath = path.join(snapshotsDir, 'work.json');
78
+
79
+ const db = getDb();
80
+ const tables = await getTables(db);
81
+ const data = await exportTables(db, tables);
82
+
83
+ try {
84
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
85
+ } catch (err) {
86
+ // Log warning but don't throw - we don't want to block git operations
87
+ console.error('Pre-commit hook warning: Failed to export work.json');
88
+ if (err.code === 'EACCES') {
89
+ console.error(' Permission denied - check directory permissions');
90
+ } else if (err.code === 'ENOSPC') {
91
+ console.error(' No space left on device');
92
+ } else {
93
+ console.error(` ${err.message}`);
94
+ }
95
+ console.error(' Commit will proceed but snapshots were not updated');
96
+ // Return path anyway so commit can continue
97
+ }
98
+
99
+ return outputPath;
100
+ }
101
+
102
+ /**
103
+ * Export database.db to .jettypod/snapshots/database.json
104
+ * @returns {Promise<string>} Path to exported file
105
+ */
106
+ async function exportDatabaseDb() {
107
+ const sqlite3 = require('sqlite3').verbose();
108
+ const snapshotsDir = ensureSnapshotsDir();
109
+ const outputPath = path.join(snapshotsDir, 'database.json');
110
+ const dbPath = path.join(getJettypodDir(), 'database.db');
111
+
112
+ // Check if database.db exists
113
+ if (!fs.existsSync(dbPath)) {
114
+ // Create empty export if database doesn't exist
115
+ try {
116
+ fs.writeFileSync(outputPath, JSON.stringify({}, null, 2), 'utf8');
117
+ } catch (err) {
118
+ console.error('Pre-commit hook warning: Failed to export database.json');
119
+ if (err.code === 'EACCES') {
120
+ console.error(' Permission denied - check directory permissions');
121
+ } else if (err.code === 'ENOSPC') {
122
+ console.error(' No space left on device');
123
+ } else {
124
+ console.error(` ${err.message}`);
125
+ }
126
+ console.error(' Commit will proceed but snapshots were not updated');
127
+ }
128
+ return outputPath;
129
+ }
130
+
131
+ return new Promise((resolve, reject) => {
132
+ const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, async (err) => {
133
+ if (err) {
134
+ reject(new Error(`Failed to open database.db: ${err.message}`));
135
+ return;
136
+ }
137
+
138
+ try {
139
+ const tables = await getTables(db);
140
+ const data = await exportTables(db, tables);
141
+
142
+ try {
143
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), 'utf8');
144
+ } catch (writeErr) {
145
+ console.error('Pre-commit hook warning: Failed to export database.json');
146
+ if (writeErr.code === 'EACCES') {
147
+ console.error(' Permission denied - check directory permissions');
148
+ } else if (writeErr.code === 'ENOSPC') {
149
+ console.error(' No space left on device');
150
+ } else {
151
+ console.error(` ${writeErr.message}`);
152
+ }
153
+ console.error(' Commit will proceed but snapshots were not updated');
154
+ }
155
+
156
+ db.close((closeErr) => {
157
+ if (closeErr) {
158
+ reject(new Error(`Failed to close database.db: ${closeErr.message}`));
159
+ } else {
160
+ resolve(outputPath);
161
+ }
162
+ });
163
+ } catch (exportErr) {
164
+ db.close();
165
+ reject(exportErr);
166
+ }
167
+ });
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Export both work.db and database.db
173
+ * @returns {Promise<{work: string, database: string}>} Paths to exported files
174
+ */
175
+ async function exportAll() {
176
+ const [workPath, databasePath] = await Promise.all([
177
+ exportWorkDb(),
178
+ exportDatabaseDb()
179
+ ]);
180
+
181
+ return {
182
+ work: workPath,
183
+ database: databasePath
184
+ };
185
+ }
186
+
187
+ module.exports = {
188
+ exportWorkDb,
189
+ exportDatabaseDb,
190
+ exportAll,
191
+ ensureSnapshotsDir
192
+ };
@@ -0,0 +1,193 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { getDb, getJettypodDir } = require('./database');
4
+
5
+ /**
6
+ * Import tables from JSON structure into a database
7
+ * @param {sqlite3.Database} db - Database connection
8
+ * @param {Object} data - Object with table names as keys and row arrays as values
9
+ * @returns {Promise<void>}
10
+ */
11
+ function importTables(db, data) {
12
+ return new Promise((resolve, reject) => {
13
+ const tableNames = Object.keys(data);
14
+
15
+ if (tableNames.length === 0) {
16
+ resolve();
17
+ return;
18
+ }
19
+
20
+ let completed = 0;
21
+
22
+ tableNames.forEach(tableName => {
23
+ const rows = data[tableName];
24
+
25
+ // Clear existing data
26
+ db.run(`DELETE FROM ${tableName}`, (err) => {
27
+ if (err) {
28
+ reject(new Error(`Failed to clear table ${tableName}: ${err.message}`));
29
+ return;
30
+ }
31
+
32
+ // If no rows to import, we're done with this table
33
+ if (rows.length === 0) {
34
+ completed++;
35
+ if (completed === tableNames.length) {
36
+ resolve();
37
+ }
38
+ return;
39
+ }
40
+
41
+ // Get column names from first row
42
+ const columns = Object.keys(rows[0]);
43
+ const placeholders = columns.map(() => '?').join(', ');
44
+ const columnNames = columns.join(', ');
45
+ const insertSql = `INSERT INTO ${tableName} (${columnNames}) VALUES (${placeholders})`;
46
+
47
+ // Insert all rows
48
+ let inserted = 0;
49
+ rows.forEach(row => {
50
+ const values = columns.map(col => row[col]);
51
+
52
+ db.run(insertSql, values, (err) => {
53
+ if (err) {
54
+ reject(new Error(`Failed to insert into ${tableName}: ${err.message}`));
55
+ return;
56
+ }
57
+
58
+ inserted++;
59
+ if (inserted === rows.length) {
60
+ completed++;
61
+ if (completed === tableNames.length) {
62
+ resolve();
63
+ }
64
+ }
65
+ });
66
+ });
67
+ });
68
+ });
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Import work.db from .jettypod/snapshots/work.json
74
+ * @returns {Promise<string>} Path to imported file
75
+ */
76
+ async function importWorkDb() {
77
+ const snapshotsDir = path.join(getJettypodDir(), 'snapshots');
78
+ const jsonPath = path.join(snapshotsDir, 'work.json');
79
+
80
+ // Skip if JSON doesn't exist
81
+ if (!fs.existsSync(jsonPath)) {
82
+ return jsonPath;
83
+ }
84
+
85
+ try {
86
+ const jsonContent = fs.readFileSync(jsonPath, 'utf8');
87
+ const data = JSON.parse(jsonContent);
88
+
89
+ const db = getDb();
90
+ await importTables(db, data);
91
+
92
+ return jsonPath;
93
+ } catch (err) {
94
+ // Log warning but don't throw - we don't want to block git operations
95
+ console.error('Post-checkout hook warning: Failed to import work.json');
96
+ if (err instanceof SyntaxError) {
97
+ console.error(' Malformed JSON - file may be corrupted');
98
+ } else {
99
+ console.error(` ${err.message}`);
100
+ }
101
+ console.error(' Database will remain in previous state');
102
+ return jsonPath;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Import database.db from .jettypod/snapshots/database.json
108
+ * @returns {Promise<string>} Path to imported file
109
+ */
110
+ async function importDatabaseDb() {
111
+ const sqlite3 = require('sqlite3').verbose();
112
+ const snapshotsDir = path.join(getJettypodDir(), 'snapshots');
113
+ const jsonPath = path.join(snapshotsDir, 'database.json');
114
+
115
+ // Skip if JSON doesn't exist
116
+ if (!fs.existsSync(jsonPath)) {
117
+ return jsonPath;
118
+ }
119
+
120
+ try {
121
+ const jsonContent = fs.readFileSync(jsonPath, 'utf8');
122
+ const data = JSON.parse(jsonContent);
123
+
124
+ // If no tables to import, we're done
125
+ if (Object.keys(data).length === 0) {
126
+ return jsonPath;
127
+ }
128
+
129
+ const dbPath = path.join(getJettypodDir(), 'database.db');
130
+
131
+ return new Promise((resolve, reject) => {
132
+ const db = new sqlite3.Database(dbPath, async (err) => {
133
+ if (err) {
134
+ console.error('Post-checkout hook warning: Failed to open database.db');
135
+ console.error(` ${err.message}`);
136
+ console.error(' Database will remain in previous state');
137
+ resolve(jsonPath);
138
+ return;
139
+ }
140
+
141
+ try {
142
+ await importTables(db, data);
143
+
144
+ db.close((closeErr) => {
145
+ if (closeErr) {
146
+ console.error('Post-checkout hook warning: Failed to close database.db');
147
+ console.error(` ${closeErr.message}`);
148
+ }
149
+ resolve(jsonPath);
150
+ });
151
+ } catch (importErr) {
152
+ db.close();
153
+ console.error('Post-checkout hook warning: Failed to import database.json');
154
+ console.error(` ${importErr.message}`);
155
+ console.error(' Database will remain in previous state');
156
+ resolve(jsonPath);
157
+ }
158
+ });
159
+ });
160
+ } catch (err) {
161
+ // Log warning but don't throw - we don't want to block git operations
162
+ console.error('Post-checkout hook warning: Failed to import database.json');
163
+ if (err instanceof SyntaxError) {
164
+ console.error(' Malformed JSON - file may be corrupted');
165
+ } else {
166
+ console.error(` ${err.message}`);
167
+ }
168
+ console.error(' Database will remain in previous state');
169
+ return jsonPath;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Import both work.db and database.db
175
+ * @returns {Promise<{work: string, database: string}>} Paths to imported files
176
+ */
177
+ async function importAll() {
178
+ const [workPath, databasePath] = await Promise.all([
179
+ importWorkDb(),
180
+ importDatabaseDb()
181
+ ]);
182
+
183
+ return {
184
+ work: workPath,
185
+ database: databasePath
186
+ };
187
+ }
188
+
189
+ module.exports = {
190
+ importWorkDb,
191
+ importDatabaseDb,
192
+ importAll
193
+ };
@@ -0,0 +1,32 @@
1
+ // External transition handler - orchestrates external transition
2
+ const { readStandards } = require('./production-standards-reader');
3
+ const { generateInfrastructureChores } = require('./infrastructure-chore-generator');
4
+ const { getDb } = require('./database');
5
+
6
+ /**
7
+ * Handle external transition - creates Infrastructure Readiness epic and generates chores
8
+ * @returns {Promise<number>} Created epic ID
9
+ */
10
+ async function handleExternalTransition() {
11
+ // Read production standards (throws if file doesn't exist)
12
+ const productionStandards = await readStandards();
13
+
14
+ // Create Infrastructure Readiness epic
15
+ const db = getDb();
16
+ const result = db.prepare(`
17
+ INSERT INTO work (type, title, description, status)
18
+ VALUES (?, ?, ?, ?)
19
+ `).run('epic', 'Infrastructure Readiness',
20
+ 'Production infrastructure chores generated from standards', 'backlog');
21
+
22
+ const epicId = result.lastInsertRowid;
23
+
24
+ // Generate chores from infrastructure-scoped standards
25
+ // This will emit warning if no infrastructure standards found
26
+ generateInfrastructureChores(epicId, productionStandards.standards);
27
+
28
+ console.log(`Created epic #${epicId}: Infrastructure Readiness`);
29
+ return epicId;
30
+ }
31
+
32
+ module.exports = { handleExternalTransition };