jettypod 4.4.118 โ†’ 4.4.121

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 (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -99,7 +99,7 @@ async function runDiagnostics(db, repoPath, options = {}) {
99
99
  count: reconcileResults.staleDbEntries.length,
100
100
  message: `Found ${reconcileResults.staleDbEntries.length} database entries with missing filesystem directories`,
101
101
  details: reconcileResults.staleDbEntries,
102
- recommendation: 'Run reconciliation with cleanup=true to mark as corrupted'
102
+ recommendation: 'Run reconciliation with cleanup=true to mark as cleaned'
103
103
  });
104
104
  }
105
105
 
@@ -192,23 +192,23 @@ async function runDiagnostics(db, repoPath, options = {}) {
192
192
  async function checkDatabaseIntegrity(db) {
193
193
  const issues = [];
194
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) => {
195
+ // Check for cleaned worktrees (terminal state โ€” informational only)
196
+ const cleanedCount = await new Promise((resolve, reject) => {
197
+ db.get('SELECT COUNT(*) as count FROM worktrees WHERE status = ?', ['cleaned'], (err, row) => {
198
198
  if (err) reject(err);
199
199
  else resolve(row.count);
200
200
  });
201
201
  });
202
202
 
203
- if (corruptedCount > 0) {
203
+ if (cleanedCount > 0) {
204
204
  issues.push({
205
205
  severity: SEVERITY.INFO,
206
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'
207
+ type: 'cleaned_worktrees',
208
+ count: cleanedCount,
209
+ message: `Found ${cleanedCount} cleaned worktree(s) in database`,
210
+ details: { count: cleanedCount },
211
+ recommendation: 'These are historical records โ€” no action needed'
212
212
  });
213
213
  }
214
214
 
@@ -232,7 +232,7 @@ async function checkDatabaseIntegrity(db) {
232
232
  count: orphanedWorktrees.length,
233
233
  message: `Found ${orphanedWorktrees.length} worktree(s) with missing work items`,
234
234
  details: orphanedWorktrees,
235
- recommendation: 'Mark these worktrees as corrupted'
235
+ recommendation: 'Mark these worktrees as cleaned'
236
236
  });
237
237
  }
238
238
 
@@ -265,7 +265,7 @@ async function checkFilesystemHealth(db, repoPath) {
265
265
  type: 'missing_worktree_directory',
266
266
  message: `Active worktree directory missing: ${worktreePath}`,
267
267
  details: { worktree },
268
- recommendation: 'Mark worktree as corrupted and recreate if needed'
268
+ recommendation: 'Mark worktree as cleaned and recreate if needed'
269
269
  });
270
270
  continue;
271
271
  }
@@ -293,7 +293,7 @@ async function checkFilesystemHealth(db, repoPath) {
293
293
  type: 'permission_issue',
294
294
  message: `Cannot read/write worktree directory: ${worktreePath}`,
295
295
  details: { worktree, error: err.message },
296
- recommendation: 'Fix directory permissions or mark worktree as corrupted'
296
+ recommendation: 'Fix directory permissions or mark worktree as cleaned'
297
297
  });
298
298
  }
299
299
  }
@@ -344,11 +344,11 @@ function generateRecommendations(report) {
344
344
  });
345
345
  }
346
346
 
347
- if (report.issues.some(i => i.type === 'corrupted_worktrees')) {
347
+ if (report.issues.some(i => i.type === 'cleaned_worktrees')) {
348
348
  recommendations.push({
349
349
  priority: 'low',
350
- action: 'Clean up corrupted worktree entries from database',
351
- reason: 'Keeping database clean improves query performance'
350
+ action: 'Cleaned worktree entries are historical records',
351
+ reason: 'No action needed โ€” these are part of the audit trail'
352
352
  });
353
353
  }
354
354
 
@@ -117,7 +117,7 @@ async function startWork(workItem, options = {}) {
117
117
  /**
118
118
  * Stop work on a work item with graceful degradation
119
119
  *
120
- * Attempts to cleanup worktree. If that fails, marks as corrupted and continues.
120
+ * Attempts to cleanup worktree. If that fails, marks as cleaned and continues.
121
121
  *
122
122
  * @param {number} worktreeId - Worktree ID (can be null if working in main)
123
123
  * @param {Object} options - Optional configuration
@@ -21,7 +21,7 @@ const config = require('./config');
21
21
  /**
22
22
  * Valid worktree statuses
23
23
  */
24
- const VALID_STATUSES = ['active', 'merging', 'cleanup_pending', 'corrupted'];
24
+ const VALID_STATUSES = ['active', 'merging', 'cleanup_pending', 'cleaned'];
25
25
 
26
26
  /**
27
27
  * Create a worktree for a work item
@@ -352,7 +352,7 @@ async function getAllActiveWorktrees(options = {}) {
352
352
  * Mark worktree status
353
353
  *
354
354
  * @param {number} worktreeId - Worktree ID
355
- * @param {string} status - New status (active|merging|cleanup_pending|corrupted)
355
+ * @param {string} status - New status (active|merging|cleanup_pending|cleaned)
356
356
  * @param {Object} options - Optional configuration
357
357
  * @param {Object} options.db - Database connection (defaults to global database)
358
358
  * @returns {Promise<void>}
@@ -671,13 +671,13 @@ async function cleanupWorktree(worktreeId, options = {}) {
671
671
  }
672
672
  }
673
673
 
674
- // Step 5: Mark as corrupted (we never delete DB entries)
675
- // Using 'corrupted' as the final state for cleaned up worktrees
674
+ // Step 5: Mark as cleaned (we never delete DB entries)
675
+ // Using 'cleaned' as the final state for cleaned up worktrees
676
676
  // This preserves the audit trail
677
677
  await new Promise((resolve, reject) => {
678
678
  db.run(
679
679
  'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
680
- ['corrupted', worktreeId],
680
+ ['cleaned', worktreeId],
681
681
  (err) => {
682
682
  if (err) reject(err);
683
683
  else resolve();
@@ -686,12 +686,12 @@ async function cleanupWorktree(worktreeId, options = {}) {
686
686
  });
687
687
 
688
688
  } catch (err) {
689
- // On any failure, mark as corrupted
689
+ // On any failure, mark as cleaned (terminal state)
690
690
  try {
691
691
  await new Promise((resolve, reject) => {
692
692
  db.run(
693
693
  'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
694
- ['corrupted', worktreeId],
694
+ ['cleaned', worktreeId],
695
695
  (err) => {
696
696
  if (err) reject(err);
697
697
  else resolve();
@@ -699,7 +699,7 @@ async function cleanupWorktree(worktreeId, options = {}) {
699
699
  );
700
700
  });
701
701
  } catch (dbErr) {
702
- console.error(`Failed to mark worktree as corrupted: ${dbErr.message}`);
702
+ console.error(`Failed to mark worktree as cleaned: ${dbErr.message}`);
703
703
  }
704
704
 
705
705
  throw new Error(`Worktree cleanup failed: ${err.message}`);
@@ -95,7 +95,7 @@ async function reconcileState(db, repoPath, options = {}) {
95
95
  if (cleanup && staleEntries.length > 0) {
96
96
  for (const entry of staleEntries) {
97
97
  try {
98
- await markWorktreeCorrupted(db, entry.id);
98
+ await markWorktreeCleaned(db, entry.id);
99
99
  results.cleanupAttempts.staleDbEntries++;
100
100
  } catch (err) {
101
101
  results.errors.push(`Failed to mark stale entry as corrupted: ${err.message}`);
@@ -127,7 +127,7 @@ async function reconcileState(db, repoPath, options = {}) {
127
127
  results.cleanupAttempts.gitFilesystemMismatches++;
128
128
  results.gitCommands.push(`git worktree add --force`);
129
129
  } catch (err) {
130
- await markWorktreeCorrupted(db, mismatch.id);
130
+ await markWorktreeCleaned(db, mismatch.id);
131
131
  results.errors.push(`Failed to re-register worktree ${mismatch.path}: ${err.message}`);
132
132
  }
133
133
  }
@@ -342,13 +342,13 @@ async function detectStaleDbEntries(db, repoPath) {
342
342
  }
343
343
 
344
344
  /**
345
- * Mark worktree as corrupted in database
345
+ * Mark worktree as cleaned in database (terminal state)
346
346
  */
347
- async function markWorktreeCorrupted(db, worktreeId) {
347
+ async function markWorktreeCleaned(db, worktreeId) {
348
348
  return new Promise((resolve, reject) => {
349
349
  db.run(
350
350
  'UPDATE worktrees SET status = ?, updated_at = datetime(\'now\') WHERE id = ?',
351
- ['corrupted', worktreeId],
351
+ ['cleaned', worktreeId],
352
352
  (err) => {
353
353
  if (err) reject(err);
354
354
  else resolve();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.118",
3
+ "version": "4.4.121",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -34,8 +34,10 @@
34
34
  "test:unit": "NODE_ENV=test jest",
35
35
  "test:unit:watch": "NODE_ENV=test jest --watch",
36
36
  "test:cleanup": "node scripts/test-cleanup.js",
37
+ "version": "node -e \"const v=require('./package.json').version; const f='apps/dashboard/package.json'; const p=JSON.parse(require('fs').readFileSync(f)); p.version=v; require('fs').writeFileSync(f, JSON.stringify(p,null,2)+'\\n')\"",
37
38
  "prepublishOnly": "echo '๐Ÿ”„ Syncing skills to templates...' && cp -r .claude/skills/* skills-templates/ && (git diff --quiet skills-templates/ || (git add skills-templates/ && git commit -m 'chore: Sync skills-templates')) && echo 'โœ… Skills synced' && echo '๐Ÿ”จ Building dashboard...' && cd apps/dashboard && npm install && npm run build && echo 'โœ… Dashboard built'",
38
39
  "postpublish": "git push origin main --tags && echo 'โœ… Pushed version commit and tags'",
40
+ "postinstall": "node scripts/postinstall.js",
39
41
  "dashboard": "npm run dev --prefix apps/dashboard",
40
42
  "dashboard:build": "npm run build --prefix apps/dashboard",
41
43
  "dashboard:start": "npm run start --prefix apps/dashboard"
@@ -48,13 +50,18 @@
48
50
  "ws": "^8.18.3"
49
51
  },
50
52
  "jest": {
53
+ "setupFiles": [
54
+ "./jest.setup.js"
55
+ ],
51
56
  "testEnvironment": "node",
52
57
  "testMatch": [
53
58
  "**/__tests__/**/*.js",
54
59
  "**/*.test.js"
55
60
  ],
56
61
  "testPathIgnorePatterns": [
57
- "/node_modules/"
62
+ "/node_modules/",
63
+ "/apps/dashboard/dist/",
64
+ "/.jettypod-work/"
58
65
  ],
59
66
  "collectCoverage": false,
60
67
  "coverageDirectory": "coverage"
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ // Convert cucumber ndjson (message format) to standard cucumber JSON format.
3
+ // Usage: node scripts/ndjson-to-cucumber-json.js [input.ndjson] [output.json]
4
+ // Defaults: cucumber-results.ndjson -> cucumber-results.json
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ function convert(ndjsonPath, jsonPath) {
10
+ const raw = fs.readFileSync(ndjsonPath, 'utf-8');
11
+ const lines = raw.split('\n').filter(l => l.trim());
12
+
13
+ // Lookup tables
14
+ const gherkinDocuments = new Map(); // uri -> gherkinDocument
15
+ const pickles = new Map(); // pickle.id -> pickle
16
+ const testCases = new Map(); // testCase.id -> testCase
17
+ const testCaseStartedMap = new Map(); // testCaseStarted.id -> testCaseStarted
18
+ const stepDefinitions = new Map(); // stepDefinition.id -> stepDefinition
19
+
20
+ // Results
21
+ const testStepResults = new Map(); // testCaseStartedId -> [{stepId, result}]
22
+
23
+ for (const line of lines) {
24
+ let msg;
25
+ try {
26
+ msg = JSON.parse(line);
27
+ } catch {
28
+ continue;
29
+ }
30
+
31
+ if (msg.gherkinDocument) {
32
+ gherkinDocuments.set(msg.gherkinDocument.uri, msg.gherkinDocument);
33
+ }
34
+ if (msg.pickle) {
35
+ pickles.set(msg.pickle.id, msg.pickle);
36
+ }
37
+ if (msg.stepDefinition) {
38
+ stepDefinitions.set(msg.stepDefinition.id, msg.stepDefinition);
39
+ }
40
+ if (msg.testCase) {
41
+ testCases.set(msg.testCase.id, msg.testCase);
42
+ }
43
+ if (msg.testCaseStarted) {
44
+ testCaseStartedMap.set(msg.testCaseStarted.id, msg.testCaseStarted);
45
+ testStepResults.set(msg.testCaseStarted.id, []);
46
+ }
47
+ if (msg.testStepFinished) {
48
+ const tsf = msg.testStepFinished;
49
+ const arr = testStepResults.get(tsf.testCaseStartedId);
50
+ if (arr) {
51
+ arr.push({
52
+ testStepId: tsf.testStepId,
53
+ result: tsf.testStepResult,
54
+ });
55
+ }
56
+ }
57
+ }
58
+
59
+ // Build cucumber JSON structure grouped by feature URI
60
+ const featureMap = new Map(); // uri -> { feature data }
61
+
62
+ for (const [tcsId, tcs] of testCaseStartedMap) {
63
+ const testCase = testCases.get(tcs.testCaseId);
64
+ if (!testCase) continue;
65
+ const pickle = pickles.get(testCase.pickleId);
66
+ if (!pickle) continue;
67
+
68
+ const uri = pickle.uri;
69
+ if (!featureMap.has(uri)) {
70
+ const doc = gherkinDocuments.get(uri);
71
+ featureMap.set(uri, {
72
+ uri,
73
+ name: doc?.feature?.name || uri,
74
+ description: doc?.feature?.description || '',
75
+ keyword: doc?.feature?.keyword || 'Feature',
76
+ elements: [],
77
+ });
78
+ }
79
+
80
+ const stepResults = testStepResults.get(tcsId) || [];
81
+ const steps = [];
82
+
83
+ for (const testStep of testCase.testSteps) {
84
+ // Skip hook steps (no pickleStepId)
85
+ if (!testStep.pickleStepId) continue;
86
+
87
+ const pickleStep = pickle.steps.find(s => s.id === testStep.pickleStepId);
88
+ if (!pickleStep) continue;
89
+
90
+ // Find the result for this step
91
+ const stepResult = stepResults.find(r => r.testStepId === testStep.id);
92
+ const result = stepResult?.result;
93
+
94
+ const dur = result?.duration || { seconds: 0, nanos: 0 };
95
+ const durationNanos = (dur.seconds || 0) * 1e9 + (dur.nanos || 0);
96
+
97
+ // Get keyword from gherkin document
98
+ let keyword = '';
99
+ const doc = gherkinDocuments.get(uri);
100
+ if (doc?.feature) {
101
+ for (const child of doc.feature.children || []) {
102
+ const scenario = child.scenario;
103
+ if (!scenario) continue;
104
+ for (const gherkinStep of scenario.steps) {
105
+ if (pickleStep.astNodeIds?.includes(gherkinStep.id)) {
106
+ keyword = gherkinStep.keyword;
107
+ break;
108
+ }
109
+ }
110
+ if (keyword) break;
111
+ }
112
+ }
113
+
114
+ steps.push({
115
+ keyword: keyword || 'Step ',
116
+ name: pickleStep.text || '',
117
+ result: {
118
+ status: (result?.status || 'UNKNOWN').toLowerCase(),
119
+ duration: durationNanos,
120
+ error_message: result?.message || undefined,
121
+ },
122
+ });
123
+ }
124
+
125
+ featureMap.get(uri).elements.push({
126
+ type: 'scenario',
127
+ name: pickle.name,
128
+ steps,
129
+ });
130
+ }
131
+
132
+ const output = Array.from(featureMap.values());
133
+ fs.writeFileSync(jsonPath, JSON.stringify(output, null, 2));
134
+ return output.reduce((sum, f) => sum + f.elements.length, 0);
135
+ }
136
+
137
+ // CLI entry point
138
+ if (require.main === module) {
139
+ const projectRoot = path.resolve(__dirname, '..');
140
+ const ndjsonPath = process.argv[2] || path.join(projectRoot, 'cucumber-results.ndjson');
141
+ const jsonPath = process.argv[3] || path.join(projectRoot, 'cucumber-results.json');
142
+
143
+ if (!fs.existsSync(ndjsonPath)) {
144
+ console.error(`No ndjson file found at ${ndjsonPath}`);
145
+ process.exit(1);
146
+ }
147
+
148
+ const count = convert(ndjsonPath, jsonPath);
149
+ console.log(`Converted ${count} scenarios to ${jsonPath}`);
150
+ }
151
+
152
+ module.exports = { convert };
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Records the source directory on global install so jettypod can auto-update.
4
+ // Writes ~/.jettypod-dev.json with the source path for staleness detection.
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ // Only run for global installs
11
+ if (process.env.npm_config_global !== 'true') process.exit(0);
12
+
13
+ const sourceDir = process.env.INIT_CWD;
14
+ if (!sourceDir) process.exit(0);
15
+
16
+ // Verify source dir looks like a jettypod repo (has jettypod.js)
17
+ if (!fs.existsSync(path.join(sourceDir, 'jettypod.js'))) process.exit(0);
18
+
19
+ try {
20
+ const devInfo = { sourceDir };
21
+ const devFile = path.join(os.homedir(), '.jettypod-dev.json');
22
+ fs.writeFileSync(devFile, JSON.stringify(devInfo, null, 2) + '\n');
23
+ } catch {
24
+ // Non-fatal โ€” skip silently
25
+ }
@@ -44,7 +44,7 @@ When this skill is activated, you are implementing a bug fix directly on the bug
44
44
 
45
45
  Your job: Implement the fix, write a regression test, and verify everything passes.
46
46
 
47
- ## ๐Ÿ”‘ Critical Context
47
+ ## Critical Context
48
48
 
49
49
  **You are working in an isolated git worktree:**
50
50
  - `work start [bug-id]` already created a dedicated worktree
@@ -55,7 +55,7 @@ Your job: Implement the fix, write a regression test, and verify everything pass
55
55
 
56
56
  ---
57
57
 
58
- ## ๐Ÿšจ SHELL CWD RECOVERY
58
+ ## SHELL CWD RECOVERY
59
59
 
60
60
  **If ALL bash commands start failing with "Error: Exit code 1" and no output:**
61
61
 
@@ -69,7 +69,7 @@ Your shell's working directory was likely inside a worktree that was deleted. Th
69
69
 
70
70
  ---
71
71
 
72
- ## ๐Ÿ›‘ PRE-FLIGHT VALIDATION (REQUIRED)
72
+ ## PRE-FLIGHT VALIDATION (REQUIRED)
73
73
 
74
74
  **Before proceeding, validate the worktree exists.**
75
75
 
@@ -81,9 +81,9 @@ sqlite3 .jettypod/work.db "SELECT wi.id, wi.title, wi.status, wt.worktree_path,
81
81
 
82
82
  | worktree_path | What it means | Action |
83
83
  |---------------|---------------|--------|
84
- | **Has a path** | โœ… Worktree exists, ready to proceed | Continue to Step 0 |
85
- | **NULL or empty** | โŒ `work start` was not called | **STOP - run `jettypod work start [bug-id]` first** |
86
- | **No rows returned** | โŒ No bug is in progress | **STOP - verify the bug exists and run `work start`** |
84
+ | **Has a path** | Worktree exists, ready to proceed | Continue to Step 0 |
85
+ | **NULL or empty** | `work start` was not called | **STOP - run `jettypod work start [bug-id]` first** |
86
+ | **No rows returned** | No bug is in progress | **STOP - verify the bug exists and run `work start`** |
87
87
 
88
88
  **After pre-flight passes, emit gate signal:**
89
89
 
@@ -114,7 +114,7 @@ The bug-planning skill embedded implementation guidance:
114
114
 
115
115
  ```
116
116
  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
117
- ๐Ÿ› BUG MODE: Fixing #[bug-id]
117
+ BUG MODE: Fixing #[bug-id]
118
118
  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
119
119
 
120
120
  Bug: #[id] [title]
@@ -176,14 +176,14 @@ sqlite3 .jettypod/work.db "SELECT project_state FROM project_config WHERE id = 1
176
176
 
177
177
  ```
178
178
  โ”โ”โ” Iteration 1 โ”โ”โ”
179
- ๐Ÿ“‚ Reading: [file:line from breadcrumbs]
180
- ๐Ÿ”ง Change: [what you're fixing]
179
+ Reading: [file:line from breadcrumbs]
180
+ Change: [what you're fixing]
181
181
  ```
182
182
 
183
183
  **After implementation:**
184
184
 
185
185
  ```
186
- ๐ŸŽ‰ Fix Implemented
186
+ Fix Implemented
187
187
 
188
188
  Changes made:
189
189
  - [Change 1]
@@ -197,6 +197,16 @@ Now writing regression test...
197
197
 
198
198
  ---
199
199
 
200
+ ### BDD Step Definition Constraints (ENFORCED)
201
+
202
+ Before writing any step definitions, read `templates/bdd-guidance.md`. BANNED in step defs: `setTimeout`/`sleep`, module-level `let`/`var`, raw SQL, assertions in Given/When, loops/branching, self-fulfilling mocks (setting state then asserting it). Steps must be 1-5 lines calling one helper via Cucumber World (`this`). Read `features/support/helpers/` and reuse before creating.
203
+
204
+ **Scaffolding:** If `features/support/helpers/` does not exist in the project, scaffold it before writing step definitions. Copy `templates/bdd-scaffolding/wait.js` to `features/support/helpers/wait.js` and `templates/bdd-scaffolding/world.js` to `features/support/world.js`.
205
+
206
+ **Helper Reuse:** Before writing step definitions, glob `features/support/helpers/**/*.js` and read each file. Import and call existing helpers in your steps โ€” never duplicate functionality that already exists.
207
+
208
+ ---
209
+
200
210
  ### Step 2: Write Regression Test
201
211
 
202
212
  **Goal:** Create a test that verifies the bug is fixed and prevents regression.
@@ -239,7 +249,7 @@ npx cucumber-js features/<bug-slug>.feature
239
249
  **Confirm test passes:**
240
250
 
241
251
  ```
242
- โœ… Regression Test Passing
252
+ Regression Test Passing
243
253
 
244
254
  Test: features/<bug-slug>.feature
245
255
  Status: PASSING
@@ -272,7 +282,7 @@ npx cucumber-js features/<related-feature>.feature 2>/dev/null || true
272
282
  **If regressions found:**
273
283
 
274
284
  ```
275
- โš ๏ธ Regression Detected
285
+ Regression Detected
276
286
 
277
287
  Failing test: [test name]
278
288
  Error: [error message]
@@ -285,14 +295,40 @@ Fix the regression and re-run tests. Iterate until all related tests pass.
285
295
  **When all related tests pass:**
286
296
 
287
297
  ```
288
- โœ… Related Tests Passing
298
+ Related Tests Passing
289
299
 
290
- - Regression test: โœ… PASS
291
- - Related area tests: โœ… [X] scenarios passing
300
+ - Regression test: PASS
301
+ - Related area tests: [X] scenarios passing
292
302
 
293
303
  Ready to commit and merge.
294
304
  ```
295
305
 
306
+ **Proceed to Step 3B.**
307
+
308
+ **Quality Gate:** Before committing, review every `.steps.js` file you wrote or modified. Check for banlist violations: `setTimeout`/`sleep`, module-level `let`/`var`, raw SQL, assertions in Given/When, self-fulfilling mocks, steps longer than 5 lines. Fix violations before proceeding.
309
+
310
+ ---
311
+
312
+ ### Step 3B: Prove It Works
313
+
314
+ **Before committing, demonstrate the fix works in practice โ€” not just that tests pass.**
315
+
316
+ 1. **Run or build the actual code** โ€” compile, start the server, execute the command, or trigger the relevant workflow
317
+ 2. **Show real output** โ€” display console output, build result, or observable behavior
318
+ 3. **Confirm the bug is fixed** โ€” verify the original bug scenario no longer reproduces
319
+
320
+ **Display:**
321
+
322
+ ```
323
+ Verifying fix in practice...
324
+
325
+ [Actual output from running/building the code]
326
+
327
+ Verified: [what was confirmed โ€” original bug no longer reproduces]
328
+ ```
329
+
330
+ **If the change cannot be run directly** (requires hardware, external service, or manual UI interaction), state what the user should verify manually and why you can't verify it yourself.
331
+
296
332
  **Proceed to Step 4.**
297
333
 
298
334
  ---
@@ -315,7 +351,7 @@ Fix: [What was changed]
315
351
 
316
352
  Closes #<bug-id>
317
353
 
318
- ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code)
354
+ Generated with [Claude Code](https://claude.com/claude-code)
319
355
 
320
356
  Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
321
357
  ```
@@ -327,6 +363,8 @@ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
327
363
  jettypod work merge <bug-id>
328
364
  ```
329
365
 
366
+ **If merge fails with `Uncommitted changes detected`:** `git stash` from main repo CWD โ†’ create a work item for the stashed changes โ†’ `jettypod work start <id>` โ†’ `git stash pop` in that worktree โ†’ commit โ†’ merge โ†’ cleanup โ†’ then retry your original merge.
367
+
330
368
  ```bash
331
369
  # Step 2: cd to main repo
332
370
  cd <main-repo-path>
@@ -347,6 +385,22 @@ jettypod work cleanup <bug-id>
347
385
  **The merge command automatically sets the bug as ready for review.**
348
386
  It will appear with accept/reject buttons on the kanban board. Do NOT call `jettypod work status <bug-id> done` โ€” that bypasses the review gate.
349
387
 
388
+ **Generate QA checklist:**
389
+
390
+ Based on the bug fix and regression test, generate specific QA steps to verify the fix and ensure no regressions. Write a JSON array to `/tmp/qa-steps.json`:
391
+ ```json
392
+ [
393
+ {"section": "Bug Fix Verification", "text": "Reproduce the original bug scenario โ€” it should no longer occur", "detail": "Steps to reproduce..."},
394
+ {"section": "Regression Check", "text": "Verify related functionality still works", "detail": "..."},
395
+ ...
396
+ ]
397
+ ```
398
+ Be specific. 3-8 steps covering: original bug fixed, regression scenarios, edge cases.
399
+
400
+ ```bash
401
+ jettypod work set-qa-steps <bug-id> --from=/tmp/qa-steps.json
402
+ ```
403
+
350
404
  **Emit gate signal:**
351
405
 
352
406
  ```bash
@@ -357,11 +411,11 @@ jettypod ui gate complete --data='{"summary":"Bug fixed: [brief description]","f
357
411
 
358
412
  ```
359
413
  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
360
- โœ… Bug Fix Complete
414
+ Bug Fix Complete
361
415
  โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
362
416
 
363
- ๐Ÿ› Bug: #[bug-id] [title]
364
- ๐Ÿงช Regression test: โœ… features/<bug-slug>.feature
417
+ Bug: #[bug-id] [title]
418
+ Regression test: features/<bug-slug>.feature
365
419
 
366
420
  What was fixed:
367
421
  - Root cause: [brief description]
@@ -400,4 +454,9 @@ cd <main-repo> # Change to main repo
400
454
  jettypod work cleanup <bug-id> # Clean up worktree
401
455
  ```
402
456
 
403
- **โš ๏ธ Do NOT mark bug as done manually โ€” the merge command sets the review gate automatically.**
457
+ **Force-cleanup stale worktree** (when merge is blocked or fix was superseded):
458
+ ```bash
459
+ jettypod work cleanup --force <bug-id> # Bypasses merge requirement, prompts for confirmation
460
+ ```
461
+
462
+ **Do NOT mark bug as done manually โ€” the merge command sets the review gate automatically.**