@opengsd/gsd-pi 1.0.2-dev.867e002 → 1.0.2-dev.cce3612

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 (318) hide show
  1. package/dist/onboarding.js +22 -3
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/context7/index.js +12 -2
  4. package/dist/resources/extensions/get-secrets-from-user.js +16 -16
  5. package/dist/resources/extensions/google-cli/index.js +30 -0
  6. package/dist/resources/extensions/google-cli/models.js +55 -0
  7. package/dist/resources/extensions/google-cli/package.json +11 -0
  8. package/dist/resources/extensions/google-cli/readiness.js +12 -0
  9. package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +62 -1
  11. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +37 -0
  13. package/dist/resources/extensions/gsd/auto/run-unit.js +8 -0
  14. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -7
  16. package/dist/resources/extensions/gsd/auto-post-unit.js +18 -2
  17. package/dist/resources/extensions/gsd/auto-prompts.js +5 -236
  18. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  19. package/dist/resources/extensions/gsd/auto-start.js +232 -49
  20. package/dist/resources/extensions/gsd/auto.js +6 -1
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +39 -5
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -7
  24. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -27
  25. package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
  26. package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
  27. package/dist/resources/extensions/gsd/commands-usage.js +105 -1
  28. package/dist/resources/extensions/gsd/config-overlay.js +20 -14
  29. package/dist/resources/extensions/gsd/context-overlay.js +22 -16
  30. package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
  31. package/dist/resources/extensions/gsd/doctor-engine-checks.js +87 -0
  32. package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
  33. package/dist/resources/extensions/gsd/doctor.js +6 -1
  34. package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
  35. package/dist/resources/extensions/gsd/guided-flow.js +5 -6
  36. package/dist/resources/extensions/gsd/key-manager.js +45 -13
  37. package/dist/resources/extensions/gsd/milestone-reopen-events.js +28 -0
  38. package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
  39. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
  40. package/dist/resources/extensions/gsd/preferences-skills.js +11 -4
  41. package/dist/resources/extensions/gsd/preferences.js +14 -2
  42. package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
  43. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
  44. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/system.md +1 -3
  46. package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
  47. package/dist/resources/extensions/gsd/repository-registry.js +3 -1
  48. package/dist/resources/extensions/gsd/skill-activation.js +233 -0
  49. package/dist/resources/extensions/gsd/skill-catalog.data.js +820 -0
  50. package/dist/resources/extensions/gsd/skill-catalog.install.js +179 -0
  51. package/dist/resources/extensions/gsd/skill-catalog.js +5 -1028
  52. package/dist/resources/extensions/gsd/skill-discovery.js +121 -79
  53. package/dist/resources/extensions/gsd/skill-scope.js +52 -0
  54. package/dist/resources/extensions/gsd/skill-telemetry.js +6 -39
  55. package/dist/resources/extensions/gsd/skills.js +60 -0
  56. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +351 -0
  57. package/dist/resources/extensions/gsd/state-reconciliation/index.js +41 -0
  58. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +4 -0
  59. package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
  60. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +63 -2
  61. package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
  62. package/dist/resources/extensions/gsd/unit-context-manifest.js +35 -26
  63. package/dist/resources/extensions/gsd/user-input-boundary.js +1 -1
  64. package/dist/resources/extensions/gsd/vision-ask.js +22 -0
  65. package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
  66. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
  67. package/dist/resources/extensions/search-the-web/native-search.js +57 -8
  68. package/dist/resources/extensions/shared/confirm-ui.js +9 -6
  69. package/dist/resources/extensions/shared/dialog-frame.js +42 -0
  70. package/dist/resources/extensions/shared/interview-ui.js +42 -30
  71. package/dist/resources/extensions/shared/next-action-ui.js +6 -6
  72. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  73. package/dist/web/standalone/.next/BUILD_ID +1 -1
  74. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  75. package/dist/web/standalone/.next/build-manifest.json +2 -2
  76. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  77. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.html +1 -1
  94. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  101. package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
  102. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  104. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  105. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  106. package/package.json +1 -1
  107. package/packages/cloud-mcp-gateway/package.json +2 -2
  108. package/packages/contracts/package.json +1 -1
  109. package/packages/daemon/package.json +4 -4
  110. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +1 -0
  111. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
  112. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +22 -8
  113. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
  114. package/packages/gsd-agent-core/package.json +5 -5
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
  117. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
  118. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
  119. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  120. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  121. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
  122. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
  123. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
  124. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  125. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
  126. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
  138. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
  140. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
  142. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  144. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  146. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
  147. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  148. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
  149. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  150. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
  151. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
  152. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
  153. package/packages/gsd-agent-modes/package.json +7 -7
  154. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  155. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  156. package/packages/mcp-server/package.json +3 -3
  157. package/packages/native/package.json +1 -1
  158. package/packages/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
  159. package/packages/pi-agent-core/dist/harness/skills.js +6 -0
  160. package/packages/pi-agent-core/dist/harness/skills.js.map +1 -1
  161. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts +7 -0
  162. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts.map +1 -1
  163. package/packages/pi-agent-core/dist/harness/system-prompt.js +7 -0
  164. package/packages/pi-agent-core/dist/harness/system-prompt.js.map +1 -1
  165. package/packages/pi-agent-core/package.json +1 -1
  166. package/packages/pi-ai/dist/models.generated.d.ts +8 -59
  167. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  168. package/packages/pi-ai/dist/models.generated.js +21 -72
  169. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  170. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  171. package/packages/pi-ai/dist/providers/anthropic.js +50 -0
  172. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  173. package/packages/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  174. package/packages/pi-ai/dist/providers/openai-responses-shared.js +28 -4
  175. package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  176. package/packages/pi-ai/dist/types.d.ts +2 -0
  177. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  178. package/packages/pi-ai/dist/types.js.map +1 -1
  179. package/packages/pi-ai/package.json +1 -1
  180. package/packages/pi-coding-agent/README.md +1 -1
  181. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +2 -2
  182. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/extensions/loader.js +1 -1
  185. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/extensions/runner.js +8 -2
  188. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/skills.d.ts +3 -0
  190. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/skills.js +3 -0
  192. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  193. package/packages/pi-coding-agent/package.json +7 -7
  194. package/packages/pi-tui/package.json +1 -1
  195. package/packages/rpc-client/package.json +2 -2
  196. package/pkg/package.json +1 -1
  197. package/src/resources/extensions/context7/index.ts +15 -2
  198. package/src/resources/extensions/get-secrets-from-user.ts +17 -16
  199. package/src/resources/extensions/google-cli/index.ts +34 -0
  200. package/src/resources/extensions/google-cli/models.ts +57 -0
  201. package/src/resources/extensions/google-cli/package.json +11 -0
  202. package/src/resources/extensions/google-cli/readiness.ts +15 -0
  203. package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
  204. package/src/resources/extensions/gsd/auto/loop.ts +74 -1
  205. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -2
  206. package/src/resources/extensions/gsd/auto/phases.ts +46 -0
  207. package/src/resources/extensions/gsd/auto/run-unit.ts +10 -0
  208. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  209. package/src/resources/extensions/gsd/auto-dispatch.ts +31 -11
  210. package/src/resources/extensions/gsd/auto-post-unit.ts +37 -2
  211. package/src/resources/extensions/gsd/auto-prompts.ts +4 -284
  212. package/src/resources/extensions/gsd/auto-recovery.ts +10 -7
  213. package/src/resources/extensions/gsd/auto-start.ts +307 -56
  214. package/src/resources/extensions/gsd/auto.ts +6 -1
  215. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
  216. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +42 -5
  217. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
  218. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -28
  219. package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
  220. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
  221. package/src/resources/extensions/gsd/commands-usage.ts +110 -5
  222. package/src/resources/extensions/gsd/config-overlay.ts +19 -16
  223. package/src/resources/extensions/gsd/context-overlay.ts +24 -19
  224. package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
  225. package/src/resources/extensions/gsd/doctor-engine-checks.ts +99 -0
  226. package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
  227. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  228. package/src/resources/extensions/gsd/doctor.ts +6 -1
  229. package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
  230. package/src/resources/extensions/gsd/guided-flow.ts +5 -6
  231. package/src/resources/extensions/gsd/key-manager.ts +57 -14
  232. package/src/resources/extensions/gsd/milestone-reopen-events.ts +28 -0
  233. package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
  234. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
  235. package/src/resources/extensions/gsd/preferences-skills.ts +11 -4
  236. package/src/resources/extensions/gsd/preferences.ts +17 -2
  237. package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
  238. package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
  239. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  240. package/src/resources/extensions/gsd/prompts/system.md +1 -3
  241. package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
  242. package/src/resources/extensions/gsd/repository-registry.ts +3 -1
  243. package/src/resources/extensions/gsd/skill-activation.ts +292 -0
  244. package/src/resources/extensions/gsd/skill-catalog.data.ts +858 -0
  245. package/src/resources/extensions/gsd/skill-catalog.install.ts +205 -0
  246. package/src/resources/extensions/gsd/skill-catalog.ts +16 -1087
  247. package/src/resources/extensions/gsd/skill-discovery.ts +134 -78
  248. package/src/resources/extensions/gsd/skill-scope.ts +63 -0
  249. package/src/resources/extensions/gsd/skill-telemetry.ts +6 -40
  250. package/src/resources/extensions/gsd/skills.ts +75 -0
  251. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +499 -0
  252. package/src/resources/extensions/gsd/state-reconciliation/index.ts +40 -0
  253. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +8 -0
  254. package/src/resources/extensions/gsd/state-reconciliation/types.ts +30 -0
  255. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +328 -2
  256. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +21 -0
  257. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +28 -2
  258. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +41 -0
  259. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
  260. package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
  261. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
  262. package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
  263. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
  264. package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
  265. package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
  266. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
  267. package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +1 -0
  268. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
  269. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +1 -1
  270. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
  271. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +101 -1
  272. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
  273. package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
  274. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
  275. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
  276. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
  277. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +93 -0
  278. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
  279. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +116 -11
  280. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +30 -1
  281. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
  282. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +111 -0
  283. package/src/resources/extensions/gsd/tests/skill-scope-auto.test.ts +67 -0
  284. package/src/resources/extensions/gsd/tests/skills.test.ts +55 -0
  285. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
  286. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +303 -0
  287. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +19 -0
  288. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
  289. package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
  290. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
  291. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +18 -0
  292. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +26 -0
  293. package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
  294. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +74 -1
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +82 -0
  297. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
  298. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
  299. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
  300. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
  301. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +82 -5
  302. package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
  303. package/src/resources/extensions/gsd/unit-context-manifest.ts +37 -26
  304. package/src/resources/extensions/gsd/user-input-boundary.ts +1 -1
  305. package/src/resources/extensions/gsd/vision-ask.ts +28 -0
  306. package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
  307. package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
  308. package/src/resources/extensions/search-the-web/native-search.ts +60 -8
  309. package/src/resources/extensions/shared/confirm-ui.ts +8 -12
  310. package/src/resources/extensions/shared/dialog-frame.ts +71 -0
  311. package/src/resources/extensions/shared/interview-ui.ts +43 -42
  312. package/src/resources/extensions/shared/next-action-ui.ts +6 -6
  313. package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
  314. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
  315. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
  316. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  317. /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → orfEoZqDIo6Be_Z9ZFipD}/_buildManifest.js +0 -0
  318. /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → orfEoZqDIo6Be_Z9ZFipD}/_ssgManifest.js +0 -0
@@ -0,0 +1,499 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Fail-closed reconciliation guards for DB/artifact and slice-id drift.
3
+
4
+ import {
5
+ existsSync,
6
+ lstatSync,
7
+ mkdirSync,
8
+ readdirSync,
9
+ renameSync,
10
+ rmSync,
11
+ } from "node:fs";
12
+ import { basename, join } from "node:path";
13
+
14
+ import {
15
+ _getAdapter,
16
+ getAllMilestones,
17
+ getMilestoneSlices,
18
+ getSliceTasks,
19
+ isDbAvailable,
20
+ } from "../../gsd-db.js";
21
+ import { clearParseCache } from "../../files.js";
22
+ import {
23
+ clearPathCache,
24
+ gsdProjectionRoot,
25
+ resolveMilestonePath,
26
+ resolveSliceFile,
27
+ resolveTaskFile,
28
+ } from "../../paths.js";
29
+ import { isClosedStatus } from "../../status-guards.js";
30
+ import { invalidateStateCache } from "../../state.js";
31
+ import type { GSDState } from "../../types.js";
32
+ import { isAfter, latestExplicitReopenAt } from "../../milestone-reopen-events.js";
33
+ import type { DriftContext, DriftHandler, DriftRecord } from "../types.js";
34
+
35
+ type DiskSliceIdDivergenceDrift = Extract<
36
+ DriftRecord,
37
+ { kind: "disk-slice-id-divergence" }
38
+ >;
39
+ type ArtifactDbStatusDivergenceDrift = Extract<
40
+ DriftRecord,
41
+ { kind: "artifact-db-status-divergence" }
42
+ >;
43
+ type CompletedMilestoneReopenedDrift = Extract<
44
+ DriftRecord,
45
+ { kind: "completed-milestone-reopened" }
46
+ >;
47
+
48
+ type ArtifactStatusRow = {
49
+ path: string;
50
+ artifact_type: string;
51
+ milestone_id: string | null;
52
+ slice_id: string | null;
53
+ task_id: string | null;
54
+ imported_at: string | null;
55
+ };
56
+
57
+ type CompletedDispatchRow = {
58
+ started_at: string | null;
59
+ ended_at: string | null;
60
+ };
61
+
62
+ function safeListArtifactRows(milestoneId: string): ArtifactStatusRow[] {
63
+ const adapter = _getAdapter();
64
+ if (!adapter) return [];
65
+ try {
66
+ return adapter
67
+ .prepare(
68
+ `SELECT path, artifact_type, milestone_id, slice_id, task_id, imported_at
69
+ FROM artifacts
70
+ WHERE milestone_id = :mid
71
+ ORDER BY imported_at, path`,
72
+ )
73
+ .all({ ":mid": milestoneId }) as ArtifactStatusRow[];
74
+ } catch {
75
+ return [];
76
+ }
77
+ }
78
+
79
+ function latestCompletedMilestoneDispatch(
80
+ milestoneId: string,
81
+ ): CompletedDispatchRow | null {
82
+ const adapter = _getAdapter();
83
+ if (!adapter) return null;
84
+ try {
85
+ const row = adapter
86
+ .prepare(
87
+ `SELECT started_at, ended_at
88
+ FROM unit_dispatches
89
+ WHERE milestone_id = :mid
90
+ AND unit_type = 'complete-milestone'
91
+ AND unit_id = :mid
92
+ AND status = 'completed'
93
+ ORDER BY COALESCE(ended_at, started_at) DESC, id DESC
94
+ LIMIT 1`,
95
+ )
96
+ .get({ ":mid": milestoneId }) as CompletedDispatchRow | undefined;
97
+ return row ?? null;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ function hasExplicitReopenAfter(
104
+ basePath: string,
105
+ milestoneId: string,
106
+ completedDispatchAt: string | null | undefined,
107
+ ): boolean {
108
+ const reopenAt = latestExplicitReopenAt(basePath, milestoneId);
109
+ if (!reopenAt) return false;
110
+ if (!completedDispatchAt) return true;
111
+ return Date.parse(reopenAt) > Date.parse(completedDispatchAt);
112
+ }
113
+
114
+ function addUniqueDrift(
115
+ drifts: ArtifactDbStatusDivergenceDrift[],
116
+ seen: Set<string>,
117
+ drift: ArtifactDbStatusDivergenceDrift,
118
+ ): void {
119
+ const key = [
120
+ drift.milestoneId,
121
+ drift.sliceId ?? "",
122
+ drift.taskId ?? "",
123
+ drift.artifactType,
124
+ drift.artifactPath ?? "",
125
+ drift.reason,
126
+ ].join("|");
127
+ if (seen.has(key)) return;
128
+ seen.add(key);
129
+ drifts.push(drift);
130
+ }
131
+
132
+ function detectArtifactDbStatusDriftForMilestone(
133
+ basePath: string,
134
+ milestoneId: string,
135
+ ): ArtifactDbStatusDivergenceDrift[] {
136
+ const milestone = getAllMilestones().find((m) => m.id === milestoneId);
137
+ if (!milestone || isClosedStatus(milestone.status)) return [];
138
+
139
+ const latestReopen = latestExplicitReopenAt(basePath, milestoneId);
140
+ const artifacts = safeListArtifactRows(milestoneId).filter((row) =>
141
+ isAfter(row.imported_at, latestReopen),
142
+ );
143
+ const bySlice = new Map(getMilestoneSlices(milestoneId).map((slice) => [slice.id, slice]));
144
+ const drifts: ArtifactDbStatusDivergenceDrift[] = [];
145
+ const seen = new Set<string>();
146
+
147
+ for (const slice of bySlice.values()) {
148
+ if (!isClosedStatus(slice.status)) {
149
+ const diskSummary = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
150
+ if (diskSummary && existsSync(diskSummary)) {
151
+ addUniqueDrift(drifts, seen, {
152
+ kind: "artifact-db-status-divergence",
153
+ milestoneId,
154
+ sliceId: slice.id,
155
+ artifactType: "SUMMARY",
156
+ artifactPath: diskSummary,
157
+ dbStatus: slice.status,
158
+ reason: `slice ${slice.id} has SUMMARY on disk while DB status is ${slice.status}`,
159
+ });
160
+ }
161
+ }
162
+
163
+ const tasks = getSliceTasks(milestoneId, slice.id);
164
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
165
+ const summaryRows = artifacts.filter(
166
+ (row) =>
167
+ row.artifact_type === "SUMMARY" &&
168
+ row.slice_id === slice.id &&
169
+ row.task_id,
170
+ );
171
+
172
+ if (tasks.length === 0 && summaryRows.length > 0) {
173
+ addUniqueDrift(drifts, seen, {
174
+ kind: "artifact-db-status-divergence",
175
+ milestoneId,
176
+ sliceId: slice.id,
177
+ artifactType: "SUMMARY",
178
+ artifactPath: summaryRows[0]?.path,
179
+ dbStatus: "no-db-tasks",
180
+ reason: `slice ${slice.id} has task SUMMARY artifacts but no DB tasks`,
181
+ });
182
+ }
183
+
184
+ for (const row of summaryRows) {
185
+ const task = row.task_id ? taskById.get(row.task_id) : null;
186
+ if (!task) {
187
+ if (tasks.length > 0) {
188
+ addUniqueDrift(drifts, seen, {
189
+ kind: "artifact-db-status-divergence",
190
+ milestoneId,
191
+ sliceId: slice.id,
192
+ taskId: row.task_id ?? undefined,
193
+ artifactType: "SUMMARY",
194
+ artifactPath: row.path,
195
+ dbStatus: "missing-db-task",
196
+ reason: `task ${slice.id}/${row.task_id} has SUMMARY artifact but no DB task`,
197
+ });
198
+ }
199
+ continue;
200
+ }
201
+ if (isClosedStatus(task.status)) continue;
202
+ addUniqueDrift(drifts, seen, {
203
+ kind: "artifact-db-status-divergence",
204
+ milestoneId,
205
+ sliceId: slice.id,
206
+ taskId: row.task_id ?? undefined,
207
+ artifactType: "SUMMARY",
208
+ artifactPath: row.path,
209
+ dbStatus: task.status,
210
+ reason: `task ${slice.id}/${row.task_id} has SUMMARY artifact while DB status is ${task.status}`,
211
+ });
212
+ }
213
+
214
+ for (const task of tasks) {
215
+ if (isClosedStatus(task.status)) continue;
216
+ const diskTaskSummary = resolveTaskFile(
217
+ basePath,
218
+ milestoneId,
219
+ slice.id,
220
+ task.id,
221
+ "SUMMARY",
222
+ );
223
+ if (!diskTaskSummary || !existsSync(diskTaskSummary)) continue;
224
+ addUniqueDrift(drifts, seen, {
225
+ kind: "artifact-db-status-divergence",
226
+ milestoneId,
227
+ sliceId: slice.id,
228
+ taskId: task.id,
229
+ artifactType: "SUMMARY",
230
+ artifactPath: diskTaskSummary,
231
+ dbStatus: task.status,
232
+ reason: `task ${slice.id}/${task.id} has SUMMARY on disk while DB status is ${task.status}`,
233
+ });
234
+ }
235
+ }
236
+
237
+ for (const row of artifacts) {
238
+ if (row.artifact_type !== "SUMMARY" || !row.slice_id || row.task_id) continue;
239
+ const slice = bySlice.get(row.slice_id);
240
+ if (!slice || isClosedStatus(slice.status)) continue;
241
+ addUniqueDrift(drifts, seen, {
242
+ kind: "artifact-db-status-divergence",
243
+ milestoneId,
244
+ sliceId: row.slice_id,
245
+ artifactType: "SUMMARY",
246
+ artifactPath: row.path,
247
+ dbStatus: slice.status,
248
+ reason: `slice ${row.slice_id} has SUMMARY artifact while DB status is ${slice.status}`,
249
+ });
250
+ }
251
+
252
+ return drifts;
253
+ }
254
+
255
+ function classifyDiskOnlySliceDir(
256
+ sliceDir: string,
257
+ ): DiskSliceIdDivergenceDrift["disposition"] {
258
+ let sawScaffold = false;
259
+ const stack = [sliceDir];
260
+
261
+ while (stack.length > 0) {
262
+ const dir = stack.pop()!;
263
+ let entries: string[];
264
+ try {
265
+ entries = readdirSync(dir);
266
+ } catch {
267
+ return "block-meaningful";
268
+ }
269
+ if (entries.length === 0 && dir !== sliceDir) {
270
+ sawScaffold = true;
271
+ continue;
272
+ }
273
+ for (const entry of entries) {
274
+ if (entry === ".DS_Store") continue;
275
+ const full = join(dir, entry);
276
+ let stat;
277
+ try {
278
+ stat = lstatSync(full);
279
+ } catch {
280
+ return "block-meaningful";
281
+ }
282
+ if (stat.isDirectory()) {
283
+ sawScaffold = true;
284
+ stack.push(full);
285
+ continue;
286
+ }
287
+ if (stat.isFile() && stat.size === 0) {
288
+ sawScaffold = true;
289
+ continue;
290
+ }
291
+ return "block-meaningful";
292
+ }
293
+ }
294
+
295
+ return sawScaffold ? "quarantine-scaffold" : "delete-empty";
296
+ }
297
+
298
+ function detectDiskSliceIdDivergenceForMilestone(
299
+ basePath: string,
300
+ milestoneId: string,
301
+ ): DiskSliceIdDivergenceDrift[] {
302
+ const milestonePath = resolveMilestonePath(basePath, milestoneId);
303
+ if (!milestonePath) return [];
304
+
305
+ const slicesDir = join(milestonePath, "slices");
306
+ if (!existsSync(slicesDir)) return [];
307
+
308
+ const knownSliceIds = new Set(getMilestoneSlices(milestoneId).map((slice) => slice.id));
309
+ const drifts: DiskSliceIdDivergenceDrift[] = [];
310
+
311
+ for (const entry of readdirSync(slicesDir)) {
312
+ const sliceDir = join(slicesDir, entry);
313
+ try {
314
+ if (!lstatSync(sliceDir).isDirectory()) continue;
315
+ } catch {
316
+ continue;
317
+ }
318
+ if (entry === "parallel-research") continue;
319
+ if (knownSliceIds.has(entry)) continue;
320
+
321
+ const disposition = classifyDiskOnlySliceDir(sliceDir);
322
+ drifts.push({
323
+ kind: "disk-slice-id-divergence",
324
+ milestoneId,
325
+ sliceId: entry,
326
+ sliceDir,
327
+ disposition,
328
+ reason:
329
+ disposition === "block-meaningful"
330
+ ? `disk-only slice directory ${entry} contains meaningful files and is not in the DB`
331
+ : `disk-only slice directory ${entry} is not in the DB`,
332
+ });
333
+ }
334
+
335
+ return drifts;
336
+ }
337
+
338
+ export function detectArtifactDbDrift(
339
+ _state: GSDState,
340
+ ctx: DriftContext,
341
+ ): Array<
342
+ | DiskSliceIdDivergenceDrift
343
+ | ArtifactDbStatusDivergenceDrift
344
+ | CompletedMilestoneReopenedDrift
345
+ > {
346
+ if (!isDbAvailable()) return [];
347
+
348
+ const drifts: Array<
349
+ | DiskSliceIdDivergenceDrift
350
+ | ArtifactDbStatusDivergenceDrift
351
+ | CompletedMilestoneReopenedDrift
352
+ > = [];
353
+
354
+ for (const milestone of getAllMilestones()) {
355
+ if (isClosedStatus(milestone.status)) continue;
356
+
357
+ const completedDispatch = latestCompletedMilestoneDispatch(milestone.id);
358
+ const completedAt = completedDispatch?.ended_at ?? completedDispatch?.started_at ?? null;
359
+ if (
360
+ completedDispatch &&
361
+ !hasExplicitReopenAfter(ctx.basePath, milestone.id, completedAt)
362
+ ) {
363
+ drifts.push({
364
+ kind: "completed-milestone-reopened",
365
+ milestoneId: milestone.id,
366
+ dbStatus: milestone.status,
367
+ completedDispatchAt: completedAt,
368
+ });
369
+ }
370
+
371
+ drifts.push(...detectArtifactDbStatusDriftForMilestone(ctx.basePath, milestone.id));
372
+ drifts.push(...detectDiskSliceIdDivergenceForMilestone(ctx.basePath, milestone.id));
373
+ }
374
+
375
+ return drifts;
376
+ }
377
+
378
+ function quarantineSliceDir(record: DiskSliceIdDivergenceDrift, basePath: string): void {
379
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
380
+ const quarantineDir = join(
381
+ gsdProjectionRoot(basePath),
382
+ "quarantine",
383
+ "milestones",
384
+ record.milestoneId,
385
+ "slices",
386
+ );
387
+ mkdirSync(quarantineDir, { recursive: true });
388
+ const target = join(quarantineDir, `${basename(record.sliceDir)}-${stamp}`);
389
+ renameSync(record.sliceDir, target);
390
+ }
391
+
392
+ function diskSliceIdDivergenceGuidance(record: DiskSliceIdDivergenceDrift): string {
393
+ const quarantineExample = `.gsd/quarantine/milestones/${record.milestoneId}/slices/${record.sliceId}-manual-review`;
394
+ return (
395
+ `Slice ID drift in ${record.milestoneId}: ${record.reason}. ` +
396
+ "Runtime will not import disk-only slice IDs into the DB. " +
397
+ `Review ${record.sliceDir}. ` +
398
+ `If ${record.sliceId} is stale, move or delete that directory; to preserve it, move it under ${quarantineExample}. ` +
399
+ "If it contains work to keep, copy or merge that content into a DB-backed slice, or explicitly recreate the slice through GSD planning, then remove the disk-only directory. " +
400
+ `After repair, run /gsd doctor ${record.milestoneId}, then resume with /gsd next or /gsd auto.`
401
+ );
402
+ }
403
+
404
+ export function repairArtifactDbDrift(
405
+ record:
406
+ | DiskSliceIdDivergenceDrift
407
+ | ArtifactDbStatusDivergenceDrift
408
+ | CompletedMilestoneReopenedDrift,
409
+ ctx: DriftContext,
410
+ ): void {
411
+ if (record.kind === "disk-slice-id-divergence") {
412
+ if (record.disposition === "delete-empty") {
413
+ rmSync(record.sliceDir, { recursive: true, force: true });
414
+ } else if (record.disposition === "quarantine-scaffold") {
415
+ quarantineSliceDir(record, ctx.basePath);
416
+ } else {
417
+ throw new Error(diskSliceIdDivergenceGuidance(record));
418
+ }
419
+ clearPathCache();
420
+ clearParseCache();
421
+ invalidateStateCache();
422
+ return;
423
+ }
424
+
425
+ if (record.kind === "completed-milestone-reopened") {
426
+ throw new Error(
427
+ `Milestone ${record.milestoneId} has completed complete-milestone dispatch history` +
428
+ ` (${record.completedDispatchAt ?? "time unknown"}) but the DB status is ${record.dbStatus}. ` +
429
+ "Refusing to plan it again without an explicit reopen or recovery.",
430
+ );
431
+ }
432
+
433
+ throw new Error(
434
+ `Artifact/DB status drift in ${record.milestoneId}` +
435
+ `${record.sliceId ? `/${record.sliceId}` : ""}` +
436
+ `${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
437
+ "Runtime will not silently import completion artifacts into DB state; run explicit recovery/repair after review.",
438
+ );
439
+ }
440
+
441
+ export function describeArtifactDbDriftBlocker(
442
+ record:
443
+ | DiskSliceIdDivergenceDrift
444
+ | ArtifactDbStatusDivergenceDrift
445
+ | CompletedMilestoneReopenedDrift,
446
+ ): string | null {
447
+ if (record.kind === "disk-slice-id-divergence") {
448
+ if (record.disposition !== "block-meaningful") return null;
449
+ return diskSliceIdDivergenceGuidance(record);
450
+ }
451
+
452
+ if (record.kind === "completed-milestone-reopened") {
453
+ return (
454
+ `Milestone ${record.milestoneId} has completed complete-milestone dispatch history` +
455
+ ` (${record.completedDispatchAt ?? "time unknown"}) but the DB status is ${record.dbStatus}. ` +
456
+ "Refusing to plan it again without an explicit reopen or recovery."
457
+ );
458
+ }
459
+
460
+ return (
461
+ `Artifact/DB status drift in ${record.milestoneId}` +
462
+ `${record.sliceId ? `/${record.sliceId}` : ""}` +
463
+ `${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
464
+ "Runtime will not silently import completion artifacts into DB state; run explicit recovery/repair after review."
465
+ );
466
+ }
467
+
468
+ export const diskSliceIdDivergenceHandler: DriftHandler<DiskSliceIdDivergenceDrift> = {
469
+ kind: "disk-slice-id-divergence",
470
+ detect: (state, ctx) =>
471
+ detectArtifactDbDrift(state, ctx).filter(
472
+ (record): record is DiskSliceIdDivergenceDrift =>
473
+ record.kind === "disk-slice-id-divergence",
474
+ ),
475
+ blocker: describeArtifactDbDriftBlocker,
476
+ repair: repairArtifactDbDrift,
477
+ };
478
+
479
+ export const artifactDbStatusDivergenceHandler: DriftHandler<ArtifactDbStatusDivergenceDrift> = {
480
+ kind: "artifact-db-status-divergence",
481
+ detect: (state, ctx) =>
482
+ detectArtifactDbDrift(state, ctx).filter(
483
+ (record): record is ArtifactDbStatusDivergenceDrift =>
484
+ record.kind === "artifact-db-status-divergence",
485
+ ),
486
+ blocker: describeArtifactDbDriftBlocker,
487
+ repair: repairArtifactDbDrift,
488
+ };
489
+
490
+ export const completedMilestoneReopenedHandler: DriftHandler<CompletedMilestoneReopenedDrift> = {
491
+ kind: "completed-milestone-reopened",
492
+ detect: (state, ctx) =>
493
+ detectArtifactDbDrift(state, ctx).filter(
494
+ (record): record is CompletedMilestoneReopenedDrift =>
495
+ record.kind === "completed-milestone-reopened",
496
+ ),
497
+ blocker: describeArtifactDbDriftBlocker,
498
+ repair: repairArtifactDbDrift,
499
+ };
@@ -76,6 +76,8 @@ export async function reconcileBeforeDispatch(
76
76
  }
77
77
 
78
78
  const failures: ReconciliationFailureDetail[] = [];
79
+ const blockers: string[] = [];
80
+ let repairedThisPass = false;
79
81
  for (const record of drift) {
80
82
  const handler = registry.find((h) => h.kind === record.kind);
81
83
  if (!handler) {
@@ -87,14 +89,33 @@ export async function reconcileBeforeDispatch(
87
89
  });
88
90
  continue;
89
91
  }
92
+ const blocker = handler.blocker ? await handler.blocker(record, ctx) : null;
93
+ if (blocker) {
94
+ blockers.push(blocker);
95
+ continue;
96
+ }
90
97
  try {
91
98
  await handler.repair(record, ctx);
92
99
  repaired.push(record);
100
+ repairedThisPass = true;
93
101
  } catch (cause) {
94
102
  failures.push({ drift: record, cause });
95
103
  }
96
104
  }
97
105
 
106
+ if (blockers.length > 0) {
107
+ let blockerState = stateSnapshot;
108
+ if (repairedThisPass) {
109
+ deps.invalidateStateCache();
110
+ blockerState = await deps.deriveState(basePath, deps.deriveStateOptions);
111
+ }
112
+ return {
113
+ ok: true,
114
+ stateSnapshot: blockerState,
115
+ repaired,
116
+ blockers: [...new Set([...(blockerState.blockers ?? []), ...blockers])],
117
+ };
118
+ }
98
119
  if (failures.length > 0) {
99
120
  throw new ReconciliationFailedError({ failures, pass });
100
121
  }
@@ -108,6 +129,25 @@ export async function reconcileBeforeDispatch(
108
129
  const persistent = await detectAllDrift(finalState, finalCtx, registry);
109
130
 
110
131
  if (persistent.length > 0) {
132
+ const blockers: string[] = [];
133
+ const unblockedPersistent: DriftRecord[] = [];
134
+ for (const record of persistent) {
135
+ const handler = registry.find((h) => h.kind === record.kind);
136
+ const blocker = handler?.blocker ? await handler.blocker(record, finalCtx) : null;
137
+ if (blocker) {
138
+ blockers.push(blocker);
139
+ } else {
140
+ unblockedPersistent.push(record);
141
+ }
142
+ }
143
+ if (blockers.length > 0 && unblockedPersistent.length === 0) {
144
+ return {
145
+ ok: true,
146
+ stateSnapshot: finalState,
147
+ repaired,
148
+ blockers: [...new Set([...(finalState.blockers ?? []), ...blockers])],
149
+ };
150
+ }
111
151
  throw new ReconciliationFailedError({ persistentDrift: persistent });
112
152
  }
113
153
 
@@ -3,6 +3,11 @@
3
3
  // the catalog. Tests can override per-call via ReconciliationDeps.registry.
4
4
 
5
5
  import { completionTimestampHandler } from "./drift/completion.js";
6
+ import {
7
+ artifactDbStatusDivergenceHandler,
8
+ completedMilestoneReopenedHandler,
9
+ diskSliceIdDivergenceHandler,
10
+ } from "./drift/artifact-db.js";
6
11
  import { mergeStateHandler } from "./drift/merge-state.js";
7
12
  import { unregisteredMilestoneHandler } from "./drift/project-md.js";
8
13
  import { roadmapDivergenceHandler } from "./drift/roadmap.js";
@@ -22,6 +27,9 @@ export const DRIFT_REGISTRY: ReadonlyArray<DriftHandler<any>> = [
22
27
  staleRenderHandler,
23
28
  staleWorkerHandler,
24
29
  unregisteredMilestoneHandler,
30
+ diskSliceIdDivergenceHandler,
25
31
  roadmapDivergenceHandler,
32
+ completedMilestoneReopenedHandler,
33
+ artifactDbStatusDivergenceHandler,
26
34
  completionTimestampHandler,
27
35
  ];
@@ -15,6 +15,30 @@ export type DriftRecord =
15
15
  | { kind: "stale-worker"; lockPath: string; pid: number }
16
16
  | { kind: "unregistered-milestone"; milestoneId: string }
17
17
  | { kind: "roadmap-divergence"; milestoneId: string; sliceId?: string }
18
+ | {
19
+ kind: "disk-slice-id-divergence";
20
+ milestoneId: string;
21
+ sliceId: string;
22
+ sliceDir: string;
23
+ disposition: "delete-empty" | "quarantine-scaffold" | "block-meaningful";
24
+ reason: string;
25
+ }
26
+ | {
27
+ kind: "artifact-db-status-divergence";
28
+ milestoneId: string;
29
+ sliceId?: string;
30
+ taskId?: string;
31
+ artifactType: string;
32
+ artifactPath?: string;
33
+ dbStatus?: string;
34
+ reason: string;
35
+ }
36
+ | {
37
+ kind: "completed-milestone-reopened";
38
+ milestoneId: string;
39
+ dbStatus: string;
40
+ completedDispatchAt?: string | null;
41
+ }
18
42
  | {
19
43
  kind: "missing-completion-timestamp";
20
44
  entity: "task" | "slice" | "milestone";
@@ -40,6 +64,12 @@ export interface DriftContext {
40
64
  export interface DriftHandler<T extends DriftRecord = DriftRecord> {
41
65
  kind: T["kind"];
42
66
  detect: (state: GSDState, ctx: DriftContext) => T[] | Promise<T[]>;
67
+ /**
68
+ * Return a terminal blocker message for drift that is intentionally
69
+ * non-repairable in runtime. This lets callers stop cleanly without
70
+ * classifying the condition as a repair exception.
71
+ */
72
+ blocker?: (record: T, ctx: DriftContext) => string | null | Promise<string | null>;
43
73
  repair: (record: T, ctx: DriftContext) => Promise<void> | void;
44
74
  }
45
75