jettypod 4.4.120 → 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 (208) hide show
  1. package/.env +2 -1
  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 +54 -49
  8. package/apps/dashboard/app/demo/gates/page.tsx +3 -5
  9. package/apps/dashboard/app/design-system/page.tsx +1 -1
  10. package/apps/dashboard/app/globals.css +74 -2
  11. package/apps/dashboard/app/install-claude/page.tsx +3 -5
  12. package/apps/dashboard/app/login/page.tsx +17 -20
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +60 -12
  15. package/apps/dashboard/app/signup/page.tsx +14 -17
  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 +12 -15
  19. package/apps/dashboard/app/work/[id]/page.tsx +90 -75
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +70 -61
  22. package/apps/dashboard/components/CardMenu.tsx +0 -1
  23. package/apps/dashboard/components/ClaudePanel.tsx +541 -283
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
  26. package/apps/dashboard/components/CopyableId.tsx +1 -2
  27. package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
  28. package/apps/dashboard/components/DragContext.tsx +132 -62
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +5 -6
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +0 -1
  34. package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
  35. package/apps/dashboard/components/EpicGroup.tsx +100 -70
  36. package/apps/dashboard/components/GateCard.tsx +0 -1
  37. package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
  39. package/apps/dashboard/components/JettyLoader.tsx +0 -1
  40. package/apps/dashboard/components/KanbanBoard.tsx +319 -173
  41. package/apps/dashboard/components/KanbanCard.tsx +341 -107
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
  44. package/apps/dashboard/components/MainNav.tsx +24 -25
  45. package/apps/dashboard/components/MessageBlock.tsx +93 -16
  46. package/apps/dashboard/components/ModeStartCard.tsx +0 -1
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
  48. package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
  53. package/apps/dashboard/components/ReviewFooter.tsx +12 -14
  54. package/apps/dashboard/components/SessionList.tsx +0 -1
  55. package/apps/dashboard/components/SubscribeContent.tsx +40 -11
  56. package/apps/dashboard/components/TestTree.tsx +1 -2
  57. package/apps/dashboard/components/TipCard.tsx +2 -4
  58. package/apps/dashboard/components/Toast.tsx +0 -1
  59. package/apps/dashboard/components/TypeIcon.tsx +7 -8
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
  62. package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
  63. package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
  64. package/apps/dashboard/components/WorkItemTree.tsx +2 -4
  65. package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
  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 +20 -73
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
  72. package/apps/dashboard/components/ui/Button.tsx +1 -1
  73. package/apps/dashboard/components/ui/Input.tsx +1 -1
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
  77. package/apps/dashboard/contexts/UsageContext.tsx +62 -31
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  81. package/apps/dashboard/index.html +73 -0
  82. package/apps/dashboard/lib/data-bridge.ts +722 -0
  83. package/apps/dashboard/lib/db.ts +69 -1302
  84. package/apps/dashboard/lib/environment-config.ts +173 -0
  85. package/apps/dashboard/lib/environment-verification.ts +119 -0
  86. package/apps/dashboard/lib/kanban-utils.ts +226 -26
  87. package/apps/dashboard/lib/proof-run.ts +495 -0
  88. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  89. package/apps/dashboard/lib/service-recovery.ts +326 -0
  90. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  91. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  92. package/apps/dashboard/lib/session-stream-manager.ts +253 -122
  93. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  94. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  95. package/apps/dashboard/lib/tauri.ts +106 -0
  96. package/apps/dashboard/lib/utils.ts +3 -3
  97. package/apps/dashboard/next-env.d.ts +1 -1
  98. package/apps/dashboard/package.json +21 -33
  99. package/apps/dashboard/public/bug-icon.png +0 -0
  100. package/apps/dashboard/public/buoy-icon.png +0 -0
  101. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  102. package/apps/dashboard/public/pier-icon.png +0 -0
  103. package/apps/dashboard/public/star-icon.png +0 -0
  104. package/apps/dashboard/public/wrench-icon.png +0 -0
  105. package/apps/dashboard/scripts/tauri-build.js +228 -0
  106. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  107. package/apps/dashboard/src/main.tsx +12 -0
  108. package/apps/dashboard/src/router.tsx +107 -0
  109. package/apps/dashboard/src/vite-env.d.ts +1 -0
  110. package/apps/dashboard/tsconfig.json +7 -12
  111. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  112. package/apps/dashboard/vite.config.ts +33 -0
  113. package/apps/update-server/src/index.ts +167 -30
  114. package/claude-hooks/global-guardrails.js +14 -13
  115. package/crates/jettypod-cli/Cargo.toml +19 -0
  116. package/crates/jettypod-cli/src/commands.rs +1249 -0
  117. package/crates/jettypod-cli/src/main.rs +595 -0
  118. package/crates/jettypod-core/Cargo.toml +26 -0
  119. package/crates/jettypod-core/build.rs +98 -0
  120. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  121. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  122. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  123. package/crates/jettypod-core/src/auth.rs +294 -0
  124. package/crates/jettypod-core/src/config.rs +397 -0
  125. package/crates/jettypod-core/src/db/mod.rs +507 -0
  126. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  127. package/crates/jettypod-core/src/db/startup.rs +101 -0
  128. package/crates/jettypod-core/src/db/validate.rs +149 -0
  129. package/crates/jettypod-core/src/error.rs +76 -0
  130. package/crates/jettypod-core/src/git.rs +458 -0
  131. package/crates/jettypod-core/src/lib.rs +20 -0
  132. package/crates/jettypod-core/src/sessions.rs +625 -0
  133. package/crates/jettypod-core/src/skills.rs +556 -0
  134. package/crates/jettypod-core/src/work.rs +1086 -0
  135. package/crates/jettypod-core/src/worktree.rs +628 -0
  136. package/crates/jettypod-core/src/ws.rs +767 -0
  137. package/cucumber-test.cjs +6 -0
  138. package/jettypod.js +96 -4
  139. package/lib/bdd-preflight.js +96 -0
  140. package/lib/merge-lock.js +111 -253
  141. package/lib/migrations/030-rejection-round-columns.js +54 -0
  142. package/lib/migrations/031-session-isolation-index.js +17 -0
  143. package/lib/work-commands/index.js +58 -16
  144. package/lib/work-tracking/index.js +108 -8
  145. package/package.json +1 -1
  146. package/skills-templates/bug-mode/SKILL.md +43 -1
  147. package/skills-templates/chore-mode/SKILL.md +40 -1
  148. package/skills-templates/design-system-selection/SKILL.md +273 -0
  149. package/skills-templates/epic-planning/SKILL.md +14 -0
  150. package/skills-templates/feature-planning/SKILL.md +90 -1
  151. package/skills-templates/production-mode/SKILL.md +20 -0
  152. package/skills-templates/simple-improvement/SKILL.md +39 -2
  153. package/skills-templates/speed-mode/SKILL.md +10 -15
  154. package/skills-templates/stable-mode/SKILL.md +47 -0
  155. package/apps/dashboard/README.md +0 -36
  156. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
  157. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  158. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
  159. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  160. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
  161. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  162. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  163. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  164. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  165. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  166. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  167. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  168. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  169. package/apps/dashboard/app/api/tests/route.ts +0 -9
  170. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  171. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  172. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  173. package/apps/dashboard/app/api/usage/route.ts +0 -17
  174. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  175. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  176. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  177. package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
  178. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
  179. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  180. package/apps/dashboard/app/layout.tsx +0 -55
  181. package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
  182. package/apps/dashboard/electron/ipc-handlers.js +0 -1026
  183. package/apps/dashboard/electron/main.js +0 -2306
  184. package/apps/dashboard/electron/preload.js +0 -125
  185. package/apps/dashboard/electron/session-manager.js +0 -163
  186. package/apps/dashboard/electron-builder.config.js +0 -357
  187. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  188. package/apps/dashboard/lib/backlog-parser.ts +0 -50
  189. package/apps/dashboard/lib/claude-process-manager.ts +0 -529
  190. package/apps/dashboard/lib/db-bridge.ts +0 -283
  191. package/apps/dashboard/lib/prototypes.ts +0 -202
  192. package/apps/dashboard/lib/test-results-db.ts +0 -307
  193. package/apps/dashboard/lib/tests.ts +0 -282
  194. package/apps/dashboard/next.config.js +0 -66
  195. package/apps/dashboard/postcss.config.mjs +0 -7
  196. package/apps/dashboard/public/bug-icon.svg +0 -9
  197. package/apps/dashboard/public/buoy-icon.svg +0 -9
  198. package/apps/dashboard/public/file.svg +0 -1
  199. package/apps/dashboard/public/globe.svg +0 -1
  200. package/apps/dashboard/public/in-flight-seagull.svg +0 -9
  201. package/apps/dashboard/public/next.svg +0 -1
  202. package/apps/dashboard/public/pier-icon.svg +0 -14
  203. package/apps/dashboard/public/star-icon.svg +0 -9
  204. package/apps/dashboard/public/vercel.svg +0 -1
  205. package/apps/dashboard/public/window.svg +0 -1
  206. package/apps/dashboard/public/wrench-icon.svg +0 -9
  207. package/apps/dashboard/scripts/download-node.js +0 -104
  208. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
@@ -1331,13 +1331,12 @@ async function cleanupWorkItem(workItemId, options = {}) {
1331
1331
  * Pushes feature branch, checks out main, merges, and pushes main
1332
1332
  * Post-merge hook will mark work item as done and cleanup worktree
1333
1333
  * @param {Object} options - Merge options
1334
- * @param {boolean} options.withTransition - Hold lock for transition phase (BDD generation)
1335
1334
  * @param {boolean} options.releaseLock - Release held lock only (no merge)
1336
- * @returns {Promise<void|Object>} Returns {lockHeld: true} if withTransition, void otherwise
1335
+ * @returns {Promise<void>}
1337
1336
  * @throws {Error} If no current work, git operations fail, or not in git repo
1338
1337
  */
1339
1338
  async function mergeWork(options = {}) {
1340
- const { withTransition = false, releaseLock = false, featureBranch = null, workItemId = null } = options;
1339
+ const { releaseLock = false, featureBranch = null, workItemId = null } = options;
1341
1340
 
1342
1341
  // Handle lock release-only mode first (can run from anywhere)
1343
1342
  if (releaseLock) {
@@ -2014,7 +2013,7 @@ async function mergeWork(options = {}) {
2014
2013
  });
2015
2014
  console.log(`✅ ${currentWork.type.charAt(0).toUpperCase() + currentWork.type.slice(1)} #${currentWork.id} ready for review`);
2016
2015
  } else {
2017
- // Chore/bug under a feature: mark as done (feature handles the accept flow)
2016
+ // Chore/bug under a feature: mark as done, then check if feature is complete
2018
2017
  console.log(`Marking ${currentWork.type} as done...`);
2019
2018
  const completedAt = new Date().toISOString();
2020
2019
  await new Promise((resolve, reject) => {
@@ -2028,6 +2027,60 @@ async function mergeWork(options = {}) {
2028
2027
  );
2029
2028
  });
2030
2029
  console.log(`✅ ${currentWork.type.charAt(0).toUpperCase() + currentWork.type.slice(1)} #${currentWork.id} marked as done`);
2030
+
2031
+ // Check if all sibling chores under the parent feature are now done
2032
+ // If so, and mode progression is complete, set ready_for_review on the parent
2033
+ const parent = await new Promise((resolve, reject) => {
2034
+ db.get('SELECT id, type, mode FROM work_items WHERE id = ?', [currentWork.parent_id], (err, row) => {
2035
+ if (err) return reject(err);
2036
+ resolve(row);
2037
+ });
2038
+ });
2039
+
2040
+ if (parent && parent.type === 'feature') {
2041
+ const siblingChores = await new Promise((resolve, reject) => {
2042
+ db.all(
2043
+ 'SELECT id, status FROM work_items WHERE parent_id = ? AND type = ?',
2044
+ [parent.id, 'chore'],
2045
+ (err, rows) => {
2046
+ if (err) return reject(err);
2047
+ resolve(rows || []);
2048
+ }
2049
+ );
2050
+ });
2051
+
2052
+ const allChoresDone = siblingChores.length > 0 && siblingChores.every(c => c.status === 'done');
2053
+ if (allChoresDone) {
2054
+ const config = await new Promise((resolve, reject) => {
2055
+ db.get('SELECT project_state FROM project_config WHERE id = 1', [], (err, row) => {
2056
+ if (err) return reject(err);
2057
+ resolve(row);
2058
+ });
2059
+ });
2060
+ const projectState = (config && config.project_state) || 'internal';
2061
+ const featureMode = parent.mode || 'speed';
2062
+
2063
+ let featureComplete = false;
2064
+ if (projectState === 'internal') {
2065
+ featureComplete = (featureMode === 'stable');
2066
+ } else if (projectState === 'external') {
2067
+ featureComplete = (featureMode === 'production');
2068
+ }
2069
+
2070
+ if (featureComplete) {
2071
+ await new Promise((resolve, reject) => {
2072
+ db.run('UPDATE work_items SET ready_for_review = 1 WHERE id = ?', [parent.id], (err) => {
2073
+ if (err) return reject(err);
2074
+ resolve();
2075
+ });
2076
+ });
2077
+ console.log(`✅ Feature #${parent.id} ready for review (all ${featureMode} mode chores done)`);
2078
+ } else {
2079
+ const nextMode = featureMode === 'speed' ? 'stable' : 'production';
2080
+ console.log(`✓ All ${featureMode} mode chores done. Feature #${parent.id} ready for ${nextMode} mode.`);
2081
+ }
2082
+ }
2083
+ }
2031
2084
  }
2032
2085
  }
2033
2086
 
@@ -2058,20 +2111,9 @@ async function mergeWork(options = {}) {
2058
2111
  console.log(' Worktrees accumulate until cleaned up.');
2059
2112
  }
2060
2113
 
2061
- if (withTransition) {
2062
- // Hold lock for transition phase (BDD generation)
2063
- // Work is done and worktree is cleaned up, but lock is held so no other merges
2064
- // happen while Claude generates stable scenarios on main
2065
- console.log('⚠️ Merge lock held for transition phase');
2066
- console.log(' Skills will release lock after generating stable scenarios');
2067
- console.log(' Release lock with: jettypod work merge --release-lock');
2068
- return Promise.resolve({ lockHeld: true });
2069
- }
2070
-
2071
2114
  return Promise.resolve();
2072
2115
  } finally {
2073
- // Release lock unless holding for transition
2074
- if (lock && !withTransition) {
2116
+ if (lock) {
2075
2117
  try {
2076
2118
  await lock.release();
2077
2119
  console.log('✅ Merge lock released');
@@ -298,8 +298,12 @@ function create(type, title, description = '', parentId = null, mode = null, nee
298
298
  // Set phase for features (discovery when mode=NULL, implementation when mode is set, NULL for everything else)
299
299
  const phase = type === 'feature' ? (mode ? 'implementation' : 'discovery') : null;
300
300
 
301
- const sql = `INSERT INTO work_items (type, title, description, parent_id, epic_id, mode, needs_discovery, phase, status, plan_at_creation) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
302
- db.run(sql, [type, title, description, parentId, epicId, mode, needsDiscovery ? 1 : 0, phase, 'backlog', planAtCreation], function(err) {
301
+ // If parent has been rejected, tag this child with the current rejection round
302
+ let rejectionRound = null;
303
+
304
+ function doInsert() {
305
+ const sql = `INSERT INTO work_items (type, title, description, parent_id, epic_id, mode, needs_discovery, phase, status, plan_at_creation, rejection_round) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
306
+ db.run(sql, [type, title, description, parentId, epicId, mode, needsDiscovery ? 1 : 0, phase, 'backlog', planAtCreation, rejectionRound], function(err) {
303
307
  if (err) {
304
308
  return reject(err);
305
309
  }
@@ -327,6 +331,21 @@ function create(type, title, description = '', parentId = null, mode = null, nee
327
331
  resolve(newId);
328
332
  }
329
333
  });
334
+ }
335
+
336
+ if (parentId) {
337
+ db.get('SELECT rejection_count FROM work_items WHERE id = ?', [parentId], (err, parentRow) => {
338
+ if (err) {
339
+ return reject(err);
340
+ }
341
+ if (parentRow && parentRow.rejection_count > 0) {
342
+ rejectionRound = parentRow.rejection_count;
343
+ }
344
+ doInsert();
345
+ });
346
+ } else {
347
+ doInsert();
348
+ }
330
349
  }
331
350
  });
332
351
  }
@@ -398,13 +417,32 @@ function filterProductionItems(items, itemsById) {
398
417
  * Get all work items as hierarchical tree structure
399
418
  * @param {boolean} includeCompleted - Include done/cancelled items (default: false)
400
419
  * @param {boolean} showAll - Show all items including production (bypasses internal/external filtering)
420
+ * @param {number|null} sessionId - If provided, hide items owned by OTHER active sessions
401
421
  * @returns {Promise<Array>} Root work items with nested children
402
422
  * @throws {Error} If database query fails
403
423
  */
404
- function getTree(includeCompleted = false, showAll = false) {
424
+ function getTree(includeCompleted = false, showAll = false, sessionId = null) {
405
425
  return new Promise((resolve, reject) => {
406
- const whereClause = includeCompleted ? '' : "WHERE (status NOT IN ('done', 'cancelled') OR status IS NULL)";
407
- db.all(`SELECT * FROM work_items ${whereClause} ORDER BY parent_id, id`, [], (err, rows) => {
426
+ const conditions = [];
427
+ const params = [];
428
+
429
+ if (!includeCompleted) {
430
+ conditions.push("(status NOT IN ('done', 'cancelled') OR status IS NULL)");
431
+ }
432
+
433
+ // Session isolation: hide items actively owned by OTHER sessions
434
+ if (sessionId) {
435
+ conditions.push(`work_items.id NOT IN (
436
+ SELECT cs.work_item_id FROM claude_sessions cs
437
+ WHERE cs.work_item_id IS NOT NULL
438
+ AND cs.status = 'active'
439
+ AND cs.id != ?
440
+ )`);
441
+ params.push(sessionId);
442
+ }
443
+
444
+ const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
445
+ db.all(`SELECT * FROM work_items ${whereClause} ORDER BY parent_id, id`, params, (err, rows) => {
408
446
  if (err) {
409
447
  return reject(new Error(`Failed to fetch work items: ${err.message}`));
410
448
  }
@@ -901,6 +939,14 @@ function setScenario(id, scenarioFile) {
901
939
  });
902
940
  }
903
941
 
942
+ // Set QA steps
943
+ function setQaSteps(id, qaStepsJson) {
944
+ db.run(`UPDATE work_items SET qa_steps = ? WHERE id = ?`, [qaStepsJson, id], () => {
945
+ const steps = JSON.parse(qaStepsJson);
946
+ console.log(`Set #${id} qa_steps (${steps.length} steps)`);
947
+ });
948
+ }
949
+
904
950
  // Set mode
905
951
  function setMode(id, mode) {
906
952
  return new Promise((resolve, reject) => {
@@ -1532,7 +1578,23 @@ async function main() {
1532
1578
  expandedIds = new Set(ids);
1533
1579
  }
1534
1580
 
1535
- // Query for ALL in_progress items
1581
+ // Session isolation for backlog display
1582
+ const sessionId = process.env.JETTYPOD_SESSION_ID ? parseInt(process.env.JETTYPOD_SESSION_ID, 10) : null;
1583
+
1584
+ // Query for ALL in_progress items (with session filtering)
1585
+ const activeParams = [];
1586
+ let activeSessionFilter = '';
1587
+ if (sessionId) {
1588
+ activeSessionFilter = `
1589
+ AND w.id NOT IN (
1590
+ SELECT cs.work_item_id FROM claude_sessions cs
1591
+ WHERE cs.work_item_id IS NOT NULL
1592
+ AND cs.status = 'active'
1593
+ AND cs.id != ?
1594
+ )`;
1595
+ activeParams.push(sessionId);
1596
+ }
1597
+
1536
1598
  const activeItems = await new Promise((resolve, reject) => {
1537
1599
  db.all(`
1538
1600
  SELECT w.id, w.title, w.type, w.status,
@@ -1542,8 +1604,9 @@ async function main() {
1542
1604
  LEFT JOIN work_items p ON w.parent_id = p.id
1543
1605
  LEFT JOIN work_items e ON w.epic_id = e.id
1544
1606
  WHERE w.status = 'in_progress'
1607
+ ${activeSessionFilter}
1545
1608
  ORDER BY w.ready_for_review DESC, w.id ASC
1546
- `, [], (err, rows) => {
1609
+ `, activeParams, (err, rows) => {
1547
1610
  if (err) return reject(err);
1548
1611
  resolve(rows || []);
1549
1612
  });
@@ -1673,7 +1736,7 @@ async function main() {
1673
1736
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1674
1737
  console.log('📋 BACKLOG');
1675
1738
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1676
- const items = await getTree(false, showAll);
1739
+ const items = await getTree(false, showAll, sessionId);
1677
1740
 
1678
1741
  printTree(items, '', true, expandedIds);
1679
1742
 
@@ -1749,6 +1812,40 @@ async function main() {
1749
1812
  break;
1750
1813
  }
1751
1814
 
1815
+ case 'set-qa-steps': {
1816
+ const id = parseInt(args[0]);
1817
+ if (isNaN(id)) {
1818
+ console.error('Error: Work item ID is required');
1819
+ console.log('Usage: jettypod work set-qa-steps <id> --from=<path>');
1820
+ process.exit(1);
1821
+ }
1822
+ const fromArg = args.find(a => a.startsWith('--from='));
1823
+ if (!fromArg) {
1824
+ console.error('Error: --from=<path> is required');
1825
+ console.log('Usage: jettypod work set-qa-steps <id> --from=<path>');
1826
+ process.exit(1);
1827
+ }
1828
+ const filePath = fromArg.replace('--from=', '');
1829
+ const fs = require('fs');
1830
+ if (!fs.existsSync(filePath)) {
1831
+ console.error(`Error: File not found: ${filePath}`);
1832
+ process.exit(1);
1833
+ }
1834
+ const qaJson = fs.readFileSync(filePath, 'utf8');
1835
+ try {
1836
+ const parsed = JSON.parse(qaJson);
1837
+ if (!Array.isArray(parsed)) {
1838
+ console.error('Error: QA steps must be a JSON array');
1839
+ process.exit(1);
1840
+ }
1841
+ } catch (e) {
1842
+ console.error(`Error: Invalid JSON in ${filePath}: ${e.message}`);
1843
+ process.exit(1);
1844
+ }
1845
+ setQaSteps(id, qaJson);
1846
+ break;
1847
+ }
1848
+
1752
1849
  case 'current': {
1753
1850
  if (!args[0]) {
1754
1851
  // No ID provided - show current work
@@ -2594,6 +2691,9 @@ Commands:
2594
2691
  jettypod work set-scenario <id> <file>
2595
2692
  Set scenario_file for a feature (e.g., features/my-feature.feature)
2596
2693
 
2694
+ jettypod work set-qa-steps <id> --from=<path>
2695
+ Set QA steps from a JSON file (array of {section, text, detail})
2696
+
2597
2697
  jettypod work current <id>
2598
2698
  Set as current work item
2599
2699
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.120",
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": {
@@ -303,12 +303,36 @@ Related Tests Passing
303
303
  Ready to commit and merge.
304
304
  ```
305
305
 
306
- **Proceed to Step 4.**
306
+ **Proceed to Step 3B.**
307
307
 
308
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
309
 
310
310
  ---
311
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
+
332
+ **Proceed to Step 4.**
333
+
334
+ ---
335
+
312
336
  ### Step 4: Commit and Merge
313
337
 
314
338
  **Emit gate signal:**
@@ -339,6 +363,8 @@ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
339
363
  jettypod work merge <bug-id>
340
364
  ```
341
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
+
342
368
  ```bash
343
369
  # Step 2: cd to main repo
344
370
  cd <main-repo-path>
@@ -359,6 +385,22 @@ jettypod work cleanup <bug-id>
359
385
  **The merge command automatically sets the bug as ready for review.**
360
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.
361
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
+
362
404
  **Emit gate signal:**
363
405
 
364
406
  ```bash
@@ -479,7 +479,29 @@ Returning to iteration loop...
479
479
 
480
480
  Go back to Step 5 to fix issues.
481
481
 
482
- **If all verification passes → Move to Step 7.**
482
+ **If all verification passes → Move to Step 6B.**
483
+
484
+ ### Step 6B: Prove It Works
485
+
486
+ **Before committing, demonstrate the change works in practice — not just that tests pass.**
487
+
488
+ 1. **Run or build the actual code** — compile, start the server, execute the command, or trigger the relevant workflow
489
+ 2. **Show real output** — display console output, build result, or observable behavior
490
+ 3. **Confirm it matches expectations** — verify the output is what was requested
491
+
492
+ **Display:**
493
+
494
+ ```
495
+ Verifying in practice...
496
+
497
+ [Actual output from running/building the code]
498
+
499
+ Verified: [what was confirmed]
500
+ ```
501
+
502
+ **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.
503
+
504
+ **Move to Step 7.**
483
505
 
484
506
  ### Step 7: Complete and Merge
485
507
 
@@ -505,6 +527,8 @@ git push
505
527
  jettypod work merge [chore-id]
506
528
  ```
507
529
 
530
+ **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.
531
+
508
532
  ```bash
509
533
  # Step 2: cd to main repo
510
534
  cd /path/to/main/repo
@@ -516,6 +540,21 @@ pwd && ls .jettypod # verify
516
540
  jettypod work cleanup [chore-id]
517
541
  ```
518
542
 
543
+ **Generate QA checklist:**
544
+
545
+ Based on the chore work completed, generate specific QA steps. Write a JSON array to `/tmp/qa-steps.json`:
546
+ ```json
547
+ [
548
+ {"section": "Section Name", "text": "Action to perform and expected result", "detail": "Additional context"},
549
+ ...
550
+ ]
551
+ ```
552
+ Be specific. 3-8 steps covering what was changed and how to verify it works.
553
+
554
+ ```bash
555
+ jettypod work set-qa-steps [chore-id] --from=/tmp/qa-steps.json
556
+ ```
557
+
519
558
  **Display:**
520
559
 
521
560
  ```