@opengsd/gsd-pi 1.8.1 → 1.9.0

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 (339) hide show
  1. package/README.md +10 -8
  2. package/dist/cli.js +5 -1
  3. package/dist/headless-events.d.ts +2 -1
  4. package/dist/headless-events.js +11 -3
  5. package/dist/headless-milestone-readiness.d.ts +29 -0
  6. package/dist/headless-milestone-readiness.js +161 -0
  7. package/dist/headless.js +25 -2
  8. package/dist/print-mode-exit.d.ts +12 -0
  9. package/dist/print-mode-exit.js +14 -0
  10. package/dist/resources/.managed-resources-content-hash +1 -1
  11. package/dist/resources/GSD-WORKFLOW.md +16 -47
  12. package/dist/resources/extensions/browser-tools/capture.js +2 -77
  13. package/dist/resources/extensions/browser-tools/screenshot-constraints.js +83 -0
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -2
  15. package/dist/resources/extensions/claude-code-cli/turn-assembler.js +30 -2
  16. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -19
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -0
  19. package/dist/resources/extensions/gsd/auto-prompts.js +35 -1
  20. package/dist/resources/extensions/gsd/auto.js +5 -1
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +78 -4
  22. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +84 -26
  23. package/dist/resources/extensions/gsd/command-feedback.js +16 -12
  24. package/dist/resources/extensions/gsd/compat/compat-marker.js +19 -2
  25. package/dist/resources/extensions/gsd/db/command-queue.js +7 -2
  26. package/dist/resources/extensions/gsd/db/engine.js +51 -16
  27. package/dist/resources/extensions/gsd/db/writers/cascades.js +5 -1
  28. package/dist/resources/extensions/gsd/db/writers/status.js +13 -3
  29. package/dist/resources/extensions/gsd/db-base-schema.js +28 -0
  30. package/dist/resources/extensions/gsd/db-migration-steps.js +30 -0
  31. package/dist/resources/extensions/gsd/db-schema-metadata.js +4 -1
  32. package/dist/resources/extensions/gsd/docs/preferences-reference.md +4 -4
  33. package/dist/resources/extensions/gsd/flat-phase-migration.js +156 -21
  34. package/dist/resources/extensions/gsd/gsd-db.js +187 -45
  35. package/dist/resources/extensions/gsd/guided-flow.js +29 -10
  36. package/dist/resources/extensions/gsd/markdown-renderer.js +16 -2
  37. package/dist/resources/extensions/gsd/md-importer.js +102 -14
  38. package/dist/resources/extensions/gsd/migrate/command.js +19 -162
  39. package/dist/resources/extensions/gsd/migrate/execution.js +98 -0
  40. package/dist/resources/extensions/gsd/migrate/plan.js +49 -0
  41. package/dist/resources/extensions/gsd/migrate/planning-writer.js +12 -17
  42. package/dist/resources/extensions/gsd/migrate/presentation.js +64 -0
  43. package/dist/resources/extensions/gsd/parallel-eligibility.js +5 -4
  44. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +28 -43
  45. package/dist/resources/extensions/gsd/paths.js +4 -1
  46. package/dist/resources/extensions/gsd/phase-anchor.js +7 -2
  47. package/dist/resources/extensions/gsd/post-execution-checks.js +4 -4
  48. package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  49. package/dist/resources/extensions/gsd/safety/evidence-collector.js +26 -1
  50. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +30 -5
  51. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +27 -13
  52. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +13 -3
  53. package/dist/resources/extensions/gsd/stop-notice.js +15 -1
  54. package/dist/resources/extensions/gsd/sync-lock.js +13 -7
  55. package/dist/resources/extensions/gsd/tools/complete-task.js +82 -1
  56. package/dist/resources/extensions/gsd/tools/plan-task.js +13 -15
  57. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +18 -15
  58. package/dist/resources/extensions/gsd/tools/replan-task.js +128 -0
  59. package/dist/resources/extensions/gsd/tools/rework-brief.js +74 -0
  60. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +81 -0
  61. package/dist/resources/extensions/gsd/unit-registry.js +4 -0
  62. package/dist/resources/extensions/gsd/uok/plan-v2.js +19 -3
  63. package/dist/resources/extensions/gsd/worktree-state-projection.js +6 -0
  64. package/dist/resources/extensions/mac-tools/index.js +31 -9
  65. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  66. package/dist/web/standalone/.next/BUILD_ID +1 -1
  67. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  68. package/dist/web/standalone/.next/build-manifest.json +2 -2
  69. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  87. package/dist/web/standalone/.next/server/app/index.html +1 -1
  88. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  89. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  91. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  93. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  94. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  96. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  97. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  99. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  100. package/dist/web/standalone/.next/static/css/3b1ceb7abfb9a230.css +1 -0
  101. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  102. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  103. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  104. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  105. package/dist/web/standalone/package.json +0 -1
  106. package/dist/web-mode.js +31 -3
  107. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  108. package/integrations/hermes/pyproject.toml +1 -1
  109. package/package.json +12 -10
  110. package/packages/cloud-mcp-gateway/package.json +2 -2
  111. package/packages/contracts/dist/workflow.d.ts +14 -0
  112. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  113. package/packages/contracts/dist/workflow.js +16 -0
  114. package/packages/contracts/dist/workflow.js.map +1 -1
  115. package/packages/contracts/package.json +1 -1
  116. package/packages/daemon/dist/cloud-config.d.ts +1 -1
  117. package/packages/daemon/dist/cloud-config.d.ts.map +1 -1
  118. package/packages/daemon/dist/cloud-config.js +5 -1
  119. package/packages/daemon/dist/cloud-config.js.map +1 -1
  120. package/packages/daemon/dist/cloud-config.test.js +51 -1
  121. package/packages/daemon/dist/cloud-config.test.js.map +1 -1
  122. package/packages/daemon/dist/local-tool-executor.d.ts.map +1 -1
  123. package/packages/daemon/dist/local-tool-executor.js +2 -0
  124. package/packages/daemon/dist/local-tool-executor.js.map +1 -1
  125. package/packages/daemon/package.json +6 -6
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/package.json +7 -7
  128. package/packages/gsd-cloud/dist/cli.d.ts +5 -0
  129. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -0
  130. package/packages/gsd-cloud/dist/cli.js +160 -0
  131. package/packages/gsd-cloud/dist/cli.js.map +1 -0
  132. package/packages/gsd-cloud/dist/cloud-config.d.ts +19 -0
  133. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -0
  134. package/packages/gsd-cloud/dist/cloud-config.js +231 -0
  135. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -0
  136. package/packages/gsd-cloud/dist/cloud-config.test.d.ts +2 -0
  137. package/packages/gsd-cloud/dist/cloud-config.test.d.ts.map +1 -0
  138. package/packages/gsd-cloud/dist/cloud-config.test.js +118 -0
  139. package/packages/gsd-cloud/dist/cloud-config.test.js.map +1 -0
  140. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts +2 -0
  141. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.d.ts.map +1 -0
  142. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js +37 -0
  143. package/packages/gsd-cloud/dist/cloud-gateway-lookup.test.js.map +1 -0
  144. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +35 -0
  145. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -0
  146. package/packages/gsd-cloud/dist/cloud-runtime.js +258 -0
  147. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -0
  148. package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts +2 -0
  149. package/packages/gsd-cloud/dist/cloud-runtime.test.d.ts.map +1 -0
  150. package/packages/gsd-cloud/dist/cloud-runtime.test.js +88 -0
  151. package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -0
  152. package/packages/gsd-cloud/dist/cloud-token.d.ts +3 -0
  153. package/packages/gsd-cloud/dist/cloud-token.d.ts.map +1 -0
  154. package/packages/gsd-cloud/dist/cloud-token.js +37 -0
  155. package/packages/gsd-cloud/dist/cloud-token.js.map +1 -0
  156. package/packages/gsd-cloud/dist/config.d.ts +17 -0
  157. package/packages/gsd-cloud/dist/config.d.ts.map +1 -0
  158. package/packages/gsd-cloud/dist/config.js +146 -0
  159. package/packages/gsd-cloud/dist/config.js.map +1 -0
  160. package/packages/gsd-cloud/dist/device-flow.d.ts +24 -0
  161. package/packages/gsd-cloud/dist/device-flow.d.ts.map +1 -0
  162. package/packages/gsd-cloud/dist/device-flow.js +128 -0
  163. package/packages/gsd-cloud/dist/device-flow.js.map +1 -0
  164. package/packages/gsd-cloud/dist/device-flow.test.d.ts +2 -0
  165. package/packages/gsd-cloud/dist/device-flow.test.d.ts.map +1 -0
  166. package/packages/gsd-cloud/dist/device-flow.test.js +155 -0
  167. package/packages/gsd-cloud/dist/device-flow.test.js.map +1 -0
  168. package/packages/gsd-cloud/dist/executors/claude-executor.d.ts +6 -0
  169. package/packages/gsd-cloud/dist/executors/claude-executor.d.ts.map +1 -0
  170. package/packages/gsd-cloud/dist/executors/claude-executor.js +15 -0
  171. package/packages/gsd-cloud/dist/executors/claude-executor.js.map +1 -0
  172. package/packages/gsd-cloud/dist/executors/codex-executor.d.ts +6 -0
  173. package/packages/gsd-cloud/dist/executors/codex-executor.d.ts.map +1 -0
  174. package/packages/gsd-cloud/dist/executors/codex-executor.js +15 -0
  175. package/packages/gsd-cloud/dist/executors/codex-executor.js.map +1 -0
  176. package/packages/gsd-cloud/dist/executors/executor.d.ts +28 -0
  177. package/packages/gsd-cloud/dist/executors/executor.d.ts.map +1 -0
  178. package/packages/gsd-cloud/dist/executors/executor.js +11 -0
  179. package/packages/gsd-cloud/dist/executors/executor.js.map +1 -0
  180. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts +34 -0
  181. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.d.ts.map +1 -0
  182. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js +179 -0
  183. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.js.map +1 -0
  184. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts +2 -0
  185. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.d.ts.map +1 -0
  186. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js +44 -0
  187. package/packages/gsd-cloud/dist/executors/gsd-pi-executor.test.js.map +1 -0
  188. package/packages/gsd-cloud/dist/executors/index.d.ts +17 -0
  189. package/packages/gsd-cloud/dist/executors/index.d.ts.map +1 -0
  190. package/packages/gsd-cloud/dist/executors/index.js +29 -0
  191. package/packages/gsd-cloud/dist/executors/index.js.map +1 -0
  192. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts +36 -0
  193. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.d.ts.map +1 -0
  194. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js +176 -0
  195. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.js.map +1 -0
  196. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts +2 -0
  197. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.d.ts.map +1 -0
  198. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js +34 -0
  199. package/packages/gsd-cloud/dist/executors/mcp-stdio-client.test.js.map +1 -0
  200. package/packages/gsd-cloud/dist/index.d.ts +7 -0
  201. package/packages/gsd-cloud/dist/index.d.ts.map +1 -0
  202. package/packages/gsd-cloud/dist/index.js +8 -0
  203. package/packages/gsd-cloud/dist/index.js.map +1 -0
  204. package/packages/gsd-cloud/dist/inject-gateway.d.ts +16 -0
  205. package/packages/gsd-cloud/dist/inject-gateway.d.ts.map +1 -0
  206. package/packages/gsd-cloud/dist/inject-gateway.js +29 -0
  207. package/packages/gsd-cloud/dist/inject-gateway.js.map +1 -0
  208. package/packages/gsd-cloud/dist/inject-gateway.test.d.ts +2 -0
  209. package/packages/gsd-cloud/dist/inject-gateway.test.d.ts.map +1 -0
  210. package/packages/gsd-cloud/dist/inject-gateway.test.js +41 -0
  211. package/packages/gsd-cloud/dist/inject-gateway.test.js.map +1 -0
  212. package/packages/gsd-cloud/dist/logger.d.ts +25 -0
  213. package/packages/gsd-cloud/dist/logger.d.ts.map +1 -0
  214. package/packages/gsd-cloud/dist/logger.js +72 -0
  215. package/packages/gsd-cloud/dist/logger.js.map +1 -0
  216. package/packages/gsd-cloud/dist/types.d.ts +51 -0
  217. package/packages/gsd-cloud/dist/types.d.ts.map +1 -0
  218. package/packages/gsd-cloud/dist/types.js +9 -0
  219. package/packages/gsd-cloud/dist/types.js.map +1 -0
  220. package/packages/gsd-cloud/package.json +3 -3
  221. package/packages/mcp-server/README.md +8 -0
  222. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  223. package/packages/mcp-server/dist/workflow-tools.js +64 -2
  224. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  225. package/packages/mcp-server/package.json +4 -4
  226. package/packages/native/package.json +1 -1
  227. package/packages/pi-agent-core/package.json +1 -1
  228. package/packages/pi-ai/dist/models.generated.d.ts +60 -0
  229. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  230. package/packages/pi-ai/dist/models.generated.js +54 -0
  231. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  232. package/packages/pi-ai/dist/providers/amazon-bedrock.js +1 -0
  233. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  234. package/packages/pi-ai/dist/providers/anthropic.js +19 -0
  235. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  236. package/packages/pi-ai/package.json +1 -1
  237. package/packages/pi-coding-agent/package.json +7 -7
  238. package/packages/pi-tui/package.json +2 -2
  239. package/packages/rpc-client/package.json +2 -2
  240. package/pkg/package.json +1 -1
  241. package/src/resources/GSD-WORKFLOW.md +16 -47
  242. package/src/resources/extensions/browser-tools/capture.ts +2 -86
  243. package/src/resources/extensions/browser-tools/screenshot-constraints.ts +94 -0
  244. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -1
  245. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +108 -0
  246. package/src/resources/extensions/claude-code-cli/turn-assembler.ts +30 -2
  247. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
  248. package/src/resources/extensions/gsd/auto-dispatch.ts +86 -19
  249. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -0
  250. package/src/resources/extensions/gsd/auto-prompts.ts +38 -1
  251. package/src/resources/extensions/gsd/auto.ts +5 -1
  252. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +91 -4
  253. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -24
  254. package/src/resources/extensions/gsd/command-feedback.ts +19 -12
  255. package/src/resources/extensions/gsd/compat/compat-marker.ts +21 -4
  256. package/src/resources/extensions/gsd/db/command-queue.ts +7 -1
  257. package/src/resources/extensions/gsd/db/engine.ts +53 -17
  258. package/src/resources/extensions/gsd/db/writers/cascades.ts +5 -1
  259. package/src/resources/extensions/gsd/db/writers/status.ts +25 -8
  260. package/src/resources/extensions/gsd/db-base-schema.ts +30 -0
  261. package/src/resources/extensions/gsd/db-migration-steps.ts +32 -0
  262. package/src/resources/extensions/gsd/db-schema-metadata.ts +4 -1
  263. package/src/resources/extensions/gsd/docs/preferences-reference.md +4 -4
  264. package/src/resources/extensions/gsd/flat-phase-migration.ts +160 -19
  265. package/src/resources/extensions/gsd/gsd-db.ts +255 -52
  266. package/src/resources/extensions/gsd/guided-flow.ts +39 -10
  267. package/src/resources/extensions/gsd/markdown-renderer.ts +23 -7
  268. package/src/resources/extensions/gsd/md-importer.ts +109 -11
  269. package/src/resources/extensions/gsd/migrate/command.ts +21 -227
  270. package/src/resources/extensions/gsd/migrate/execution.ts +136 -0
  271. package/src/resources/extensions/gsd/migrate/plan.ts +101 -0
  272. package/src/resources/extensions/gsd/migrate/planning-writer.ts +12 -20
  273. package/src/resources/extensions/gsd/migrate/presentation.ts +83 -0
  274. package/src/resources/extensions/gsd/parallel-eligibility.ts +5 -4
  275. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +37 -40
  276. package/src/resources/extensions/gsd/paths.ts +4 -1
  277. package/src/resources/extensions/gsd/phase-anchor.ts +8 -2
  278. package/src/resources/extensions/gsd/post-execution-checks.ts +4 -4
  279. package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
  280. package/src/resources/extensions/gsd/safety/evidence-collector.ts +28 -1
  281. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +31 -5
  282. package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +30 -13
  283. package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +16 -3
  284. package/src/resources/extensions/gsd/state-reconciliation/types.ts +2 -2
  285. package/src/resources/extensions/gsd/stop-notice.ts +18 -1
  286. package/src/resources/extensions/gsd/sync-lock.ts +14 -6
  287. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +96 -1
  288. package/src/resources/extensions/gsd/tests/command-feedback.test.ts +12 -0
  289. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +104 -0
  290. package/src/resources/extensions/gsd/tests/complete-task.test.ts +286 -0
  291. package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +186 -0
  292. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +29 -0
  293. package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +123 -0
  294. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +50 -0
  295. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +49 -0
  296. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +81 -0
  297. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +22 -0
  298. package/src/resources/extensions/gsd/tests/external-edit-scoped-import.test.ts +262 -0
  299. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +202 -0
  300. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +29 -0
  301. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +25 -0
  302. package/src/resources/extensions/gsd/tests/import-slice-milestone-completion-preserve.test.ts +116 -0
  303. package/src/resources/extensions/gsd/tests/migrate-plan.test.ts +68 -0
  304. package/src/resources/extensions/gsd/tests/migrate-presentation.test.ts +98 -0
  305. package/src/resources/extensions/gsd/tests/migrate-safety-audit.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +55 -0
  307. package/src/resources/extensions/gsd/tests/parallel-eligibility-file-overlap.test.ts +146 -0
  308. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +59 -0
  309. package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +32 -4
  310. package/src/resources/extensions/gsd/tests/plan-task.test.ts +26 -23
  311. package/src/resources/extensions/gsd/tests/planning-writer.test.ts +29 -0
  312. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +22 -0
  313. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +50 -0
  314. package/src/resources/extensions/gsd/tests/replan-task-handler.test.ts +103 -0
  315. package/src/resources/extensions/gsd/tests/rework-brief.test.ts +197 -0
  316. package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +21 -0
  317. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +17 -0
  318. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +19 -0
  319. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +34 -0
  320. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +93 -1
  321. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -0
  322. package/src/resources/extensions/gsd/tests/workflow-migration-gates.test.ts +139 -0
  323. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +36 -0
  324. package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +52 -0
  325. package/src/resources/extensions/gsd/tools/complete-task.ts +120 -0
  326. package/src/resources/extensions/gsd/tools/plan-task.ts +14 -16
  327. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +18 -17
  328. package/src/resources/extensions/gsd/tools/replan-task.ts +160 -0
  329. package/src/resources/extensions/gsd/tools/rework-brief.ts +91 -0
  330. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +92 -0
  331. package/src/resources/extensions/gsd/types.ts +11 -0
  332. package/src/resources/extensions/gsd/unit-registry.ts +4 -0
  333. package/src/resources/extensions/gsd/uok/plan-v2.ts +20 -3
  334. package/src/resources/extensions/gsd/worktree-state-projection.ts +12 -0
  335. package/src/resources/extensions/mac-tools/index.ts +52 -9
  336. package/src/resources/extensions/mac-tools/tests/mac-screenshot-payload.test.cjs +70 -0
  337. package/dist/web/standalone/.next/static/css/18c2fdf261bfc1b7.css +0 -1
  338. /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → BE5XoklToI6lZjRzpXih8}/_buildManifest.js +0 -0
  339. /package/dist/web/standalone/.next/static/{UaXRrOYxBpG9QBINKhZkg → BE5XoklToI6lZjRzpXih8}/_ssgManifest.js +0 -0
@@ -8,6 +8,7 @@ import { loadJsonFileOrNull } from "../json-persistence.js";
8
8
  import { getIsolationMode } from "../preferences.js";
9
9
  import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
10
10
  import { logWarning } from "../workflow-logger.js";
11
+ import { acquireSyncLock, releaseSyncLock } from "../sync-lock.js";
11
12
  import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
12
13
  import { worktreesDirs } from "../worktree-placement.js";
13
14
  import { bashReferencesProjectRootOutsideWorktree } from "../worktree-shell-guard.js";
@@ -318,12 +319,65 @@ export function refreshWriteGateStateFromDisk(basePath) {
318
319
  }
319
320
  return currentWriteGateSnapshot(basePath);
320
321
  }
322
+ const WRITE_GATE_LOCK_NAME = "write-gate.lock";
323
+ /**
324
+ * Test-only seam: fires once inside the held cross-process lock (after acquire,
325
+ * before the read-merge-write body) with the resolved lock root. Lets a
326
+ * regression test observe that a concurrent process is genuinely locked out of
327
+ * the critical section. Production leaves it null. Follows the repo's
328
+ * `_setGhAvailableForTest` naming convention.
329
+ */
330
+ let interleaveHookForTest = null;
331
+ export function _setWriteGateInterleaveHookForTest(fn) {
332
+ interleaveHookForTest = fn;
333
+ }
334
+ /**
335
+ * Serialize the read-merge-write critical section across the two processes
336
+ * that share the snapshot file (extension host + workflow MCP child). Atomic
337
+ * rename prevents torn files but not lost updates: without this lock the host
338
+ * can read the snapshot, the child can arm a gate and persist, then the host's
339
+ * later persist overwrites `pendingGateId`/`activeQueuePhase` (last-writer-wins
340
+ * fields) and silently drops the armed HARD-BLOCK gate.
341
+ *
342
+ * Fail-OPEN by contract: if a live peer holds the lock we proceed WITHOUT it —
343
+ * exactly the racy-but-functional behavior that shipped before this lock — and
344
+ * log once. The lock shrinks the race window to ~zero; it must never block,
345
+ * throw, or refuse a gate mutation. The lock is skipped entirely when snapshot
346
+ * persistence is off (no disk file, no cross-process seam).
347
+ */
348
+ function withWriteGateLock(basePath, fn) {
349
+ if (!shouldPersistWriteGateSnapshot())
350
+ return fn();
351
+ const lockRoot = resolveWriteGateSnapshotRoot(basePath);
352
+ // Materialize `.gsd/` (including a dangling external-state symlink target)
353
+ // before locking. acquireSyncLock's own mkdir does NOT follow a symlink to
354
+ // create its target, so without this the O_EXCL open lands on a missing
355
+ // parent and the lock silently fails open — leaving the seam unserialized
356
+ // for exactly the external-state projects that need it. This is the same
357
+ // directory the persist path ensures anyway.
358
+ try {
359
+ ensureWriteGateSnapshotDirectory(basePath);
360
+ }
361
+ catch { /* best-effort; acquire fails-open below */ }
362
+ const { acquired } = acquireSyncLock(lockRoot, 0, WRITE_GATE_LOCK_NAME);
363
+ if (!acquired) {
364
+ logWarning("intercept", "write-gate: proceeding without cross-process lock (held by live peer)", { lockRoot });
365
+ return fn();
366
+ }
367
+ try {
368
+ interleaveHookForTest?.(lockRoot);
369
+ return fn();
370
+ }
371
+ finally {
372
+ releaseSyncLock(lockRoot, WRITE_GATE_LOCK_NAME);
373
+ }
374
+ }
321
375
  /**
322
376
  * Read-modify-write primitive for gate mutations. Reconciles the disk
323
377
  * snapshot into memory (union merge), applies the mutation on top, then
324
- * persists. The whole sequence is synchronous, so the reconcile read
325
- * doubles as the pre-persist merge of concurrent writes there is no
326
- * version field; the read-merge-write is simply unconditional.
378
+ * persists all under the cross-process lock (withWriteGateLock). The whole
379
+ * sequence is synchronous, so the reconcile read doubles as the pre-persist
380
+ * merge of concurrent writes; there is no version field.
327
381
  *
328
382
  * The mutate callback sees the already-reconciled state, so policy checks
329
383
  * (e.g. host setPending's verified-on-disk-wins guard) can live inside it
@@ -335,23 +389,25 @@ export function refreshWriteGateStateFromDisk(basePath) {
335
389
  */
336
390
  function mutateWriteGateState(basePath, mutate, opts) {
337
391
  const state = getWriteGateState(basePath);
338
- if (shouldPersistWriteGateSnapshot() && (opts?.reconcile ?? true)) {
339
- const disk = readDiskSnapshot(basePath);
340
- if (disk) {
341
- mergeSnapshotIntoState(state, disk);
342
- }
343
- else {
344
- // Missing OR unparseable on disk: treat as a full reset. Keeping
345
- // stale in-memory state across a corrupt snapshot would persist it
346
- // back on this very mutation and defeat the documented
347
- // "delete the file to clear the HARD BLOCK gate" escape hatch.
348
- replaceStateFromSnapshot(state, EMPTY_SNAPSHOT);
392
+ return withWriteGateLock(basePath, () => {
393
+ if (shouldPersistWriteGateSnapshot() && (opts?.reconcile ?? true)) {
394
+ const disk = readDiskSnapshot(basePath);
395
+ if (disk) {
396
+ mergeSnapshotIntoState(state, disk);
397
+ }
398
+ else {
399
+ // Missing OR unparseable on disk: treat as a full reset. Keeping
400
+ // stale in-memory state across a corrupt snapshot would persist it
401
+ // back on this very mutation and defeat the documented
402
+ // "delete the file to clear the HARD BLOCK gate" escape hatch.
403
+ replaceStateFromSnapshot(state, EMPTY_SNAPSHOT);
404
+ }
349
405
  }
350
- }
351
- if (mutate(state) === false)
352
- return false;
353
- persistWriteGateSnapshot(basePath, opts?.writer ?? defaultWriteGateWriter());
354
- return true;
406
+ if (mutate(state) === false)
407
+ return false;
408
+ persistWriteGateSnapshot(basePath, opts?.writer ?? defaultWriteGateWriter());
409
+ return true;
410
+ });
355
411
  }
356
412
  export function isDepthVerified(basePath = process.cwd()) {
357
413
  return getWriteGateState(basePath).verifiedDepthMilestones.size > 0;
@@ -550,13 +606,15 @@ export const childWriteGateAdapter = {
550
606
  };
551
607
  function childMutate(basePath, mutate) {
552
608
  const state = getWriteGateState(basePath);
553
- if (shouldPersistWriteGateSnapshot()) {
554
- // Always-fresh: disk is the only truth for the child; discard any
555
- // cross-turn in-memory residue before mutating.
556
- replaceStateFromSnapshot(state, readDiskSnapshot(basePath) ?? EMPTY_SNAPSHOT);
557
- }
558
- mutate(state);
559
- persistWriteGateSnapshot(basePath, "child");
609
+ withWriteGateLock(basePath, () => {
610
+ if (shouldPersistWriteGateSnapshot()) {
611
+ // Always-fresh: disk is the only truth for the child; discard any
612
+ // cross-turn in-memory residue before mutating.
613
+ replaceStateFromSnapshot(state, readDiskSnapshot(basePath) ?? EMPTY_SNAPSHOT);
614
+ }
615
+ mutate(state);
616
+ persistWriteGateSnapshot(basePath, "child");
617
+ });
560
618
  }
561
619
  /**
562
620
  * Which adapter the module-level convenience exports (markDepthVerified,
@@ -57,19 +57,23 @@ export function notifyDiscussNeedsInteractiveMenu(ctx, reason) {
57
57
  hints: DISCUSS_HINTS,
58
58
  });
59
59
  }
60
- /** Hub picker for /gsd queue (reorder vs add) is unavailable. */
60
+ /**
61
+ * Hub picker for /gsd queue (reorder vs add) is unavailable.
62
+ *
63
+ * Unlike the other picker-guidance notices, /gsd queue does NOT dead-end here:
64
+ * the caller falls through to the add-work flow, which runs headlessly. So this
65
+ * notice must avoid the "did not start:" picker-failure phrasing — the headless
66
+ * host classifies that as a blocked dead-end (see stop-notice.ts / #1294) and
67
+ * would exit 10 before the add-work run starts.
68
+ */
61
69
  export function notifyQueueHubNeedsInteractiveMenu(ctx, reason) {
62
- notifyPickerCommandNeedsInteractiveMenu(ctx, {
63
- command: "/gsd queue",
64
- reason,
65
- alternatives: [
66
- "Add-work flow can run headless continuing with queue discussion.",
67
- ],
68
- hints: [
69
- "/gsd queue — run from the GSD TUI to reorder pending milestones",
70
- "/gsd status — see milestone order and phase",
71
- ],
72
- });
70
+ ctx.ui.notify([
71
+ `/gsd queue reorder menu needs an interactive session (${reason}).`,
72
+ "Continuing headless with the add-work flow.",
73
+ "",
74
+ " /gsd queue run from the GSD TUI to reorder pending milestones",
75
+ " • /gsd status — see milestone order and phase",
76
+ ].join("\n"), "warning");
73
77
  }
74
78
  /** /gsd, /gsd new-milestone, /gsd new-project wizard menus. */
75
79
  export function notifySmartEntryNeedsInteractiveMenu(ctx, reason) {
@@ -6,7 +6,7 @@
6
6
  // (drift to import). gsd-core is oblivious to this file and ignores it.
7
7
  import { createHash } from "node:crypto";
8
8
  import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
9
- import { dirname, join } from "node:path";
9
+ import { dirname, join, isAbsolute, normalize } from "node:path";
10
10
  /** Current marker schema version. Bump on breaking format changes + migrate. */
11
11
  export const COMPAT_MARKER_SCHEMA = 2;
12
12
  /** Marker returned when no marker exists yet (fresh project, first gsd-pi run). */
@@ -170,10 +170,27 @@ function isValidProjectionEntry(x) {
170
170
  return false;
171
171
  return true;
172
172
  }
173
+ /**
174
+ * Projection-map keys are repo-relative paths under .gsd/ or .planning/.
175
+ * The marker is repo-controlled content, so a key must never escape its
176
+ * root: reject absolute paths, drive letters, backslashes, and any
177
+ * normalized form that starts with "..". Mirrors the write-side guard in
178
+ * db-writer.ts.
179
+ */
180
+ function isSafeProjectionKey(key) {
181
+ if (key.length === 0 || key.includes("\\") || key.includes("\0"))
182
+ return false;
183
+ if (isAbsolute(key) || /^[A-Za-z]:/.test(key))
184
+ return false;
185
+ const norm = normalize(key);
186
+ return norm !== ".." && !norm.startsWith("../") && !isAbsolute(norm);
187
+ }
173
188
  function isValidProjectionMap(x) {
174
189
  if (typeof x !== "object" || x === null)
175
190
  return false;
176
- for (const v of Object.values(x)) {
191
+ for (const [k, v] of Object.entries(x)) {
192
+ if (!isSafeProjectionKey(k))
193
+ return false;
177
194
  if (!isValidProjectionEntry(v))
178
195
  return false;
179
196
  }
@@ -12,7 +12,7 @@
12
12
  // - broadcast queries: WHERE target_worker IS NULL
13
13
  // Workers should poll for both forms (their own ID + broadcasts) on each
14
14
  // claim cycle.
15
- import { _getAdapter, isDbAvailable, transaction, } from "../gsd-db.js";
15
+ import { _getAdapter, immediateTransaction, isDbAvailable, transaction, } from "../gsd-db.js";
16
16
  /**
17
17
  * Enqueue a command. Returns the new row id. Broadcast commands
18
18
  * (targetWorker=null) will be claimed by exactly one worker — the IPC
@@ -47,7 +47,12 @@ export function claimNextCommand(workerId) {
47
47
  return null;
48
48
  const now = new Date().toISOString();
49
49
  const db = _getAdapter();
50
- return transaction(() => {
50
+ // BEGIN IMMEDIATE: this is a read-then-write claim. Under WAL, a deferred
51
+ // transaction that reads first and then upgrades to a writer can fail with
52
+ // SQLITE_BUSY_SNAPSHOT (not retried by busy_timeout) when two workers race;
53
+ // taking the write lock up front serializes claimants so the loser waits
54
+ // and then sees the row already claimed (changes !== 1 → null).
55
+ return immediateTransaction(() => {
51
56
  // Find the oldest unclaimed command targeted at this worker OR
52
57
  // broadcast. The partial index covers both via NULL-in-B-tree.
53
58
  const row = db.prepare(`SELECT id, target_worker, command, args_json, enqueued_at,
@@ -18,7 +18,7 @@ import { createBaseSchemaObjects } from "../db-base-schema.js";
18
18
  import { createCoordinationTablesV24 } from "../db-coordination-schema.js";
19
19
  import { createDbConnectionCache } from "../db-connection-cache.js";
20
20
  import { backupDatabaseBeforeMigration, isMigrationBackupError } from "../db-migration-backup.js";
21
- import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, applyMigrationV29RepositoryTargets, } from "../db-migration-steps.js";
21
+ import { applyMigrationV2Artifacts, applyMigrationV3Memories, applyMigrationV4DecisionMadeBy, applyMigrationV5HierarchyTables, applyMigrationV6SliceSummaries, applyMigrationV7Dependencies, applyMigrationV8PlanningFields, applyMigrationV9Ordering, applyMigrationV10ReplanTrigger, applyMigrationV11TaskPlanning, applyMigrationV12QualityGates, applyMigrationV13HotPathIndexes, applyMigrationV14SliceDependencies, applyMigrationV15AuditTables, applyMigrationV16EscalationSource, applyMigrationV17TaskEscalation, applyMigrationV18MemorySources, applyMigrationV19MemoryFts, applyMigrationV20MemoryRelations, applyMigrationV21StructuredMemories, applyMigrationV22QualityGateRepair, applyMigrationV23MilestoneQueue, applyMigrationV26MilestoneCommitAttributions, applyMigrationV27ArtifactHash, applyMigrationV28MemoryLastHitAt, applyMigrationV29RepositoryTargets, applyMigrationV30ReworkBriefs, } from "../db-migration-steps.js";
22
22
  import { isMemoriesFtsAvailableSchema, rebuildMemoriesFtsSchemaOnce, tryCreateMemoriesFtsSchema, } from "../db-memory-fts-schema.js";
23
23
  import { createDbOpenState } from "../db-open-state.js";
24
24
  import { createRuntimeKvTableV25 } from "../db-runtime-kv-schema.js";
@@ -55,7 +55,7 @@ const providerLoader = createSqliteProviderLoader({
55
55
  nodeVersion: process.versions.node,
56
56
  writeStderr: (message) => process.stderr.write(message),
57
57
  });
58
- export const SCHEMA_VERSION = 29;
58
+ export const SCHEMA_VERSION = 30;
59
59
  function initSchema(db, fileBacked, dbPath) {
60
60
  const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
61
61
  if (fileBacked)
@@ -80,19 +80,41 @@ function initSchema(db, fileBacked, dbPath) {
80
80
  });
81
81
  const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
82
82
  if (existing && existing["cnt"] === 0) {
83
- createCoordinationTablesV24(db);
84
- createRuntimeKvTableV25(db);
85
- // Fresh install all tables are created above with the full current schema,
86
- // so it is safe to create all migration-specific indexes here. For existing
87
- // databases these indexes are created inside the individual migration guards
88
- // in migrateSchema() after the corresponding columns have been added.
89
- db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
90
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
91
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
92
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
93
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
94
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
95
- recordSchemaVersion(db, SCHEMA_VERSION);
83
+ // An empty schema_version table usually means a fresh install, but it can
84
+ // also be a legacy/truncated DB that already holds user data. Stamping
85
+ // that DB SCHEMA_VERSION without running migrations would mis-mark it as
86
+ // fully migrated and break at first query. Probe before stamping.
87
+ const hasData = ["milestones", "decisions", "memories"].some((t) => {
88
+ try {
89
+ const r = db.prepare(`SELECT count(*) as cnt FROM ${t}`).get();
90
+ return (r?.["cnt"] ?? 0) > 0;
91
+ }
92
+ catch { /* table absent on a truly fresh DB — treat as no data */
93
+ return false;
94
+ }
95
+ });
96
+ if (hasData) {
97
+ // Legacy DB with data but no version row: record the baseline so
98
+ // migrateSchema runs the full chain instead of stamping the current version.
99
+ recordSchemaVersion(db, 1);
100
+ }
101
+ else {
102
+ createCoordinationTablesV24(db);
103
+ createRuntimeKvTableV25(db);
104
+ // Fresh install — all tables are created above with the full current schema,
105
+ // so it is safe to create all migration-specific indexes here. For existing
106
+ // databases these indexes are created inside the individual migration guards
107
+ // in migrateSchema() after the corresponding columns have been added.
108
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
109
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
110
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
111
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
112
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
113
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
114
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_briefs_task ON rework_briefs(milestone_id, slice_id, task_id)");
115
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_findings_status ON rework_brief_findings(brief_id, severity, status)");
116
+ recordSchemaVersion(db, SCHEMA_VERSION);
117
+ }
96
118
  }
97
119
  db.exec("COMMIT");
98
120
  }
@@ -143,9 +165,16 @@ function copyQualityGateRowsToRepairedTable(db) {
143
165
  FROM quality_gates
144
166
  `);
145
167
  }
168
+ let _migrationFaultForTest = false;
169
+ /** Test-only: force migrateSchema to throw after applying its steps but before COMMIT. */
170
+ export function _setMigrationFaultForTest(v) { _migrationFaultForTest = v; }
146
171
  function migrateSchema(db, dbPath) {
147
172
  const currentVersion = getCurrentSchemaVersion(db);
148
- if (currentVersion >= SCHEMA_VERSION)
173
+ if (currentVersion > SCHEMA_VERSION) {
174
+ throw new Error(`gsd.db schema is v${currentVersion}, newer than the v${SCHEMA_VERSION} this gsd-pi supports. ` +
175
+ `Update gsd-pi (npm i -g @opengsd/gsd-pi) before opening this project.`);
176
+ }
177
+ if (currentVersion === SCHEMA_VERSION)
149
178
  return;
150
179
  backupDatabaseBeforeMigration(db, dbPath, currentVersion, {
151
180
  existsSync,
@@ -282,6 +311,12 @@ function migrateSchema(db, dbPath) {
282
311
  applyMigrationV29RepositoryTargets(db);
283
312
  recordSchemaVersion(db, 29);
284
313
  }
314
+ if (currentVersion < 30) {
315
+ applyMigrationV30ReworkBriefs(db);
316
+ recordSchemaVersion(db, 30);
317
+ }
318
+ if (_migrationFaultForTest)
319
+ throw new Error("migration fault injected for test");
285
320
  db.exec("COMMIT");
286
321
  }
287
322
  catch (err) {
@@ -172,7 +172,11 @@ export function skipSliceCascade(milestoneId, sliceId) {
172
172
  outcome = { ok: false, reason: "slice-not-found" };
173
173
  return;
174
174
  }
175
- if (slice.status === "complete" || slice.status === "done") {
175
+ // Reject any closed slice except "skipped" (which falls through to the
176
+ // wasAlreadySkipped no-op). isClosedStatus covers the "closed"/"done"
177
+ // aliases too — hand-picking literals here nulls a completed slice's
178
+ // completed_at on re-skip.
179
+ if (isClosedStatus(slice.status) && slice.status !== "skipped") {
176
180
  outcome = { ok: false, reason: "slice-already-complete" };
177
181
  return;
178
182
  }
@@ -37,22 +37,29 @@ function requireDb() {
37
37
  export function applyStatusTransition(t) {
38
38
  const db = requireDb();
39
39
  const completedAt = t.completedAt ?? null;
40
+ const preserve = t.preserveCompletion ? 1 : 0;
40
41
  switch (t.entity) {
41
42
  case "task":
42
- db.prepare(`UPDATE tasks SET status = :status, completed_at = :completed_at
43
+ db.prepare(`UPDATE tasks SET status = :status,
44
+ completed_at = CASE WHEN :preserve_completion = 1 AND tasks.completed_at IS NOT NULL
45
+ THEN tasks.completed_at ELSE :completed_at END
43
46
  WHERE milestone_id = :milestone_id AND slice_id = :slice_id AND id = :id`).run({
44
47
  ":status": t.status,
45
48
  ":completed_at": completedAt,
49
+ ":preserve_completion": preserve,
46
50
  ":milestone_id": t.milestoneId,
47
51
  ":slice_id": t.sliceId,
48
52
  ":id": t.taskId,
49
53
  });
50
54
  return;
51
55
  case "slice":
52
- db.prepare(`UPDATE slices SET status = :status, completed_at = :completed_at
56
+ db.prepare(`UPDATE slices SET status = :status,
57
+ completed_at = CASE WHEN :preserve_completion = 1 AND slices.completed_at IS NOT NULL
58
+ THEN slices.completed_at ELSE :completed_at END
53
59
  WHERE milestone_id = :milestone_id AND id = :id`).run({
54
60
  ":status": t.status,
55
61
  ":completed_at": completedAt,
62
+ ":preserve_completion": preserve,
56
63
  ":milestone_id": t.milestoneId,
57
64
  ":id": t.sliceId,
58
65
  });
@@ -63,7 +70,10 @@ export function applyStatusTransition(t) {
63
70
  if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(t.status)) {
64
71
  throw new Error(`Cannot update closed milestone ${t.milestoneId} from ${currentStatus} to ${t.status}; use gsd_milestone_reopen for an explicit reopen.`);
65
72
  }
66
- db.prepare(`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`).run({ ":status": t.status, ":completed_at": completedAt, ":id": t.milestoneId });
73
+ db.prepare(`UPDATE milestones SET status = :status,
74
+ completed_at = CASE WHEN :preserve_completion = 1 AND milestones.completed_at IS NOT NULL
75
+ THEN milestones.completed_at ELSE :completed_at END
76
+ WHERE id = :id`).run({ ":status": t.status, ":completed_at": completedAt, ":preserve_completion": preserve, ":id": t.milestoneId });
67
77
  return;
68
78
  }
69
79
  }
@@ -221,6 +221,32 @@ export function createBaseSchemaObjects(db, hooks) {
221
221
  created_at TEXT NOT NULL DEFAULT '',
222
222
  FOREIGN KEY (milestone_id) REFERENCES milestones(id)
223
223
  )
224
+ `);
225
+ db.exec(`
226
+ CREATE TABLE IF NOT EXISTS rework_briefs (
227
+ id TEXT PRIMARY KEY,
228
+ milestone_id TEXT NOT NULL DEFAULT '',
229
+ slice_id TEXT NOT NULL DEFAULT '',
230
+ task_id TEXT NOT NULL DEFAULT '',
231
+ created_at TEXT NOT NULL DEFAULT '',
232
+ updated_at TEXT NOT NULL DEFAULT ''
233
+ )
234
+ `);
235
+ db.exec(`
236
+ CREATE TABLE IF NOT EXISTS rework_brief_findings (
237
+ brief_id TEXT NOT NULL,
238
+ finding_id TEXT NOT NULL,
239
+ severity TEXT NOT NULL DEFAULT 'blocking',
240
+ description TEXT NOT NULL DEFAULT '',
241
+ required_fix TEXT NOT NULL DEFAULT '',
242
+ verification_commands TEXT NOT NULL DEFAULT '[]',
243
+ status TEXT NOT NULL DEFAULT 'pending',
244
+ evidence TEXT NOT NULL DEFAULT '',
245
+ decision_ref TEXT NOT NULL DEFAULT '',
246
+ updated_at TEXT NOT NULL DEFAULT '',
247
+ PRIMARY KEY (brief_id, finding_id),
248
+ FOREIGN KEY (brief_id) REFERENCES rework_briefs(id)
249
+ )
224
250
  `);
225
251
  db.exec(`
226
252
  CREATE TABLE IF NOT EXISTS assessments (
@@ -336,6 +362,8 @@ export function createBaseSchemaObjects(db, hooks) {
336
362
  `);
337
363
  db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
338
364
  db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
365
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_briefs_task ON rework_briefs(milestone_id, slice_id, task_id)");
366
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_findings_status ON rework_brief_findings(brief_id, severity, status)");
339
367
  db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
340
368
  db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
341
369
  db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
@@ -418,3 +418,33 @@ export function applyMigrationV22QualityGateRepair(db, hooks) {
418
418
  ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
419
419
  ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
420
420
  }
421
+ export function applyMigrationV30ReworkBriefs(db) {
422
+ db.exec(`
423
+ CREATE TABLE IF NOT EXISTS rework_briefs (
424
+ id TEXT PRIMARY KEY,
425
+ milestone_id TEXT NOT NULL DEFAULT '',
426
+ slice_id TEXT NOT NULL DEFAULT '',
427
+ task_id TEXT NOT NULL DEFAULT '',
428
+ created_at TEXT NOT NULL DEFAULT '',
429
+ updated_at TEXT NOT NULL DEFAULT ''
430
+ )
431
+ `);
432
+ db.exec(`
433
+ CREATE TABLE IF NOT EXISTS rework_brief_findings (
434
+ brief_id TEXT NOT NULL,
435
+ finding_id TEXT NOT NULL,
436
+ severity TEXT NOT NULL DEFAULT 'blocking',
437
+ description TEXT NOT NULL DEFAULT '',
438
+ required_fix TEXT NOT NULL DEFAULT '',
439
+ verification_commands TEXT NOT NULL DEFAULT '[]',
440
+ status TEXT NOT NULL DEFAULT 'pending',
441
+ evidence TEXT NOT NULL DEFAULT '',
442
+ decision_ref TEXT NOT NULL DEFAULT '',
443
+ updated_at TEXT NOT NULL DEFAULT '',
444
+ PRIMARY KEY (brief_id, finding_id),
445
+ FOREIGN KEY (brief_id) REFERENCES rework_briefs(id)
446
+ )
447
+ `);
448
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_briefs_task ON rework_briefs(milestone_id, slice_id, task_id)");
449
+ db.exec("CREATE INDEX IF NOT EXISTS idx_rework_findings_status ON rework_brief_findings(brief_id, severity, status)");
450
+ }
@@ -12,8 +12,11 @@ export function ensureColumn(db, table, column, ddl) {
12
12
  db.exec(ddl);
13
13
  }
14
14
  export function getCurrentSchemaVersion(db) {
15
+ // MAX(version) on an empty table returns a row whose value is NULL —
16
+ // treat that (and any non-numeric value) as version 0, not NULL-as-number.
15
17
  const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get();
16
- return row ? row["v"] : 0;
18
+ const v = row?.["v"];
19
+ return typeof v === "number" ? v : 0;
17
20
  }
18
21
  export function recordSchemaVersion(db, version) {
19
22
  db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
@@ -128,7 +128,7 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
128
128
  - Two equivalent ways to set it: inline as `models.<phase>.thinking`, or as a separate `thinking:` block keyed by phase. For the same phase, the inline value wins over the block; project preferences win over global; a phase that's unset inherits via the same sibling chain as `models` (e.g. `discuss → planning`).
129
129
  - If no thinking is configured for a phase, the session level (set via `/model`) is used, exactly as before — this is fully backward-compatible.
130
130
  - `execute-task` (code-writing) has a measured reasoning floor of `medium`: at lower levels models stop planning edits and thrash on file re-reads. The floor applies to the session/default path. An **explicit** `execution` thinking level bypasses the floor and is honored verbatim (with a one-time advisory) — set `execution: low` deliberately if you want it.
131
- - Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` is only honored by select model families.
131
+ - Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` support is model-dependent; current examples include OpenAI Codex Max models and Claude Sonnet 5 on Amazon Bedrock.
132
132
 
133
133
  - `skill_staleness_days`: number — skills unused for this many days get deprioritized during discovery. Set to `0` to disable staleness tracking. Default: `60`.
134
134
 
@@ -482,13 +482,13 @@ Inline form — thinking pinned alongside the model:
482
482
  version: 1
483
483
  models:
484
484
  planning:
485
- model: openai-codex/gpt-5.4
485
+ model: bedrock/global.anthropic.claude-sonnet-5
486
486
  thinking: xhigh
487
487
  execution:
488
- model: openai-codex/gpt-5.4
488
+ model: bedrock/global.anthropic.claude-sonnet-5
489
489
  thinking: low # explicit → bypasses the execute-task medium floor
490
490
  validation:
491
- model: openai-codex/gpt-5.4
491
+ model: bedrock/global.anthropic.claude-sonnet-5
492
492
  thinking: high
493
493
  ---
494
494
  ```