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.
- package/.nvmrc +1 -0
- package/docs/COMPLETE-TESTING-STRATEGY.md +970 -0
- package/docs/DECISIONS.md +10 -12
- package/docs/NODE_VERSION.md +83 -0
- package/docs/TDD-INFRASTRUCTURE-STRATEGY.md +1374 -0
- package/docs/TESTING-FOR-NON-ENGINEERS.md +1588 -0
- package/docs/TESTING-STRATEGY-AUDIT.md +698 -0
- package/hooks/post-checkout +17 -0
- package/hooks/post-merge +17 -0
- package/hooks/pre-commit +30 -0
- package/jettypod.js +259 -120
- package/lib/coverage-tracker.js +218 -0
- package/lib/database.js +2 -0
- package/lib/db-export.js +192 -0
- package/lib/db-import.js +193 -0
- package/lib/external-transition-handler.js +32 -0
- package/lib/git-hook-helpers.js +174 -0
- package/lib/git-root.js +90 -0
- package/lib/infrastructure-chore-generator.js +45 -0
- package/lib/install-hooks.js +52 -0
- package/lib/jettypod-backup.js +238 -0
- package/lib/merge-lock.js +193 -0
- package/lib/migrations/012-add-worktree-path.js +38 -0
- package/lib/migrations/013-worktrees-table.js +86 -0
- package/lib/migrations/014-migrate-worktree-data.js +161 -0
- package/lib/migrations/015-merge-locks-table.js +67 -0
- package/lib/pattern-finder.js +152 -0
- package/lib/process-manager.js +140 -0
- package/lib/production-standards-reader.js +13 -2
- package/lib/production-standards-writer.js +85 -0
- package/lib/skills/feature-planning/dry-run-validator.js +135 -0
- package/lib/skills/feature-planning/validation-formatter.js +160 -0
- package/lib/smart-conflict-detection.js +168 -0
- package/lib/smart-fetch-rebase.js +614 -0
- package/lib/step-definition-parser.js +76 -0
- package/lib/unit-test-generator.js +232 -0
- package/lib/verification-command-generator.js +66 -0
- package/lib/worktree-diagnostics.js +413 -0
- package/lib/worktree-facade.js +174 -0
- package/lib/worktree-manager.js +636 -0
- package/lib/worktree-reconciler.js +429 -0
- package/package.json +30 -3
- package/skills-templates/external-transition/SKILL.md +34 -3
- package/skills-templates/feature-planning/SKILL.md +190 -24
- package/skills-templates/production-mode/SKILL.md +127 -9
- package/skills-templates/speed-mode/SKILL.md +454 -51
- package/skills-templates/stable-mode/SKILL.md +285 -76
- package/.claude/PROTECT_SKILLS.md +0 -28
- package/.claude/settings.json +0 -24
- package/.claude/settings.local.json +0 -16
- package/.claude/skills/epic-planning/SKILL.md +0 -297
- package/.claude/skills/external-transition/SKILL.md +0 -384
- package/.claude/skills/feature-planning/SKILL.md +0 -464
- package/.claude/skills/production-mode/SKILL.md +0 -369
- package/.claude/skills/speed-mode/SKILL.md +0 -481
- package/.claude/skills/stable-mode/SKILL.md +0 -713
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-10T23-33-09-368Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T16-15-10-070Z/epic-discover/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T16-42-43-212Z/stable-mode/SKILL.md +0 -673
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/epic-planning/SKILL.md +0 -297
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/feature-planning/SKILL.md +0 -464
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/speed-mode/SKILL.md +0 -467
- package/.claude/skills.backup-2025-11-11T17-06-09-783Z/stable-mode/SKILL.md +0 -673
- package/.devpod/current-work.json +0 -10
- package/.devpod/work.db +0 -0
- package/.github/workflows/test-safety.yml +0 -85
- package/.jettypod/config.json +0 -5
- package/.jettypod/current-work.json +0 -10
- package/.jettypod/hooks/README.md +0 -77
- package/.jettypod/hooks/protect-claude-md.js +0 -338
- package/.jettypod/test-work.db +0 -0
- package/.jettypod/work.db +0 -0
- package/CLAUDE.md +0 -49
- package/SPEED-STABLE-AUDIT.md +0 -853
- package/SYSTEM-BEHAVIOR.md +0 -2199
- package/TEST_SAFETY_AUDIT.md +0 -314
- package/TEST_SAFETY_IMPLEMENTATION.md +0 -97
- package/cucumber-report.html +0 -45
- package/dist/devpod-linux +0 -0
- package/dist/devpod-macos +0 -0
- package/dist/devpod-win.exe +0 -0
- package/docs/features/jettypod-standards-explained.md +0 -543
- package/docs/features/standards-inventory.md +0 -257
- package/features/auto-generate-production-chores.feature +0 -13
- package/features/backlog-command.feature +0 -26
- package/features/backlog-filtering-production.feature +0 -10
- package/features/claude-md-protection/steps.js +0 -498
- package/features/decisions/index.js +0 -490
- package/features/decisions/index.test.js +0 -208
- package/features/fix-text-wrapping.feature +0 -42
- package/features/git-hooks/git-hooks.feature +0 -30
- package/features/git-hooks/index.js +0 -93
- package/features/git-hooks/index.test.js +0 -137
- package/features/git-hooks/post-commit +0 -56
- package/features/git-hooks/post-merge +0 -47
- package/features/git-hooks/pre-commit +0 -28
- package/features/git-hooks/simple-steps.js +0 -53
- package/features/git-hooks/simple-test.feature +0 -10
- package/features/git-hooks/steps.js +0 -196
- package/features/jettypod-update-command.feature +0 -46
- package/features/mode-prompts/index.js +0 -95
- package/features/mode-prompts/simple-steps.js +0 -44
- package/features/mode-prompts/simple-test.feature +0 -9
- package/features/mode-prompts/validation.test.js +0 -120
- package/features/multiple-claude-instances.feature +0 -121
- package/features/production-mode-skill.feature +0 -121
- package/features/refactor-mode/steps.js +0 -217
- package/features/refactor-mode.feature +0 -49
- package/features/simplify-external-transition.feature +0 -166
- package/features/skills-update/index.test.js +0 -216
- package/features/step_definitions/backlog-command.steps.js +0 -37
- package/features/step_definitions/fix-text-wrapping.steps.js +0 -271
- package/features/step_definitions/multiple-claude-instances.steps.js +0 -621
- package/features/step_definitions/production-mode-skill.steps.js +0 -862
- package/features/step_definitions/simplify-external-transition.steps.js +0 -370
- package/features/step_definitions/terminal-logo.steps.js +0 -145
- package/features/step_definitions/update-command.steps.js +0 -183
- package/features/support/hooks.js +0 -9
- package/features/terminal-logo/index.js +0 -39
- package/features/terminal-logo/terminal-logo.feature +0 -30
- package/features/update-command/index.js +0 -181
- package/features/update-command/index.test.js +0 -225
- package/features/work-commands/bug-workflow-display.feature +0 -22
- package/features/work-commands/index.js +0 -498
- package/features/work-commands/simple-steps.js +0 -69
- package/features/work-commands/stable-tests.feature +0 -57
- package/features/work-commands/steps.js +0 -1174
- package/features/work-commands/validation.test.js +0 -88
- package/features/work-commands/work-commands.feature +0 -13
- package/features/work-tracking/discovery-validation.test.js +0 -228
- package/features/work-tracking/index.js +0 -1921
- package/features/work-tracking/mode-required.feature +0 -112
- package/features/work-tracking/phase-tracking.test.js +0 -482
- package/features/work-tracking/prototype-tracking.test.js +0 -485
- package/features/work-tracking/tree-view.test.js +0 -310
- package/features/work-tracking/work-set-mode.feature +0 -71
- package/features/work-tracking/work-start-mode.feature +0 -88
- package/full-test.txt +0 -0
- package/lib/bug-workflow.test.js +0 -177
- package/lib/claudemd.test.js +0 -195
- package/lib/config.test.js +0 -511
- package/lib/constants.test.js +0 -164
- package/lib/current-work.test.js +0 -146
- package/lib/database-project-config.test.js +0 -111
- package/lib/database.test.js +0 -106
- package/lib/decisions-generator.test.js +0 -457
- package/lib/decisions-helpers.test.js +0 -310
- package/lib/git-coordinator.js +0 -167
- package/lib/git.test.js +0 -145
- package/lib/migrations/002-default-work-item-modes.test.js +0 -351
- package/lib/production-chore-generator.test.js +0 -432
- package/lib/production-context-detector.test.js +0 -277
- package/lib/production-scenario-appender.test.js +0 -235
- package/lib/production-scenario-validator.test.js +0 -246
- package/lib/production-standards-reader.test.js +0 -270
- package/lib/project-state.test.js +0 -92
- package/lib/push-queue.js +0 -417
- package/lib/queue-processor.js +0 -74
- package/lib/test-helpers.js +0 -202
- package/lib/test-helpers.test.js +0 -255
- package/prototypes/2025-01-11-production-mode-autonomous.js +0 -119
- package/prototypes/2025-01-11-production-mode-collaborative.js +0 -166
- package/prototypes/2025-01-11-production-mode-guided.js +0 -217
- package/prototypes/2025-01-11-production-mode-smart-context.js +0 -347
- package/prototypes/2025-01-11-production-standards-example.md +0 -204
- package/prototypes/2025-11-10-backlog-filtering-tree-aware.js +0 -242
- package/prototypes/test/index.html +0 -1
- package/setup-dist-repo.sh +0 -68
- package/test-production-standards-engine.js +0 -130
- package/test-results.json +0 -2195
- package/test-safety-check.sh +0 -80
- package/work-item-tracking-plan.md +0 -199
- /package/{.jettypod/devpod.db → jettypod.db} +0 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree Reconciler - State Reconciliation Engine
|
|
3
|
+
*
|
|
4
|
+
* This module detects and fixes inconsistencies between database, git, and
|
|
5
|
+
* filesystem state. It's the "self-healing" layer that ensures state never
|
|
6
|
+
* gets permanently corrupted.
|
|
7
|
+
*
|
|
8
|
+
* Four reconciliation scenarios:
|
|
9
|
+
* 1. Orphaned worktrees (git knows, DB doesn't)
|
|
10
|
+
* 2. Orphaned branches (branch exists, no worktree)
|
|
11
|
+
* 3. Stale database entries (DB has path, filesystem doesn't)
|
|
12
|
+
* 4. Git-filesystem mismatch (filesystem has dir, git doesn't know it)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reconcile all worktree state
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} db - SQLite database connection
|
|
23
|
+
* @param {string} repoPath - Path to main git repository
|
|
24
|
+
* @param {Object} options - Reconciliation options
|
|
25
|
+
* @param {boolean} options.cleanup - Whether to attempt cleanup (default: false)
|
|
26
|
+
* @returns {Promise<Object>} Reconciliation results with detected issues and cleanup attempts
|
|
27
|
+
*/
|
|
28
|
+
async function reconcileState(db, repoPath, options = {}) {
|
|
29
|
+
const cleanup = options.cleanup || false;
|
|
30
|
+
|
|
31
|
+
const results = {
|
|
32
|
+
orphanedWorktrees: [],
|
|
33
|
+
orphanedBranches: [],
|
|
34
|
+
staleDbEntries: [],
|
|
35
|
+
gitFilesystemMismatches: [],
|
|
36
|
+
cleanupAttempts: {
|
|
37
|
+
orphanedWorktrees: 0,
|
|
38
|
+
orphanedBranches: 0,
|
|
39
|
+
staleDbEntries: 0,
|
|
40
|
+
gitFilesystemMismatches: 0
|
|
41
|
+
},
|
|
42
|
+
errors: [],
|
|
43
|
+
warnings: [],
|
|
44
|
+
gitCommands: []
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Scenario 1: Orphaned worktrees (git knows, DB doesn't)
|
|
48
|
+
try {
|
|
49
|
+
const orphanedResults = await detectOrphanedWorktrees(db, repoPath);
|
|
50
|
+
results.orphanedWorktrees = orphanedResults;
|
|
51
|
+
|
|
52
|
+
if (cleanup && orphanedResults.length > 0) {
|
|
53
|
+
for (const orphaned of orphanedResults) {
|
|
54
|
+
try {
|
|
55
|
+
await cleanupOrphanedWorktree(orphaned.path, repoPath);
|
|
56
|
+
results.cleanupAttempts.orphanedWorktrees++;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
results.errors.push(`Failed to cleanup orphaned worktree ${orphaned.path}: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
results.errors.push(`Orphaned worktree detection failed: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Scenario 2: Orphaned branches (branch exists, no worktree)
|
|
67
|
+
try {
|
|
68
|
+
const orphanedBranches = await detectOrphanedBranches(db, repoPath);
|
|
69
|
+
results.orphanedBranches = orphanedBranches;
|
|
70
|
+
|
|
71
|
+
if (cleanup && orphanedBranches.length > 0) {
|
|
72
|
+
for (const branch of orphanedBranches) {
|
|
73
|
+
try {
|
|
74
|
+
const hasCommits = await branchHasCommits(branch.name, repoPath);
|
|
75
|
+
if (!hasCommits) {
|
|
76
|
+
await cleanupOrphanedBranch(branch.name, repoPath);
|
|
77
|
+
results.cleanupAttempts.orphanedBranches++;
|
|
78
|
+
} else {
|
|
79
|
+
results.warnings.push(`Branch ${branch.name} has commits - preserving it`);
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
results.errors.push(`Failed to cleanup orphaned branch ${branch.name}: ${err.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
results.errors.push(`Orphaned branch detection failed: ${err.message}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Scenario 3: Stale database entries (DB has path, filesystem doesn't)
|
|
91
|
+
try {
|
|
92
|
+
const staleEntries = await detectStaleDbEntries(db, repoPath);
|
|
93
|
+
results.staleDbEntries = staleEntries;
|
|
94
|
+
|
|
95
|
+
if (cleanup && staleEntries.length > 0) {
|
|
96
|
+
for (const entry of staleEntries) {
|
|
97
|
+
try {
|
|
98
|
+
await markWorktreeCorrupted(db, entry.id);
|
|
99
|
+
results.cleanupAttempts.staleDbEntries++;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
results.errors.push(`Failed to mark stale entry as corrupted: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Run git worktree prune
|
|
106
|
+
try {
|
|
107
|
+
execSync('git worktree prune', { cwd: repoPath, stdio: 'pipe' });
|
|
108
|
+
results.gitCommands.push('git worktree prune');
|
|
109
|
+
} catch (err) {
|
|
110
|
+
results.errors.push(`Failed to prune git worktrees: ${err.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
results.errors.push(`Stale database entry detection failed: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Scenario 4: Git-filesystem mismatch (filesystem has dir, git doesn't)
|
|
118
|
+
try {
|
|
119
|
+
const mismatches = await detectGitFilesystemMismatches(db, repoPath);
|
|
120
|
+
results.gitFilesystemMismatches = mismatches;
|
|
121
|
+
|
|
122
|
+
if (cleanup && mismatches.length > 0) {
|
|
123
|
+
for (const mismatch of mismatches) {
|
|
124
|
+
try {
|
|
125
|
+
await reregisterWorktree(mismatch, repoPath);
|
|
126
|
+
await updateWorktreeTimestamp(db, mismatch.id);
|
|
127
|
+
results.cleanupAttempts.gitFilesystemMismatches++;
|
|
128
|
+
results.gitCommands.push(`git worktree add --force`);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
await markWorktreeCorrupted(db, mismatch.id);
|
|
131
|
+
results.errors.push(`Failed to re-register worktree ${mismatch.path}: ${err.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
results.errors.push(`Git-filesystem mismatch detection failed: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Detect worktrees that git knows about but database doesn't track
|
|
144
|
+
*/
|
|
145
|
+
async function detectOrphanedWorktrees(db, repoPath) {
|
|
146
|
+
const orphaned = [];
|
|
147
|
+
|
|
148
|
+
// Get all git worktrees
|
|
149
|
+
const gitWorktrees = getGitWorktrees(repoPath);
|
|
150
|
+
|
|
151
|
+
// Check each against database
|
|
152
|
+
for (const worktree of gitWorktrees) {
|
|
153
|
+
const dbEntry = await new Promise((resolve, reject) => {
|
|
154
|
+
db.get('SELECT * FROM worktrees WHERE worktree_path = ?', [worktree.path], (err, row) => {
|
|
155
|
+
if (err) reject(err);
|
|
156
|
+
else resolve(row);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!dbEntry) {
|
|
161
|
+
orphaned.push({
|
|
162
|
+
path: worktree.path,
|
|
163
|
+
branch: worktree.branch
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return orphaned;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get all git worktrees from git worktree list
|
|
173
|
+
*/
|
|
174
|
+
function getGitWorktrees(repoPath) {
|
|
175
|
+
try {
|
|
176
|
+
const output = execSync('git worktree list --porcelain', {
|
|
177
|
+
cwd: repoPath,
|
|
178
|
+
encoding: 'utf8',
|
|
179
|
+
stdio: 'pipe'
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const worktrees = [];
|
|
183
|
+
const lines = output.split('\n');
|
|
184
|
+
let currentWorktree = {};
|
|
185
|
+
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
if (line.startsWith('worktree ')) {
|
|
188
|
+
if (currentWorktree.path) {
|
|
189
|
+
worktrees.push(currentWorktree);
|
|
190
|
+
}
|
|
191
|
+
currentWorktree = { path: line.substring(9) };
|
|
192
|
+
} else if (line.startsWith('branch ')) {
|
|
193
|
+
currentWorktree.branch = line.substring(7).replace('refs/heads/', '');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (currentWorktree.path) {
|
|
198
|
+
worktrees.push(currentWorktree);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Normalize paths to resolve symlinks (e.g., /var -> /private/var on macOS)
|
|
202
|
+
const normalizedRepoPath = fs.existsSync(repoPath) ? fs.realpathSync(repoPath) : repoPath;
|
|
203
|
+
|
|
204
|
+
// Filter out main repository (not a worktree)
|
|
205
|
+
return worktrees.filter(w => {
|
|
206
|
+
const normalizedWorktreePath = fs.existsSync(w.path) ? fs.realpathSync(w.path) : w.path;
|
|
207
|
+
return normalizedWorktreePath !== normalizedRepoPath;
|
|
208
|
+
});
|
|
209
|
+
} catch (err) {
|
|
210
|
+
// If git worktree list fails, return empty array
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Cleanup orphaned worktree
|
|
217
|
+
*/
|
|
218
|
+
async function cleanupOrphanedWorktree(worktreePath, repoPath) {
|
|
219
|
+
try {
|
|
220
|
+
execSync(`git worktree remove --force "${worktreePath}"`, {
|
|
221
|
+
cwd: repoPath,
|
|
222
|
+
stdio: 'pipe'
|
|
223
|
+
});
|
|
224
|
+
} catch (err) {
|
|
225
|
+
throw new Error(`Failed to remove worktree: ${err.message}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Detect feature branches that have no worktree
|
|
231
|
+
*/
|
|
232
|
+
async function detectOrphanedBranches(db, repoPath) {
|
|
233
|
+
const orphaned = [];
|
|
234
|
+
|
|
235
|
+
// Get all feature branches with work-* pattern
|
|
236
|
+
const branches = getFeatureBranches(repoPath);
|
|
237
|
+
|
|
238
|
+
// Get all tracked worktrees from database
|
|
239
|
+
const trackedBranches = await new Promise((resolve, reject) => {
|
|
240
|
+
db.all('SELECT branch_name FROM worktrees', (err, rows) => {
|
|
241
|
+
if (err) reject(err);
|
|
242
|
+
else resolve(rows.map(r => r.branch_name));
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Check each branch
|
|
247
|
+
for (const branchName of branches) {
|
|
248
|
+
if (!trackedBranches.includes(branchName)) {
|
|
249
|
+
// Extract work item ID from branch name (e.g., feature/work-123-title)
|
|
250
|
+
const match = branchName.match(/work-(\d+)-/);
|
|
251
|
+
const workItemId = match ? parseInt(match[1]) : null;
|
|
252
|
+
|
|
253
|
+
orphaned.push({
|
|
254
|
+
name: branchName,
|
|
255
|
+
workItemId
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return orphaned;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get all feature branches with work-* pattern
|
|
265
|
+
*/
|
|
266
|
+
function getFeatureBranches(repoPath) {
|
|
267
|
+
try {
|
|
268
|
+
const output = execSync('git branch', {
|
|
269
|
+
cwd: repoPath,
|
|
270
|
+
encoding: 'utf8',
|
|
271
|
+
stdio: 'pipe'
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return output
|
|
275
|
+
.split('\n')
|
|
276
|
+
.map(line => line.trim().replace('* ', ''))
|
|
277
|
+
.filter(branch => branch.startsWith('feature/work-'));
|
|
278
|
+
} catch (err) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Check if branch has commits beyond main
|
|
285
|
+
*/
|
|
286
|
+
async function branchHasCommits(branchName, repoPath) {
|
|
287
|
+
try {
|
|
288
|
+
const output = execSync(`git rev-list --count main..${branchName}`, {
|
|
289
|
+
cwd: repoPath,
|
|
290
|
+
encoding: 'utf8',
|
|
291
|
+
stdio: 'pipe'
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const commitCount = parseInt(output.trim());
|
|
295
|
+
return commitCount > 0;
|
|
296
|
+
} catch (err) {
|
|
297
|
+
// If command fails, assume it has commits (safer to preserve)
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Cleanup orphaned branch
|
|
304
|
+
*/
|
|
305
|
+
async function cleanupOrphanedBranch(branchName, repoPath) {
|
|
306
|
+
try {
|
|
307
|
+
execSync(`git branch -D ${branchName}`, {
|
|
308
|
+
cwd: repoPath,
|
|
309
|
+
stdio: 'pipe'
|
|
310
|
+
});
|
|
311
|
+
} catch (err) {
|
|
312
|
+
throw new Error(`Failed to delete branch: ${err.message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Detect database entries where filesystem path doesn't exist
|
|
318
|
+
*/
|
|
319
|
+
async function detectStaleDbEntries(db, repoPath) {
|
|
320
|
+
const stale = [];
|
|
321
|
+
|
|
322
|
+
// Get all worktrees from database
|
|
323
|
+
const dbEntries = await new Promise((resolve, reject) => {
|
|
324
|
+
db.all('SELECT * FROM worktrees', (err, rows) => {
|
|
325
|
+
if (err) reject(err);
|
|
326
|
+
else resolve(rows);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Check each entry
|
|
331
|
+
for (const entry of dbEntries) {
|
|
332
|
+
if (!fs.existsSync(entry.worktree_path)) {
|
|
333
|
+
stale.push({
|
|
334
|
+
id: entry.id,
|
|
335
|
+
path: entry.worktree_path,
|
|
336
|
+
workItemId: entry.work_item_id
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return stale;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Mark worktree as corrupted in database
|
|
346
|
+
*/
|
|
347
|
+
async function markWorktreeCorrupted(db, worktreeId) {
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
db.run(
|
|
350
|
+
'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
|
|
351
|
+
['corrupted', worktreeId],
|
|
352
|
+
(err) => {
|
|
353
|
+
if (err) reject(err);
|
|
354
|
+
else resolve();
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Detect directories that exist but git doesn't track
|
|
362
|
+
*/
|
|
363
|
+
async function detectGitFilesystemMismatches(db, repoPath) {
|
|
364
|
+
const mismatches = [];
|
|
365
|
+
|
|
366
|
+
// Get all worktrees from database
|
|
367
|
+
const dbEntries = await new Promise((resolve, reject) => {
|
|
368
|
+
db.all('SELECT * FROM worktrees', (err, rows) => {
|
|
369
|
+
if (err) reject(err);
|
|
370
|
+
else resolve(rows);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Get all git worktrees
|
|
375
|
+
const gitWorktrees = getGitWorktrees(repoPath);
|
|
376
|
+
const gitPaths = gitWorktrees.map(w => w.path);
|
|
377
|
+
|
|
378
|
+
// Check each database entry
|
|
379
|
+
for (const entry of dbEntries) {
|
|
380
|
+
const dirExists = fs.existsSync(entry.worktree_path);
|
|
381
|
+
const gitKnows = gitPaths.includes(entry.worktree_path);
|
|
382
|
+
|
|
383
|
+
if (dirExists && !gitKnows) {
|
|
384
|
+
mismatches.push({
|
|
385
|
+
id: entry.id,
|
|
386
|
+
path: entry.worktree_path,
|
|
387
|
+
branchName: entry.branch_name,
|
|
388
|
+
workItemId: entry.work_item_id
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return mismatches;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Re-register worktree directory with git
|
|
398
|
+
*/
|
|
399
|
+
async function reregisterWorktree(mismatch, repoPath) {
|
|
400
|
+
try {
|
|
401
|
+
// Try to re-register the worktree with git
|
|
402
|
+
execSync(`git worktree add --force -b ${mismatch.branchName} "${mismatch.path}"`, {
|
|
403
|
+
cwd: repoPath,
|
|
404
|
+
stdio: 'pipe'
|
|
405
|
+
});
|
|
406
|
+
} catch (err) {
|
|
407
|
+
throw new Error(`Failed to re-register worktree: ${err.message}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Update worktree timestamp in database
|
|
413
|
+
*/
|
|
414
|
+
async function updateWorktreeTimestamp(db, worktreeId) {
|
|
415
|
+
return new Promise((resolve, reject) => {
|
|
416
|
+
db.run(
|
|
417
|
+
'UPDATE worktrees SET updated_at = datetime(\'now\') WHERE id = ?',
|
|
418
|
+
[worktreeId],
|
|
419
|
+
(err) => {
|
|
420
|
+
if (err) reject(err);
|
|
421
|
+
else resolve();
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = {
|
|
428
|
+
reconcileState
|
|
429
|
+
};
|
package/package.json
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jettypod",
|
|
3
|
-
"version": "4.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.1.4",
|
|
4
|
+
"description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
|
|
5
5
|
"main": "jettypod.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"jettypod": "./jettypod.js"
|
|
8
8
|
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"workflow",
|
|
11
|
+
"tdd",
|
|
12
|
+
"bdd",
|
|
13
|
+
"testing",
|
|
14
|
+
"ai-development",
|
|
15
|
+
"cucumber",
|
|
16
|
+
"jest",
|
|
17
|
+
"development-tools",
|
|
18
|
+
"test-automation",
|
|
19
|
+
"project-management"
|
|
20
|
+
],
|
|
21
|
+
"author": "Erik Spangenberg",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/spangbaryn/jettypod-source.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/spangbaryn/jettypod-source/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/spangbaryn/jettypod-source#readme",
|
|
9
31
|
"scripts": {
|
|
10
32
|
"test": "npm run test:unit && npm run test:bdd && npm run test:cleanup",
|
|
11
33
|
"test:bdd": "NODE_ENV=test cucumber-js",
|
|
12
34
|
"test:unit": "NODE_ENV=test jest",
|
|
13
35
|
"test:unit:watch": "NODE_ENV=test jest --watch",
|
|
14
|
-
"test:cleanup": "node
|
|
36
|
+
"test:cleanup": "node scripts/test-cleanup.js",
|
|
37
|
+
"prepublishOnly": "echo '🔄 Syncing skills to templates...' && cp -r .claude/skills/* skills-templates/ && echo '✅ Skills synced'"
|
|
15
38
|
},
|
|
16
39
|
"devDependencies": {
|
|
17
40
|
"@cucumber/cucumber": "^10.0.1",
|
|
@@ -31,6 +54,10 @@
|
|
|
31
54
|
"coverageDirectory": "coverage"
|
|
32
55
|
},
|
|
33
56
|
"dependencies": {
|
|
57
|
+
"chalk": "^4.1.2",
|
|
34
58
|
"sqlite3": "^5.1.7"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": "22.x"
|
|
35
62
|
}
|
|
36
63
|
}
|
|
@@ -200,7 +200,29 @@ After user selects preset, ask 3-5 refinement questions to tailor standards:
|
|
|
200
200
|
|
|
201
201
|
### Step 5: Generate and Show Recommendations
|
|
202
202
|
|
|
203
|
-
After collecting answers, use the `lib/production-standards-engine.js` to generate tailored standards
|
|
203
|
+
After collecting answers, use the `lib/production-standards-engine.js` to generate tailored standards.
|
|
204
|
+
|
|
205
|
+
**Execute this code:**
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
const { generateStandards } = require('../../lib/production-standards-engine');
|
|
209
|
+
|
|
210
|
+
// Build input from collected answers
|
|
211
|
+
const input = {
|
|
212
|
+
preset: '[user-selected-preset]', // startup-mvp, production-saas, enterprise, or regulated
|
|
213
|
+
refinement: {
|
|
214
|
+
user_count: '[answer-from-question-2]', // e.g., '100-1k'
|
|
215
|
+
downtime_impact: '[answer-from-question-3]', // e.g., 'Users annoyed'
|
|
216
|
+
data_loss_impact: '[answer-from-question-4]', // e.g., 'Annoying'
|
|
217
|
+
sensitive_data: '[answer-from-question-5]' // e.g., 'Passwords only'
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Generate standards
|
|
222
|
+
const generatedStandards = generateStandards(input);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Then show recommendations to the user:
|
|
204
226
|
|
|
205
227
|
```
|
|
206
228
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
@@ -279,9 +301,18 @@ Allow user to toggle/adjust standards, then regenerate the summary.
|
|
|
279
301
|
|
|
280
302
|
### Step 7: Save Production Standards
|
|
281
303
|
|
|
282
|
-
After user accepts, save standards to `.jettypod/production-standards.json
|
|
304
|
+
After user accepts, save standards to `.jettypod/production-standards.json`.
|
|
305
|
+
|
|
306
|
+
**Execute this code:**
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
const { saveStandards } = require('../../lib/production-standards-writer');
|
|
310
|
+
|
|
311
|
+
// Save the generated standards from Step 5
|
|
312
|
+
saveStandards(generatedStandards);
|
|
313
|
+
```
|
|
283
314
|
|
|
284
|
-
|
|
315
|
+
Then confirm to user:
|
|
285
316
|
|
|
286
317
|
```
|
|
287
318
|
✅ Production standards saved to .jettypod/production-standards.json
|