@opengsd/gsd-pi 1.2.0-dev.4c756166 → 1.2.0-dev.5457a158

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 (338) hide show
  1. package/dist/cli-style.d.ts +17 -0
  2. package/dist/cli-style.js +28 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/headless-events.d.ts +4 -2
  5. package/dist/headless-events.js +7 -29
  6. package/dist/models-resolver.d.ts +3 -13
  7. package/dist/models-resolver.js +3 -22
  8. package/dist/resource-loader.js +2 -14
  9. package/dist/resources/.managed-resources-content-hash +1 -1
  10. package/dist/resources/extensions/bg-shell/utilities.js +5 -2
  11. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  12. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +35 -4
  13. package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
  14. package/dist/resources/extensions/gsd/auto/phases.js +6 -1
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -8
  16. package/dist/resources/extensions/gsd/auto-prompts.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +12 -14
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
  19. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +7 -16
  20. package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
  21. package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
  22. package/dist/resources/extensions/gsd/auto.js +8 -20
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +86 -6
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +30 -4
  27. package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
  28. package/dist/resources/extensions/gsd/captures.js +5 -15
  29. package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
  30. package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
  31. package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
  32. package/dist/resources/extensions/gsd/db/engine.js +755 -0
  33. package/dist/resources/extensions/gsd/db/queries.js +372 -0
  34. package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
  35. package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
  36. package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
  37. package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
  38. package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
  39. package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
  40. package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
  41. package/dist/resources/extensions/gsd/doctor-format.js +9 -6
  42. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
  43. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
  44. package/dist/resources/extensions/gsd/error-classifier.js +9 -0
  45. package/dist/resources/extensions/gsd/git-service.js +1 -0
  46. package/dist/resources/extensions/gsd/gitignore.js +3 -0
  47. package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
  48. package/dist/resources/extensions/gsd/guidance.js +98 -0
  49. package/dist/resources/extensions/gsd/guided-flow.js +51 -5
  50. package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
  51. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
  52. package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
  53. package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
  54. package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
  55. package/dist/resources/extensions/gsd/model-router.js +3 -0
  56. package/dist/resources/extensions/gsd/notification-store.js +11 -4
  57. package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
  58. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
  59. package/dist/resources/extensions/gsd/paths.js +37 -24
  60. package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
  61. package/dist/resources/extensions/gsd/preferences-models.js +12 -46
  62. package/dist/resources/extensions/gsd/preferences.js +14 -0
  63. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
  67. package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
  68. package/dist/resources/extensions/gsd/publication.js +87 -0
  69. package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
  70. package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
  71. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
  72. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
  73. package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
  74. package/dist/resources/extensions/gsd/state.js +1 -20
  75. package/dist/resources/extensions/gsd/status-guards.js +56 -8
  76. package/dist/resources/extensions/gsd/stop-notice.js +57 -0
  77. package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
  78. package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
  79. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -8
  80. package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
  81. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
  82. package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
  83. package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
  84. package/dist/resources/extensions/gsd/undo.js +8 -7
  85. package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
  86. package/dist/resources/extensions/gsd/unit-context-composer.js +9 -1
  87. package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
  88. package/dist/resources/extensions/gsd/unit-registry.js +350 -0
  89. package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
  90. package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
  91. package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
  92. package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
  93. package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
  94. package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
  95. package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
  96. package/dist/resources/extensions/gsd/worktree-root.js +28 -6
  97. package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
  98. package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
  99. package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
  100. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  101. package/dist/web/standalone/.next/BUILD_ID +1 -1
  102. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  103. package/dist/web/standalone/.next/build-manifest.json +2 -2
  104. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  105. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  106. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  126. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  132. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  134. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  138. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/index.html +1 -1
  156. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  157. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  158. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  159. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  160. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  161. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  162. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  163. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  164. package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
  165. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  166. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  168. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  169. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  170. package/dist/worktree-cli.js +3 -6
  171. package/dist/worktree-status-banner.js +7 -11
  172. package/package.json +1 -1
  173. package/packages/cloud-mcp-gateway/package.json +2 -2
  174. package/packages/contracts/dist/workflow.d.ts +4 -0
  175. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  176. package/packages/contracts/dist/workflow.js.map +1 -1
  177. package/packages/contracts/package.json +1 -1
  178. package/packages/daemon/package.json +4 -4
  179. package/packages/gsd-agent-core/package.json +5 -5
  180. package/packages/gsd-agent-modes/package.json +7 -7
  181. package/packages/mcp-server/dist/cli.js +6 -3
  182. package/packages/mcp-server/dist/cli.js.map +1 -1
  183. package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
  184. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  185. package/packages/mcp-server/dist/workflow-tools.js +46 -21
  186. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  187. package/packages/mcp-server/package.json +3 -3
  188. package/packages/native/package.json +1 -1
  189. package/packages/pi-agent-core/package.json +1 -1
  190. package/packages/pi-ai/dist/models.generated.d.ts +294 -239
  191. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  192. package/packages/pi-ai/dist/models.generated.js +260 -256
  193. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  194. package/packages/pi-ai/package.json +1 -1
  195. package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
  197. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  198. package/packages/pi-coding-agent/package.json +7 -7
  199. package/packages/pi-tui/package.json +2 -2
  200. package/packages/rpc-client/package.json +2 -2
  201. package/pkg/package.json +1 -1
  202. package/src/resources/extensions/bg-shell/utilities.ts +5 -2
  203. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  204. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +37 -2
  205. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
  206. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
  207. package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
  208. package/src/resources/extensions/gsd/auto/phases.ts +10 -1
  209. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -7
  210. package/src/resources/extensions/gsd/auto-prompts.ts +3 -0
  211. package/src/resources/extensions/gsd/auto-start.ts +12 -15
  212. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  213. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +10 -17
  214. package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
  215. package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
  216. package/src/resources/extensions/gsd/auto.ts +20 -24
  217. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
  218. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
  219. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +87 -6
  220. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +29 -3
  221. package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
  222. package/src/resources/extensions/gsd/captures.ts +5 -16
  223. package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
  224. package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
  225. package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
  226. package/src/resources/extensions/gsd/db/engine.ts +809 -0
  227. package/src/resources/extensions/gsd/db/queries.ts +453 -0
  228. package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
  229. package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
  230. package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
  231. package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
  232. package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
  233. package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
  234. package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
  235. package/src/resources/extensions/gsd/doctor-format.ts +12 -7
  236. package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
  237. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
  238. package/src/resources/extensions/gsd/error-classifier.ts +11 -0
  239. package/src/resources/extensions/gsd/git-service.ts +1 -0
  240. package/src/resources/extensions/gsd/gitignore.ts +3 -0
  241. package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
  242. package/src/resources/extensions/gsd/guidance.ts +139 -0
  243. package/src/resources/extensions/gsd/guided-flow.ts +50 -5
  244. package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
  245. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
  246. package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
  247. package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
  248. package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
  249. package/src/resources/extensions/gsd/model-router.ts +3 -0
  250. package/src/resources/extensions/gsd/notification-store.ts +26 -3
  251. package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
  252. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
  253. package/src/resources/extensions/gsd/paths.ts +42 -22
  254. package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
  255. package/src/resources/extensions/gsd/preferences-models.ts +10 -46
  256. package/src/resources/extensions/gsd/preferences.ts +18 -0
  257. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  258. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  259. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  260. package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
  261. package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
  262. package/src/resources/extensions/gsd/publication.ts +122 -0
  263. package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
  264. package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
  265. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
  266. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
  267. package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
  268. package/src/resources/extensions/gsd/state.ts +4 -21
  269. package/src/resources/extensions/gsd/status-guards.ts +59 -8
  270. package/src/resources/extensions/gsd/stop-notice.ts +75 -0
  271. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
  272. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
  273. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
  274. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
  275. package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
  276. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
  277. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
  278. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
  279. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
  280. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
  281. package/src/resources/extensions/gsd/tests/guidance.test.ts +125 -0
  282. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +51 -4
  283. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +54 -1
  284. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  285. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
  286. package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
  287. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
  288. package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
  289. package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
  290. package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
  291. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
  292. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
  293. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
  294. package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
  295. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
  296. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
  297. package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
  298. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
  299. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
  300. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
  301. package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
  302. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +23 -2
  303. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
  304. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  305. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
  306. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
  307. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
  308. package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
  309. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
  310. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
  311. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
  312. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
  313. package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
  314. package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
  315. package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
  316. package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -8
  317. package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
  318. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
  319. package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
  320. package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
  321. package/src/resources/extensions/gsd/undo.ts +9 -8
  322. package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
  323. package/src/resources/extensions/gsd/unit-context-composer.ts +12 -1
  324. package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
  325. package/src/resources/extensions/gsd/unit-registry.ts +425 -0
  326. package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
  327. package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
  328. package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
  329. package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
  330. package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
  331. package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
  332. package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
  333. package/src/resources/extensions/gsd/worktree-root.ts +29 -6
  334. package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
  335. package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
  336. package/src/resources/skills/gsd-browser/SKILL.md +1 -1
  337. /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → 2p9Rv9pQflAxCBbGVI2vb}/_buildManifest.js +0 -0
  338. /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → 2p9Rv9pQflAxCBbGVI2vb}/_ssgManifest.js +0 -0
@@ -0,0 +1,139 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Guidance module — the single catalog mapping typed findings
3
+ // (Recovery kinds, milestone blocker kinds, doctor issue codes, crash unit
4
+ // classes) to user-facing remediation: what happened and what to do next.
5
+ //
6
+ // Emit sites pass the typed finding; phrasing, command names, and step
7
+ // ordering live here. A missing catalog row is a visible gap, not a silent
8
+ // omission scattered across call sites.
9
+
10
+ import type { RecoveryFailureKind } from "./recovery-classification.js";
11
+ import type { DoctorIssueCode } from "./doctor-types.js";
12
+
13
+ // ─── Shape ──────────────────────────────────────────────────────────────
14
+
15
+ export interface Guidance {
16
+ summary: string;
17
+ steps: string[];
18
+ }
19
+
20
+ /** Flatten guidance into a notification / pause-banner string. */
21
+ export function formatGuidance(guidance: Guidance): string {
22
+ if (guidance.steps.length === 0) return guidance.summary;
23
+ const numbered = guidance.steps.map((step, index) => `${index + 1}. ${step}`).join("\n");
24
+ return `${guidance.summary}\n\n${numbered}`;
25
+ }
26
+
27
+ // ─── Recovery Classification remediation ────────────────────────────────
28
+ // Keyed by RecoveryFailureKind. The provider kind is split by transience,
29
+ // which Recovery Classification resolves before looking up guidance.
30
+
31
+ export type RecoveryGuidanceKey =
32
+ | Exclude<RecoveryFailureKind, "provider">
33
+ | "provider-transient"
34
+ | "provider-permanent";
35
+
36
+ const RECOVERY_REMEDIATION: Record<RecoveryGuidanceKey, string> = {
37
+ "tool-schema": "Fix the Unit Tool Contract or tool schema before retrying.",
38
+ "tool-contract":
39
+ "Fix the Unit Tool Contract or prompt so the Unit is only asked to use tools owned by its phase.",
40
+ "tool-unavailable":
41
+ "The tool surface had not finished registering when the Unit called it (workflow MCP startup race). Retry after the surface is ready; escalate if the tool never appears.",
42
+ "deterministic-policy": "Resolve the policy blocker; retrying the same Unit will repeat the failure.",
43
+ "lifecycle-progression":
44
+ "Route to the required owning Unit or restore the missing artifact before advancing lifecycle state.",
45
+ "stale-worker":
46
+ "Run `/gsd doctor` to detect and clear the stale worker or lock, then run `/gsd auto` to resume.",
47
+ "worktree-invalid":
48
+ "Run `/gsd doctor` to diagnose the milestone worktree (`gsd worktree list` shows its state). Repair it, or merge salvageable work with `gsd worktree merge <name>` before recreating — recreating discards uncommitted work.",
49
+ "verification-drift":
50
+ "Run `/gsd status` to see the verification finding, fix or re-run the verification, then run `/gsd auto` to resume. `/gsd doctor` can repair stale state files.",
51
+ "reconciliation-drift":
52
+ "Run `/gsd doctor` to surface the persistent or repair-failed drift kinds, apply its fixes, then run `/gsd auto` to resume.",
53
+ "illegal-transition":
54
+ "A derived Phase edge rejected by the Phase Transition Invariant survived reconciliation; inspect deriveState and the State Reconciliation Module before resuming.",
55
+ "runtime-unknown": "Inspect the runtime error and add a dedicated classification if it is repeatable.",
56
+ "provider-transient": "Retry after the provider/network condition clears.",
57
+ "provider-permanent": "Inspect provider credentials, model entitlement, or request shape.",
58
+ };
59
+
60
+ export function recoveryRemediation(key: RecoveryGuidanceKey): string {
61
+ return RECOVERY_REMEDIATION[key];
62
+ }
63
+
64
+ // ─── Milestone validation blockers ──────────────────────────────────────
65
+ // NOTE: the first line of each blocker is load-bearing — validation-block-guard
66
+ // matches /milestone validation returned needs-(?:attention|remediation)/i.
67
+ // Keep that phrase intact when editing.
68
+
69
+ export function needsAttentionBlockerGuidance(milestoneId: string): string {
70
+ return [
71
+ `Milestone ${milestoneId} is blocked because milestone validation returned needs-attention.`,
72
+ `Fix options:`,
73
+ `1. Review the validation details: \`/gsd status\``,
74
+ `2. If you fixed the missing evidence or issue, re-run milestone validation: \`/gsd validate-milestone\``,
75
+ `3. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
76
+ `4. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
77
+ `After validation or override passes, run \`/gsd auto\` to complete and merge the milestone.`,
78
+ ].join("\n");
79
+ }
80
+
81
+ export function needsRemediationBlockerGuidance(milestoneId: string): string {
82
+ return [
83
+ `Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
84
+ `Fix options:`,
85
+ `1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
86
+ `2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
87
+ `3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
88
+ ].join("\n");
89
+ }
90
+
91
+ // ─── Crash recovery resume hints ────────────────────────────────────────
92
+
93
+ /** Resume hint for an interrupted auto-mode unit, by unit class. */
94
+ export function crashResumeHint(unitType: string, unitId: string): string | undefined {
95
+ if (unitType === "starting" && unitId === "bootstrap") {
96
+ return `No work was lost. Run /gsd auto to restart.`;
97
+ }
98
+ if (unitType.includes("research") || unitType.includes("plan")) {
99
+ return `The ${unitType} unit may be incomplete. Run /gsd auto to re-run it.`;
100
+ }
101
+ if (unitType.includes("execute")) {
102
+ return `Task execution was interrupted. Run /gsd auto to resume — completed work is preserved.`;
103
+ }
104
+ if (unitType.includes("complete")) {
105
+ return `Slice/milestone completion was interrupted. Run /gsd auto to finish.`;
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ // ─── Doctor issue fix hints ─────────────────────────────────────────────
111
+ // Partial by design: codes without a row render no hint. Add rows here as
112
+ // guidance is authored — the gap is visible in one place.
113
+
114
+ const DOCTOR_FIX_HINTS: Partial<Record<DoctorIssueCode, string>> = {
115
+ db_unavailable:
116
+ "The workflow database could not be opened — state derivation is degraded. Restart the session; if it persists, run `/gsd doctor` from the project root.",
117
+ stale_crash_lock: "Run `/gsd doctor` to clear the stale lock, then `/gsd auto` to resume.",
118
+ stale_parallel_session: "Run `/gsd doctor` to clear the stale session registration.",
119
+ unresolved_git_conflicts:
120
+ "Resolve the conflict markers, commit, then re-run `/gsd auto`.",
121
+ conflict_markers_in_tracked_files:
122
+ "Search the listed files for `<<<<<<<` markers, resolve, and commit.",
123
+ worktree_dirty:
124
+ "Commit or merge the worktree's changes (`gsd worktree merge <name>`) before removing it.",
125
+ worktree_branch_merged: "The branch is merged — remove the worktree to reclaim space.",
126
+ orphaned_auto_worktree: "Run `/gsd doctor` to fix, or merge salvageable work with `gsd worktree merge <name>`.",
127
+ gitignore_missing_patterns: "Run `/gsd doctor` to append the missing .gitignore patterns.",
128
+ invalid_preferences: "Edit .gsd/PREFERENCES.md to fix the invalid field, then re-run the command.",
129
+ provider_key_missing: "Add the provider API key to your environment or provider config, then retry.",
130
+ provider_key_backedoff: "The key is cooling down after repeated failures — wait, or switch the phase model in .gsd/PREFERENCES.md.",
131
+ state_file_stale: "Run `/gsd doctor` to rebuild the projection from the database.",
132
+ state_file_missing: "Run `/gsd doctor` to rebuild the projection from the database.",
133
+ projection_drift: "Run `/gsd doctor` to rebuild markdown projections from the database (DB is the source of truth).",
134
+ uat_retry_exhausted: "Review the failing UAT criteria via `/gsd status`, fix the issue, then re-run `/gsd auto`.",
135
+ };
136
+
137
+ export function doctorFixHint(code: DoctorIssueCode): string | undefined {
138
+ return DOCTOR_FIX_HINTS[code];
139
+ }
@@ -90,7 +90,7 @@ import {
90
90
  } from "./preparation.js";
91
91
  import { verifyExpectedArtifact } from "./auto-recovery.js";
92
92
  import type { MilestoneScope } from "./workspace.js";
93
- import { getPendingGate } from "./bootstrap/write-gate.js";
93
+ import { clearPendingGate, extractDepthVerificationMilestoneId, getPendingGate } from "./bootstrap/write-gate.js";
94
94
  import {
95
95
  _getPendingAutoStart,
96
96
  clearPendingAutoStart,
@@ -100,7 +100,7 @@ import {
100
100
  setPendingAutoStart,
101
101
  } from "./pending-auto-start.js";
102
102
  import { clearGuidedUnitContext, setGuidedUnitContext } from "./guided-unit-context.js";
103
- import { scheduleAutoStartAfterIdle } from "./discussion-handoff.js";
103
+ import { checkAutoStartAfterDiscuss, scheduleAutoStartAfterIdle } from "./discussion-handoff.js";
104
104
  export {
105
105
  maybeHandleEmptyIntentTurn,
106
106
  maybeHandleReadyPhraseWithoutFiles,
@@ -1582,6 +1582,25 @@ function selfHealRuntimeRecords(basePath: string, ctx: ExtensionContext): { clea
1582
1582
  }
1583
1583
  }
1584
1584
 
1585
+ /**
1586
+ * True when an agent turn is currently streaming or a dispatched message is
1587
+ * still queued waiting to trigger one. Used by the pending-auto-start stale
1588
+ * check: a live discuss turn can run for minutes before writing its first
1589
+ * artifact, and deleting its entry as "stale" re-dispatches the workflow —
1590
+ * resetting the interview and producing a duplicate completion turn.
1591
+ */
1592
+ function isAgentTurnInFlight(ctx: ExtensionCommandContext): boolean {
1593
+ try {
1594
+ if (typeof ctx.isIdle === "function" && !ctx.isIdle()) return true;
1595
+ if (typeof ctx.hasPendingMessages === "function" && ctx.hasPendingMessages()) return true;
1596
+ } catch {
1597
+ // assertActive() throws on a stale runner context; fall through to
1598
+ // artifact/age staleness signals.
1599
+ logWarning("guided", "isAgentTurnInFlight: ctx method threw (stale runner); assuming no turn in flight");
1600
+ }
1601
+ return false;
1602
+ }
1603
+
1585
1604
  // ─── Milestone Actions Submenu ──────────────────────────────────────────────
1586
1605
 
1587
1606
  /**
@@ -1911,12 +1930,18 @@ export async function showSmartEntry(
1911
1930
  // and fires another dispatchWorkflow, resetting the conversation mid-interview.
1912
1931
  if (hasPendingAutoStart(basePath)) {
1913
1932
  // #3274: If /clear interrupted the discussion, the pending entry is stale.
1914
- // Detect staleness: no manifest, no milestone CONTEXT artifact, AND entry is older than
1915
- // 30s (avoids race between .set() and LLM writing first artifact).
1933
+ // Detect staleness: no manifest, no milestone CONTEXT/CONTEXT-DRAFT artifact,
1934
+ // the entry is older than 30s (avoids race between .set() and LLM writing the
1935
+ // first artifact), AND no agent turn is in flight. A dispatched discuss turn
1936
+ // can think for well over 30s before its first question round writes any
1937
+ // artifact; deleting the entry while that turn is live re-dispatches the
1938
+ // workflow, which both resets the interview and queues a duplicate turn that
1939
+ // replays the final "context written" message after the real one.
1916
1940
  const entry = _getPendingAutoStart(basePath)!;
1917
1941
  const ageMs = Date.now() - (entry.createdAt || 0);
1918
1942
  const manifestExists = existsSync(join(gsdRoot(basePath), "DISCUSSION-MANIFEST.json"));
1919
1943
  const milestoneHasContext = !!resolveMilestoneFile(basePath, entry.milestoneId, "CONTEXT");
1944
+ const milestoneHasDraft = !!resolveMilestoneFile(basePath, entry.milestoneId, "CONTEXT-DRAFT");
1920
1945
  const milestoneHasRoadmap = !!resolveMilestoneFile(basePath, entry.milestoneId, "ROADMAP");
1921
1946
  const milestoneRow = isDbAvailable() ? getMilestone(entry.milestoneId) : null;
1922
1947
  const discussPlanComplete = milestoneHasRoadmap && !!milestoneRow && milestoneRow.status !== "queued";
@@ -1924,10 +1949,30 @@ export async function showSmartEntry(
1924
1949
  // The discuss flow already completed, but pending auto-start cleanup handshake did not run.
1925
1950
  // Clear stale in-memory guard and continue through normal active-milestone routing.
1926
1951
  deletePendingAutoStart(basePath);
1927
- } else if (!manifestExists && !milestoneHasContext && ageMs > 30_000) {
1952
+ } else if (
1953
+ !manifestExists &&
1954
+ !milestoneHasContext &&
1955
+ !milestoneHasDraft &&
1956
+ ageMs > 30_000 &&
1957
+ !isAgentTurnInFlight(ctx)
1958
+ ) {
1928
1959
  // Stale entry from an interrupted discussion — clear and continue
1929
1960
  deletePendingAutoStart(basePath);
1930
1961
  } else {
1962
+ if (milestoneHasContext && !isAgentTurnInFlight(ctx)) {
1963
+ // The discussion already produced CONTEXT but the agent_end handoff
1964
+ // never consumed the entry — e.g. an external-engine post-hoc gate
1965
+ // re-arm wiped the depth verification after the save (write-gate
1966
+ // two-process sync). CONTEXT can only be written through a verified
1967
+ // depth gate, so a gate still pending for this milestone is stale:
1968
+ // clear it and re-run the handoff instead of dead-ending.
1969
+ const gateBasePath = entry.scope.workspace.projectRoot;
1970
+ const pendingGateId = getPendingGate(gateBasePath);
1971
+ if (pendingGateId && extractDepthVerificationMilestoneId(pendingGateId) === entry.milestoneId) {
1972
+ clearPendingGate(gateBasePath);
1973
+ }
1974
+ if (checkAutoStartAfterDiscuss(basePath)) return;
1975
+ }
1931
1976
  ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
1932
1977
  return;
1933
1978
  }
@@ -1,5 +1,7 @@
1
1
  // Project/App: gsd-pi
2
- // File Purpose: Shared parsing and formatting helpers for MCP-scoped tool names.
2
+ // File Purpose: GSD-facing face over the shared @gsd/pi-ai MCP tool-name helpers.
3
+
4
+ import { parseMcpToolName as parsePiAiMcpToolName, stripMcpToolPrefix } from "@gsd/pi-ai";
3
5
 
4
6
  const MCP_TOOL_PREFIX = "mcp__";
5
7
 
@@ -9,18 +11,11 @@ export interface ParsedMcpToolName {
9
11
  }
10
12
 
11
13
  export function parseMcpToolName(toolName: string): ParsedMcpToolName | null {
12
- if (!toolName.startsWith(MCP_TOOL_PREFIX)) return null;
13
- const toolSeparator = toolName.indexOf("__", MCP_TOOL_PREFIX.length);
14
- if (toolSeparator < 0) return null;
15
- return {
16
- serverName: toolName.slice(MCP_TOOL_PREFIX.length, toolSeparator),
17
- toolName: toolName.slice(toolSeparator + 2),
18
- };
14
+ const parsed = parsePiAiMcpToolName(toolName);
15
+ return parsed ? { serverName: parsed.server, toolName: parsed.tool } : null;
19
16
  }
20
17
 
21
- export function stripMcpToolPrefix(toolName: string): string {
22
- return parseMcpToolName(toolName)?.toolName ?? toolName;
23
- }
18
+ export { stripMcpToolPrefix };
24
19
 
25
20
  export function toMcpToolName(serverName: string, toolName: string): string {
26
21
  return `${MCP_TOOL_PREFIX}${serverName}__${toolName}`;
@@ -264,7 +264,7 @@ export function reportConsolidationGaps(basePath: string): ConsolidationGapRepor
264
264
  try {
265
265
  const report = scanConsolidationGaps(basePath);
266
266
  if (report.totalGaps === 0) return report;
267
- appendNotification(report.summary, "warning", "workflow-logger");
267
+ appendNotification(report.summary, "warning", "workflow-logger", { kind: "memory-consolidation" });
268
268
  logWarning("memory-consolidation", report.summary);
269
269
  return report;
270
270
  } catch (e) {
@@ -10,6 +10,7 @@ import { readCrashLock, isLockProcessAlive } from "../crash-recovery.js";
10
10
  import { closeWorkflowDatabase } from "../db-workspace.js";
11
11
  import { readPausedSessionMetadata } from "../interrupted-session.js";
12
12
  import { gsdRoot } from "../paths.js";
13
+ import { canonicalWorktreesDir } from "../worktree-placement.js";
13
14
  import type { MigrationPreview } from "./writer.js";
14
15
 
15
16
  export interface MigrationPaths {
@@ -106,14 +107,24 @@ export function assertMigrationHasSlices(preview: MigrationPreview): void {
106
107
  }
107
108
 
108
109
  function hasWorktreeState(targetRoot: string): boolean {
109
- const worktreesDir = join(gsdRoot(targetRoot), "worktrees");
110
- if (!existsSync(worktreesDir)) return false;
111
- try {
112
- return readdirSync(worktreesDir, { withFileTypes: true })
113
- .some((entry) => entry.isDirectory() || entry.isFile());
114
- } catch {
115
- return true;
110
+ // Legacy container is probed via gsdRoot() (symlink-resolved) on purpose —
111
+ // migration targets may have .gsd in the external-state layout.
112
+ const containers = [
113
+ canonicalWorktreesDir(targetRoot),
114
+ join(gsdRoot(targetRoot), "worktrees"),
115
+ ];
116
+ for (const worktreesDir of containers) {
117
+ if (!existsSync(worktreesDir)) continue;
118
+ try {
119
+ if (readdirSync(worktreesDir, { withFileTypes: true })
120
+ .some((entry) => entry.isDirectory() || entry.isFile())) {
121
+ return true;
122
+ }
123
+ } catch {
124
+ return true;
125
+ }
116
126
  }
127
+ return false;
117
128
  }
118
129
 
119
130
  export async function assertMigrationTargetAvailable(targetRoot: string): Promise<void> {
@@ -39,6 +39,9 @@ interface HierarchyScan {
39
39
  milestones: Set<string>;
40
40
  slices: Set<string>;
41
41
  tasks: Set<string>;
42
+ // Markdown milestones whose dir has no ROADMAP (CONTEXT/CONTEXT-DRAFT only
43
+ // or empty). Always empty for DB scans.
44
+ milestonesWithoutRoadmap: Set<string>;
42
45
  }
43
46
 
44
47
  function zeroCounts(): HierarchyCounts {
@@ -46,7 +49,13 @@ function zeroCounts(): HierarchyCounts {
46
49
  }
47
50
 
48
51
  function emptyScan(): HierarchyScan {
49
- return { counts: zeroCounts(), milestones: new Set(), slices: new Set(), tasks: new Set() };
52
+ return {
53
+ counts: zeroCounts(),
54
+ milestones: new Set(),
55
+ slices: new Set(),
56
+ tasks: new Set(),
57
+ milestonesWithoutRoadmap: new Set(),
58
+ };
50
59
  }
51
60
 
52
61
  function sameCounts(a: HierarchyCounts, b: HierarchyCounts): boolean {
@@ -109,7 +118,10 @@ export function scanMarkdownHierarchy(basePath: string): HierarchyScan {
109
118
  scan.milestones.add(milestoneId);
110
119
 
111
120
  const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
112
- if (!roadmapPath || !existsSync(roadmapPath)) continue;
121
+ if (!roadmapPath || !existsSync(roadmapPath)) {
122
+ scan.milestonesWithoutRoadmap.add(milestoneId);
123
+ continue;
124
+ }
113
125
 
114
126
  const roadmap = parseRoadmap(readFileSync(roadmapPath, "utf-8"));
115
127
  scan.counts.slices += roadmap.slices.length;
@@ -164,7 +176,6 @@ export async function checkMarkdownHierarchyAgainstDb(
164
176
  basePath: string,
165
177
  ): Promise<MigrationAutoCheckResult> {
166
178
  const markdownScan = scanMarkdownHierarchy(basePath);
167
- const markdown = markdownScan.counts;
168
179
 
169
180
  // Always open the DB before deciding. An empty markdown tree does NOT imply
170
181
  // an empty project — the DB may hold authoritative rows whose markdown was
@@ -184,6 +195,20 @@ export async function checkMarkdownHierarchyAgainstDb(
184
195
  const dbScan = scanDbHierarchy();
185
196
  const beforeDb = dbScan.counts;
186
197
 
198
+ // Discussion-phase scratch: a milestone dir with no ROADMAP and no DB row is
199
+ // a pre-registration discussion artifact (CONTEXT/CONTEXT-DRAFT only — the
200
+ // queued DB row is inserted only at discussion handoff). Treating it as
201
+ // drift would warn on every live discussion and recommend
202
+ // `/gsd recover --confirm`, an import that materializes abandoned-discussion
203
+ // dirs as ghost active milestones. Exclude such dirs from this comparison
204
+ // only; recover preflights use the raw scans and still see them.
205
+ for (const id of markdownScan.milestonesWithoutRoadmap) {
206
+ if (dbScan.milestones.has(id)) continue;
207
+ markdownScan.milestones.delete(id);
208
+ markdownScan.counts.milestones--;
209
+ }
210
+ const markdown = markdownScan.counts;
211
+
187
212
  const markdownEmpty = sameCounts(markdown, zeroCounts());
188
213
  const dbEmpty = sameCounts(beforeDb, zeroCounts());
189
214
 
@@ -25,6 +25,7 @@ export const BUNDLED_COST_TABLE: ModelCostEntry[] = [
25
25
  { id: "claude-opus-4-6", inputPer1k: 0.005, outputPer1k: 0.025, updatedAt: "2026-04-16" },
26
26
  { id: "claude-opus-4-7", inputPer1k: 0.005, outputPer1k: 0.025, updatedAt: "2026-04-16" },
27
27
  { id: "claude-opus-4-8", inputPer1k: 0.005, outputPer1k: 0.025, updatedAt: "2026-05-28" },
28
+ { id: "claude-fable-5", inputPer1k: 0.010, outputPer1k: 0.050, updatedAt: "2026-06-09" },
28
29
  { id: "claude-sonnet-4-6", inputPer1k: 0.003, outputPer1k: 0.015, updatedAt: "2025-03-15" },
29
30
  { id: "claude-haiku-4-5", inputPer1k: 0.0008, outputPer1k: 0.004, updatedAt: "2025-03-15" },
30
31
  { id: "claude-sonnet-4-5-20250514", inputPer1k: 0.003, outputPer1k: 0.015, updatedAt: "2025-03-15" },
@@ -101,6 +101,7 @@ export const MODEL_CAPABILITY_TIER: Record<string, ComplexityTier> = {
101
101
  "claude-opus-4-6": "heavy",
102
102
  "claude-opus-4-7": "heavy",
103
103
  "claude-opus-4-8": "heavy",
104
+ "claude-fable-5": "heavy",
104
105
  "claude-3-opus-latest": "heavy",
105
106
  "gpt-4-turbo": "heavy",
106
107
  "gpt-5": "heavy",
@@ -129,6 +130,7 @@ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
129
130
  "claude-opus-4-6": 0.005,
130
131
  "claude-opus-4-7": 0.005,
131
132
  "claude-opus-4-8": 0.005,
133
+ "claude-fable-5": 0.010,
132
134
  "gpt-4o-mini": 0.00015,
133
135
  "gpt-4o": 0.0025,
134
136
  "gpt-4.1": 0.002,
@@ -164,6 +166,7 @@ export const MODEL_CAPABILITY_PROFILES: Record<string, ModelCapabilities> = {
164
166
  "claude-opus-4-6": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
165
167
  "claude-opus-4-7": { coding: 95, debugging: 90, research: 85, reasoning: 95, speed: 30, longContext: 80, instruction: 90 },
166
168
  "claude-opus-4-8": { coding: 97, debugging: 92, research: 87, reasoning: 97, speed: 30, longContext: 85, instruction: 92 },
169
+ "claude-fable-5": { coding: 97, debugging: 92, research: 87, reasoning: 97, speed: 30, longContext: 85, instruction: 92 },
167
170
  "claude-sonnet-4-6": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
168
171
  "claude-sonnet-4-5-20250514": { coding: 85, debugging: 80, research: 75, reasoning: 80, speed: 60, longContext: 75, instruction: 85 },
169
172
  "claude-3-5-sonnet-latest": { coding: 82, debugging: 78, research: 72, reasoning: 78, speed: 62, longContext: 70, instruction: 82 },
@@ -12,6 +12,17 @@ import { randomUUID } from "node:crypto";
12
12
  export type NotifySeverity = "info" | "success" | "warning" | "error";
13
13
  export type NotificationSource = "notify" | "workflow-logger";
14
14
 
15
+ /**
16
+ * Optional structured identity for a notification. When present, `kind`
17
+ * (a stable machine name like "auto-stop" or "provider-error-pause") plus
18
+ * `scope` (e.g. a milestone/slice id) — not the prose — key deduplication,
19
+ * and consumers can filter without parsing message text.
20
+ */
21
+ export interface NotificationMeta {
22
+ kind?: string;
23
+ scope?: string;
24
+ }
25
+
15
26
  export interface NotificationEntry {
16
27
  id: string;
17
28
  ts: string;
@@ -19,6 +30,8 @@ export interface NotificationEntry {
19
30
  message: string;
20
31
  source: NotificationSource;
21
32
  read: boolean;
33
+ kind?: string;
34
+ scope?: string;
22
35
  }
23
36
 
24
37
  // ─── Constants ──────────────────────────────────────────────────────────
@@ -60,11 +73,15 @@ export function appendNotification(
60
73
  message: string,
61
74
  severity: NotifySeverity,
62
75
  source: NotificationSource = "notify",
76
+ meta?: NotificationMeta,
63
77
  ): void {
64
78
  if (!_basePath) return;
65
79
  if (_suppressCount > 0) return;
66
80
  const persistedMessage = message.length > 500 ? message.slice(0, 500) + "…" : message;
67
- const dedupKey = `${_basePath}:${severity}:${source}:${persistedMessage}`;
81
+ // Structured identity (kind + scope) keys dedup when present, so a rephrased
82
+ // message of the same event still dedups; otherwise fall back to the prose.
83
+ const identity = meta?.kind ? `${meta.kind}:${meta.scope ?? ""}` : persistedMessage;
84
+ const dedupKey = `${_basePath}:${severity}:${source}:${identity}`;
68
85
  const now = Date.now();
69
86
  const lastSeen = _recentMessageTimestamps.get(dedupKey);
70
87
  if (lastSeen !== undefined && now - lastSeen < DEDUP_WINDOW_MS) return;
@@ -82,6 +99,8 @@ export function appendNotification(
82
99
  message: persistedMessage,
83
100
  source,
84
101
  read: false,
102
+ ...(meta?.kind ? { kind: meta.kind } : {}),
103
+ ...(meta?.scope ? { scope: meta.scope } : {}),
85
104
  };
86
105
 
87
106
  try {
@@ -102,11 +121,15 @@ export function appendNotification(
102
121
 
103
122
  /**
104
123
  * Read all notification entries from disk. Returns newest-first.
124
+ * An optional filter narrows by structured identity (kind and/or scope).
105
125
  */
106
- export function readNotifications(basePath?: string): NotificationEntry[] {
126
+ export function readNotifications(basePath?: string, filter?: NotificationMeta): NotificationEntry[] {
107
127
  const bp = basePath ?? _basePath;
108
128
  if (!bp) return [];
109
- return _readEntriesFromDisk(bp).reverse();
129
+ const entries = _readEntriesFromDisk(bp).filter(
130
+ (e) => (!filter?.kind || e.kind === filter.kind) && (!filter?.scope || e.scope === filter.scope),
131
+ );
132
+ return entries.reverse();
110
133
  }
111
134
 
112
135
  /**
@@ -9,6 +9,7 @@ import { existsSync, readdirSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { spawnSync } from "node:child_process";
11
11
  import { resolveGsdPathContract } from "./paths.js";
12
+ import { worktreePathFor, worktreesDirs } from "./worktree-placement.js";
12
13
  import { getAutoWorktreePath } from "./auto-worktree.js";
13
14
  import { buildWorktreeLifecycleDeps } from "./auto.js";
14
15
  import {
@@ -42,7 +43,7 @@ export type MergeOrder = "sequential" | "by-completion";
42
43
  * Returns true when milestones.status = 'complete' in project gsd.db.
43
44
  */
44
45
  export function isMilestoneCompleteInProjectDb(basePath: string, mid: string): boolean {
45
- const workRoot = join(basePath, ".gsd", "worktrees", mid);
46
+ const workRoot = worktreePathFor(basePath, mid);
46
47
  const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
47
48
  if (!existsSync(dbPath)) return false;
48
49
 
@@ -65,15 +66,17 @@ export function isMilestoneCompleteInProjectDb(basePath: string, mid: string): b
65
66
  */
66
67
  function discoverDbCompletedMilestones(basePath: string): Set<string> {
67
68
  const completed = new Set<string>();
68
- const worktreeDir = join(basePath, ".gsd", "worktrees");
69
- try {
70
- for (const entry of readdirSync(worktreeDir)) {
71
- if (entry.startsWith("M") && isMilestoneCompleteInProjectDb(basePath, entry)) {
72
- completed.add(entry);
69
+ for (const worktreeDir of worktreesDirs(basePath)) {
70
+ if (!existsSync(worktreeDir)) continue;
71
+ try {
72
+ for (const entry of readdirSync(worktreeDir)) {
73
+ if (entry.startsWith("M") && isMilestoneCompleteInProjectDb(basePath, entry)) {
74
+ completed.add(entry);
75
+ }
73
76
  }
77
+ } catch (e) {
78
+ logWarning("parallel", `readdirSync for completed set failed: ${(e as Error).message}`);
74
79
  }
75
- } catch (e) {
76
- logWarning("parallel", `readdirSync for completed set failed: ${(e as Error).message}`);
77
80
  }
78
81
  return completed;
79
82
  }
@@ -120,7 +123,7 @@ export function determineMergeOrder(
120
123
  title: mid,
121
124
  pid: 0,
122
125
  process: null,
123
- worktreePath: basePath ? join(basePath, ".gsd", "worktrees", mid) : "",
126
+ worktreePath: basePath ? worktreePathFor(basePath, mid) : "",
124
127
  startedAt: 0,
125
128
  state: "stopped",
126
129
  cost: 0,
@@ -11,6 +11,7 @@ import { matchesKey, Key } from "@gsd/pi-tui";
11
11
  import { formatDuration } from "../shared/mod.js";
12
12
  import { formattedShortcutPair } from "./shortcut-defs.js";
13
13
  import { resolveGsdPathContract } from "./paths.js";
14
+ import { worktreePathFor, worktreesDirs } from "./worktree-placement.js";
14
15
  import {
15
16
  renderBar,
16
17
  renderDialogFrame,
@@ -101,7 +102,6 @@ function tailRead(filePath: string, maxBytes: number): string {
101
102
 
102
103
  function discoverWorkers(basePath: string): string[] {
103
104
  const parallelDir = join(basePath, ".gsd", "parallel");
104
- const worktreeDir = join(basePath, ".gsd", "worktrees");
105
105
  const mids = new Set<string>();
106
106
 
107
107
  if (existsSync(parallelDir)) {
@@ -114,7 +114,8 @@ function discoverWorkers(basePath: string): string[] {
114
114
  } catch { /* skip */ }
115
115
  }
116
116
 
117
- if (existsSync(worktreeDir)) {
117
+ for (const worktreeDir of worktreesDirs(basePath)) {
118
+ if (!existsSync(worktreeDir)) continue;
118
119
  try {
119
120
  for (const d of readdirSync(worktreeDir)) {
120
121
  if (d.startsWith("M") && existsSync(join(worktreeDir, d, ".gsd", "auto.lock"))) {
@@ -127,8 +128,7 @@ function discoverWorkers(basePath: string): string[] {
127
128
  return [...mids].sort();
128
129
  }
129
130
 
130
- function querySliceProgress(basePath: string, mid: string): SliceProgress[] {
131
- const workRoot = join(basePath, ".gsd", "worktrees", mid);
131
+ function querySliceProgress(basePath: string, mid: string, workRoot: string = worktreePathFor(basePath, mid)): SliceProgress[] {
132
132
  const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
133
133
  if (!existsSync(dbPath)) return [];
134
134
 
@@ -169,7 +169,7 @@ function extractCostFromNdjson(basePath: string, mid: string): number {
169
169
  }
170
170
 
171
171
  function queryRecentCompletions(basePath: string, mid: string): string[] {
172
- const workRoot = join(basePath, ".gsd", "worktrees", mid);
172
+ const workRoot = worktreePathFor(basePath, mid);
173
173
  const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
174
174
  if (!existsSync(dbPath)) return [];
175
175
  try {
@@ -192,9 +192,12 @@ function collectWorkerData(basePath: string): WorkerView[] {
192
192
  const workers: WorkerView[] = [];
193
193
 
194
194
  for (const mid of mids) {
195
+ // Resolve the worktree path once per worker per tick — this runs on a
196
+ // 5-second refresh interval and worktreePathFor probes the filesystem.
197
+ const workRoot = worktreePathFor(basePath, mid);
195
198
  const status = readJsonSafe<StatusJson>(join(parallelDir, `${mid}.status.json`));
196
- const lock = readJsonSafe<AutoLock>(join(basePath, ".gsd", "worktrees", mid, ".gsd", "auto.lock"));
197
- const slices = querySliceProgress(basePath, mid);
199
+ const lock = readJsonSafe<AutoLock>(join(workRoot, ".gsd", "auto.lock"));
200
+ const slices = querySliceProgress(basePath, mid, workRoot);
198
201
 
199
202
  const pid = lock?.pid || status?.pid || 0;
200
203
  const alive = pid ? isPidAlive(pid) : false;