@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
@@ -0,0 +1,64 @@
1
+ // gsd-pi - /gsd migrate presentation helpers.
2
+ // File Purpose: Pure formatting for migration previews and post-write review prompts.
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { gsdRoot } from "../paths.js";
7
+ function formatRequirements(preview) {
8
+ if (preview.requirements.total === 0)
9
+ return null;
10
+ return `Requirements: ${preview.requirements.total} (${preview.requirements.validated} validated, ${preview.requirements.active} active, ${preview.requirements.deferred} deferred, ${preview.requirements.outOfScope} out of scope)`;
11
+ }
12
+ function formatLegacyInputs(preview) {
13
+ if (!preview.migrationInputs)
14
+ return null;
15
+ return `Legacy inputs: ${preview.migrationInputs.milestonePhaseDirs} milestone phase dir(s), ${preview.migrationInputs.decisions} decision file(s), ${preview.migrationInputs.seeds} seed file(s)`;
16
+ }
17
+ export function buildMigrationPreviewSummary(preview, targetRoot) {
18
+ const lines = [
19
+ `Decisions: ${preview.decisions.total}`,
20
+ `Milestones: ${preview.milestoneCount}`,
21
+ `Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
22
+ `Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
23
+ ];
24
+ const requirements = formatRequirements(preview);
25
+ if (requirements)
26
+ lines.push(requirements);
27
+ const legacyInputs = formatLegacyInputs(preview);
28
+ if (legacyInputs)
29
+ lines.push(legacyInputs);
30
+ const targetGsdPath = gsdRoot(targetRoot);
31
+ if (existsSync(targetGsdPath)) {
32
+ lines.push("");
33
+ lines.push(`⚠ A .gsd directory already exists at ${targetGsdPath}.`);
34
+ lines.push("It will be backed up, deleted, and rewritten fresh before DB import.");
35
+ }
36
+ return lines;
37
+ }
38
+ export function formatMigrationPreviewStats(preview) {
39
+ const lines = [
40
+ `- Decisions: ${preview.decisions.total}`,
41
+ `- Milestones: ${preview.milestoneCount}`,
42
+ `- Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
43
+ `- Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
44
+ ];
45
+ const requirements = formatRequirements(preview);
46
+ if (requirements)
47
+ lines.push(`- ${requirements}`);
48
+ const legacyInputs = formatLegacyInputs(preview);
49
+ if (legacyInputs)
50
+ lines.push(`- ${legacyInputs}`);
51
+ return lines.join("\n");
52
+ }
53
+ function defaultReviewTemplatePath() {
54
+ const promptsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "prompts");
55
+ return join(promptsDir, "review-migration.md");
56
+ }
57
+ export function buildReviewPrompt(input) {
58
+ const templatePath = input.templatePath ?? defaultReviewTemplatePath();
59
+ let content = readFileSync(templatePath, "utf-8");
60
+ content = content.replaceAll("{{sourcePath}}", input.sourcePath);
61
+ content = content.replaceAll("{{gsdPath}}", input.gsdPath);
62
+ content = content.replaceAll("{{previewStats}}", formatMigrationPreviewStats(input.preview));
63
+ return content.trim();
64
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { deriveState } from "./state.js";
8
8
  import { findMilestoneIds } from "./guided-flow.js";
9
- import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
9
+ import { isDbAvailable, getMilestoneSlices, getTasksBySliceIds } from "./gsd-db.js";
10
10
  import { openExistingWorkflowDatabase } from "./db-workspace.js";
11
11
  // ─── File Collection ─────────────────────────────────────────────────────────
12
12
  /**
@@ -16,10 +16,11 @@ import { openExistingWorkflowDatabase } from "./db-workspace.js";
16
16
  async function collectTouchedFiles(basePath, milestoneId) {
17
17
  const files = new Set();
18
18
  if (isDbAvailable()) {
19
- // DB path: query slices and their tasks for file lists
19
+ // DB path: query slices and their tasks for file lists (one batched task
20
+ // query instead of one per slice; file order is irrelevant here — Set dedup)
20
21
  const slices = getMilestoneSlices(milestoneId);
21
- for (const slice of slices) {
22
- const tasks = getSliceTasks(milestoneId, slice.id);
22
+ const tasksBySlice = getTasksBySliceIds(slices.map((slice) => ({ milestoneId, sliceId: slice.id })));
23
+ for (const tasks of tasksBySlice.values()) {
23
24
  for (const task of tasks) {
24
25
  if (Array.isArray(task.files)) {
25
26
  for (const f of task.files) {
@@ -75,15 +75,18 @@ function discoverWorkers(basePath) {
75
75
  }
76
76
  return [...mids].sort();
77
77
  }
78
- function querySliceProgress(basePath, mid, workRoot = worktreePathFor(basePath, mid)) {
78
+ // Slice progress + recent completions share one DB connection per worker per
79
+ // tick. Opening the isolated worker DB twice per refresh (once for each) was
80
+ // pure churn on the 5s interval.
81
+ function queryWorkerProgress(basePath, mid, workRoot = worktreePathFor(basePath, mid)) {
79
82
  const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
80
83
  if (!existsSync(dbPath))
81
- return [];
84
+ return { slices: [], completions: [] };
82
85
  const db = openWorkflowDatabaseIsolated(dbPath);
83
86
  if (!db)
84
- return [];
87
+ return { slices: [], completions: [] };
85
88
  try {
86
- const rows = db.prepare(`SELECT
89
+ const sliceRows = db.prepare(`SELECT
87
90
  s.id AS id,
88
91
  s.status AS status,
89
92
  COUNT(t.id) AS total,
@@ -93,15 +96,29 @@ function querySliceProgress(basePath, mid, workRoot = worktreePathFor(basePath,
93
96
  WHERE s.milestone_id=:mid
94
97
  GROUP BY s.id
95
98
  ORDER BY s.id`).all({ ":mid": mid });
96
- return rows.map((row) => ({
99
+ const slices = sliceRows.map((row) => ({
97
100
  id: String(row["id"] ?? ""),
98
101
  status: String(row["status"] ?? ""),
99
102
  total: Number(row["total"] ?? 0),
100
103
  done: Number(row["done"] ?? 0),
101
104
  }));
105
+ const completionRows = db.prepare(`SELECT id, slice_id, one_liner
106
+ FROM tasks
107
+ WHERE milestone_id=:mid
108
+ AND status='complete'
109
+ AND completed_at IS NOT NULL
110
+ ORDER BY completed_at DESC
111
+ LIMIT 5`).all({ ":mid": mid });
112
+ const completions = completionRows.map((row) => {
113
+ const taskId = String(row["id"] ?? "");
114
+ const sliceId = String(row["slice_id"] ?? "");
115
+ const oneLiner = String(row["one_liner"] ?? "");
116
+ return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
117
+ });
118
+ return { slices, completions };
102
119
  }
103
120
  catch {
104
- return [];
121
+ return { slices: [], completions: [] };
105
122
  }
106
123
  finally {
107
124
  try {
@@ -200,39 +217,6 @@ function extractCostFromNdjson(basePath, mid) {
200
217
  return ndjsonCostCache.get(stdoutPath)?.total ?? 0;
201
218
  }
202
219
  }
203
- function queryRecentCompletions(basePath, mid) {
204
- const workRoot = worktreePathFor(basePath, mid);
205
- const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
206
- if (!existsSync(dbPath))
207
- return [];
208
- const db = openWorkflowDatabaseIsolated(dbPath);
209
- if (!db)
210
- return [];
211
- try {
212
- const rows = db.prepare(`SELECT id, slice_id, one_liner
213
- FROM tasks
214
- WHERE milestone_id=:mid
215
- AND status='complete'
216
- AND completed_at IS NOT NULL
217
- ORDER BY completed_at DESC
218
- LIMIT 5`).all({ ":mid": mid });
219
- return rows.map((row) => {
220
- const taskId = String(row["id"] ?? "");
221
- const sliceId = String(row["slice_id"] ?? "");
222
- const oneLiner = String(row["one_liner"] ?? "");
223
- return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
224
- });
225
- }
226
- catch {
227
- return [];
228
- }
229
- finally {
230
- try {
231
- db.close();
232
- }
233
- catch { /* ignore */ }
234
- }
235
- }
236
220
  function collectWorkerData(basePath) {
237
221
  const mids = discoverWorkers(basePath);
238
222
  const parallelDir = join(basePath, ".gsd", "parallel");
@@ -243,7 +227,7 @@ function collectWorkerData(basePath) {
243
227
  const workRoot = worktreePathFor(basePath, mid);
244
228
  const status = readJsonSafe(join(parallelDir, `${mid}.status.json`));
245
229
  const lock = readJsonSafe(join(workRoot, ".gsd", "auto.lock"));
246
- const slices = querySliceProgress(basePath, mid, workRoot);
230
+ const { slices, completions } = queryWorkerProgress(basePath, mid, workRoot);
247
231
  const pid = lock?.pid || status?.pid || 0;
248
232
  const alive = pid ? isPidAlive(pid) : false;
249
233
  // Heartbeat: prefer status.json if PID matches, else use file mtime
@@ -303,6 +287,7 @@ function collectWorkerData(basePath) {
303
287
  totalSlices: slices.length,
304
288
  doneSlices,
305
289
  slices,
290
+ completions,
306
291
  errors,
307
292
  });
308
293
  }
@@ -355,10 +340,10 @@ export class ParallelMonitorOverlay {
355
340
  if (this.disposed)
356
341
  return;
357
342
  this.workers = collectWorkerData(this.basePath);
358
- // Collect completion events
343
+ // Collect completion events (already read during collectWorkerData's single
344
+ // DB open per worker — no second connection here).
359
345
  for (const wk of this.workers) {
360
- const completions = queryRecentCompletions(this.basePath, wk.mid);
361
- for (const evt of completions) {
346
+ for (const evt of wk.completions) {
362
347
  if (!this.events.includes(evt))
363
348
  this.events.push(evt);
364
349
  }
@@ -585,19 +585,22 @@ function legacyMilestonesHasSubdirs(basePath) {
585
585
  * See the matching TODO in markdown-renderer.ts detectStaleRenders, which
586
586
  * disabled stale-render detection for the same reason.
587
587
  */
588
+ const LEGACY_MILESTONE_RUNTIME_DIRS = new Set(["anchors"]);
588
589
  export function dirIsContentBearingLegacyMilestone(dir) {
589
590
  try {
590
591
  const entries = readdirSync(dir, { withFileTypes: true });
591
592
  // 1. Any non-META regular file → real legacy content.
592
593
  if (entries.some(e => e.isFile() && !e.name.endsWith("-META.json")))
593
594
  return true;
594
- // 2. A non-empty subdirectory → real legacy content (e.g. slices/ with slice dirs).
595
+ // 2. A non-empty non-runtime subdirectory → real legacy content (e.g. slices/ with slice dirs).
595
596
  // An *empty* subdir is treated as scaffolding (e.g. git-service.ts may create
596
597
  // an empty slices/ alongside the integration META file) and must NOT flip the
597
598
  // layout — that is the Bugbot finding this guard addresses.
598
599
  return entries.some(e => {
599
600
  if (!e.isDirectory())
600
601
  return false;
602
+ if (LEGACY_MILESTONE_RUNTIME_DIRS.has(e.name))
603
+ return false;
601
604
  try {
602
605
  return readdirSync(join(dir, e.name)).length > 0;
603
606
  }
@@ -7,11 +7,14 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { gsdRoot } from "./paths.js";
9
9
  function anchorsDir(basePath, milestoneId) {
10
- return join(gsdRoot(basePath), "milestones", milestoneId, "anchors");
10
+ return join(gsdRoot(basePath), "runtime", "anchors", milestoneId);
11
11
  }
12
12
  function anchorPath(basePath, milestoneId, phase) {
13
13
  return join(anchorsDir(basePath, milestoneId), `${phase}.json`);
14
14
  }
15
+ function legacyAnchorPath(basePath, milestoneId, phase) {
16
+ return join(gsdRoot(basePath), "milestones", milestoneId, "anchors", `${phase}.json`);
17
+ }
15
18
  export function writePhaseAnchor(basePath, milestoneId, anchor) {
16
19
  const dir = anchorsDir(basePath, milestoneId);
17
20
  if (!existsSync(dir)) {
@@ -20,7 +23,9 @@ export function writePhaseAnchor(basePath, milestoneId, anchor) {
20
23
  writeFileSync(anchorPath(basePath, milestoneId, anchor.phase), JSON.stringify(anchor, null, 2), "utf-8");
21
24
  }
22
25
  export function readPhaseAnchor(basePath, milestoneId, phase) {
23
- const path = anchorPath(basePath, milestoneId, phase);
26
+ const path = existsSync(anchorPath(basePath, milestoneId, phase))
27
+ ? anchorPath(basePath, milestoneId, phase)
28
+ : legacyAnchorPath(basePath, milestoneId, phase);
24
29
  if (!existsSync(path))
25
30
  return null;
26
31
  try {
@@ -242,11 +242,11 @@ export function checkImportResolution(taskRow, _priorTasks, basePath) {
242
242
  }
243
243
  const imports = extractRelativeImports(source);
244
244
  for (const { importPath, lineNum } of imports) {
245
- // Framework-generated type modules may not exist on disk during post-exec
246
- // checks (typically generated during framework builds/typegen). Don't
247
- // block task completion on these imports.
245
+ // Framework/codegen-generated modules may not exist on disk during
246
+ // post-exec checks. Don't block task completion on these imports.
248
247
  if (/^\.{1,2}\/\+types\//.test(importPath) ||
249
- /^(?:\.{1,2}\/)+(?:[^/]+\/)*\$types(?:$|\/)/.test(importPath)) {
248
+ /^(?:\.{1,2}\/)+(?:[^/]+\/)*\$types(?:$|\/)/.test(importPath) ||
249
+ /^(?:\.{1,2}\/)+(?:[^/]+\/)*_generated(?:$|\/)/.test(importPath)) {
250
250
  continue;
251
251
  }
252
252
  const resolution = resolveImportPath(importPath, file, basePath);
@@ -66,12 +66,12 @@ Then:
66
66
  **Web apps:** when inlined Web App UAT guidance is present, follow it for Playwright scaffolding and browser-capable verification commands.
67
67
  4. For non-trivial slices, plan observability / proof level / integration closure, threat surface, and requirement impact. Omit entirely for simple slices.
68
68
  5. Decompose the slice into tasks that fit one context window each. Every task passed to `gsd_plan_slice` must use the exact keys `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. Put Why / Do / Done-when detail in `description`. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even for one path (for example, `"expectedOutput": ["src/index.ts"]`, never `"expectedOutput": "src/index.ts"`). `expectedOutput` is path-only: list only files the task creates or overwrites, and use `[]` for pure verification tasks. Never list GSD planning artifacts — anything under `.gsd/`, `.planning/`, or `.audits/`, or artifact names like `M001-CONTEXT.md` / `S01-PLAN.md` — in `inputs`, `files`, or `expectedOutput`: their content is preloaded as context, and they are written by workflow tools, not by tasks.
69
- 6. **Persist planning state through `gsd_plan_slice`.** Call it with the full payload. The tool writes to the DB and renders `{{outputPath}}` and `{{slicePath}}/tasks/T##-PLAN.md` automatically. Do NOT rely on direct `PLAN.md` writes.
69
+ 6. **Persist planning state through `gsd_plan_slice`.** Call it with the full payload. The tool writes to the DB and renders `{{outputPath}}` with embedded task planning automatically. Do NOT rely on direct `PLAN.md` writes.
70
70
  7. **Self-audit the plan.** If every task were completed exactly as written, the slice goal/demo should be true. Every must-have maps to a task. Inputs and Expected Output are backtick-wrapped file paths.
71
71
  8. If refinement produced structural decisions that diverge from the sketch, call `gsd_decision_save` for each; the tool persists the decision and regenerates `.gsd/DECISIONS.md`.
72
72
  9. {{commitInstruction}}
73
73
 
74
- The slice directory and tasks/ subdirectory already exist. Do NOT mkdir.
74
+ The slice plan path already exists. Do NOT mkdir.
75
75
 
76
76
  **Autonomous execution:** Do not call `ask_user_questions` or `secure_env_collect`. Document assumptions in the plan.
77
77
 
@@ -173,7 +173,7 @@ export function recordToolCall(toolCallId, toolName, input) {
173
173
  toolCallId,
174
174
  // gsd_exec / gsd_uat_exec carry the script body in `script` (or `code`);
175
175
  // bash-style tools use `command`/`cmd`; gsd_exec_search uses `query`.
176
- command: String(input.command ?? input.script ?? input.cmd ?? input.code ?? input.query ?? ""),
176
+ command: formatExecutionEvidenceCommand(toolName, input),
177
177
  exitCode: -1,
178
178
  outputSnippet: "",
179
179
  timestamp: Date.now(),
@@ -196,6 +196,31 @@ export function recordToolCall(toolCallId, toolName, input) {
196
196
  });
197
197
  }
198
198
  }
199
+ function pickString(input, ...keys) {
200
+ for (const key of keys) {
201
+ const value = input[key];
202
+ if (typeof value === "string" && value.trim().length > 0)
203
+ return value.trim();
204
+ }
205
+ return "";
206
+ }
207
+ function canonicalExecutionToolLabel(toolName) {
208
+ const normalized = toolName.trim().toLowerCase();
209
+ const mcpMatch = normalized.match(/^mcp__.+__(gsd_(?:uat_)?exec(?:_search)?)$/);
210
+ return mcpMatch?.[1] ?? normalized;
211
+ }
212
+ function formatExecutionEvidenceCommand(toolName, input) {
213
+ const body = pickString(input, "command", "script", "cmd", "code", "query");
214
+ const tool = canonicalExecutionToolLabel(toolName);
215
+ const purpose = pickString(input, "purpose");
216
+ const runtime = pickString(input, "runtime").toLowerCase();
217
+ const label = purpose && (tool === "gsd_exec" || tool === "gsd_uat_exec")
218
+ ? `${tool}${runtime ? ` ${runtime}` : ""}: ${purpose}`
219
+ : "";
220
+ if (label && body)
221
+ return `${label}\n${body}`;
222
+ return body || label;
223
+ }
199
224
  /**
200
225
  * Record a tool execution result. Matches the entry by toolCallId (assigned
201
226
  * at dispatch time) and fills in exit code + output. Prior versions matched
@@ -118,16 +118,25 @@ function latestClaimBatch(claimedEvidence) {
118
118
  */
119
119
  function findMatches(claimedCommand, bashCalls) {
120
120
  const normalized = claimedCommand.trim();
121
- // Exact matches first
122
- const exact = bashCalls.filter(b => b.command.trim() === normalized);
121
+ const exact = bashCalls.filter((b) => b.command.trim() === normalized);
122
+ // When an exact run exists, also consider wrapper-equivalent reruns (e.g.
123
+ // `cd ... && <command>`) so a newer pass is not shadowed by a stale exact
124
+ // failure. Do not merge arbitrary containing scripts: their exit code may
125
+ // belong to later work, not to the claimed verification command.
126
+ const scriptWrapped = bashCalls.filter((b) => {
127
+ const command = b.command.trim();
128
+ if (command.length === 0 || command === normalized)
129
+ return false;
130
+ return isWrapperEquivalentCommand(command, normalized);
131
+ });
123
132
  if (exact.length > 0)
124
- return exact;
133
+ return [...exact, ...scriptWrapped];
125
134
  // Substring match: claimed is contained in actual or actual in claimed.
126
135
  // A claimed verification command typically appears verbatim inside a
127
136
  // larger gsd_exec script body (cd prefix, multi-line scripts), so
128
137
  // script-containing-claim is the common direction. Blank-command entries
129
138
  // must be excluded — `"x".includes("")` is true, so they'd match anything.
130
- const substring = bashCalls.filter(b => b.command.trim().length > 0 &&
139
+ const substring = bashCalls.filter((b) => b.command.trim().length > 0 &&
131
140
  (b.command.includes(normalized) || normalized.includes(b.command)));
132
141
  if (substring.length > 0)
133
142
  return substring;
@@ -150,5 +159,21 @@ function findMatches(claimedCommand, bashCalls) {
150
159
  .map((match) => match.call);
151
160
  }
152
161
  function latestMatch(matches) {
153
- return matches.reduce((latest, match) => (match.timestamp > latest.timestamp ? match : latest));
162
+ return matches.reduce((latest, match) => (match.timestamp >= latest.timestamp ? match : latest));
163
+ }
164
+ /** True when `actual` is the same command with only shell wrapper noise. */
165
+ function isWrapperEquivalentCommand(actual, claimed) {
166
+ const claimIndex = actual.lastIndexOf(claimed);
167
+ if (claimIndex < 0)
168
+ return false;
169
+ const prefix = actual.slice(0, claimIndex).trim();
170
+ if (prefix.length > 0 && !/^cd\s+.+\s+&&$/.test(prefix))
171
+ return false;
172
+ const suffix = actual.slice(claimIndex + claimed.length).trim();
173
+ return suffix.length === 0 || isBenignWrapperSuffix(suffix);
174
+ }
175
+ function isBenignWrapperSuffix(suffix) {
176
+ if (/^;\s*echo\s+["']?[A-Z_]*EXIT=\$\?["']?$/.test(suffix))
177
+ return true;
178
+ return /^(?:(?:\d?>>?|&>)\s*\S+|\d?>&\d)(?:\s+(?:(?:\d?>>?|&>)\s*\S+|\d?>&\d))*$/.test(suffix);
154
179
  }
@@ -3,9 +3,10 @@
3
3
  //
4
4
  // gsd-pi's DB is canonical, but .gsd/*.md is the inter-tool contract. When
5
5
  // gsd-core edits a projection file, this handler detects the sha drift vs the
6
- // recorded baseline in .gsd/.compat.json and re-imports the affected entities
7
- // into the DB. The next renderAllFromDb pass re-projects; the write-time
8
- // invalidation hook then refreshes the marker entry, closing the loop.
6
+ // recorded baseline in .gsd/.compat.json and re-imports from markdown with
7
+ // status authority scoped to the affected milestone ids. The next
8
+ // renderAllFromDb pass re-projects; the write-time invalidation hook then
9
+ // refreshes the marker entry, closing the loop.
9
10
  import { existsSync, readFileSync } from "node:fs";
10
11
  import { join } from "node:path";
11
12
  import { computeProjectionSha, readCompatMarker, writeCompatMarker, } from "../../compat/compat-marker.js";
@@ -43,23 +44,36 @@ function detectExternalMarkdownEdit(_state, ctx) {
43
44
  return records;
44
45
  }
45
46
  /**
46
- * Idempotent repair: re-import hierarchy for the affected entities from
47
- * markdown into the DB, then update the marker entry so the next detect pass
48
- * sees the file as expected. migrateHierarchyToDb is itself idempotent
49
- * (upsert), so re-running this repair after a successful one is a no-op.
47
+ * Idempotent repair: re-import the hierarchy from markdown while allowing
48
+ * markdown status authority only for milestones named by the drifted file's
49
+ * marker entities, then update the marker entry so the next detect pass sees
50
+ * the file as expected. migrateHierarchyToDb is itself idempotent (upsert), so
51
+ * re-running this repair after a successful one is a no-op.
50
52
  */
51
53
  async function repairExternalMarkdownEdit(record, ctx) {
52
54
  try {
53
55
  // Dynamic imports break a module-init cycle: this handler ← registry ←
54
56
  // state.ts ← guided-flow.ts ← md-importer.ts ← (this handler's old static
55
57
  // import). Deferring to repair time keeps module load cycle-free.
56
- const { migrateHierarchyToDb } = await import("../../md-importer.js");
58
+ const { migrateHierarchyToDb, milestoneIdsFromEntities } = await import("../../md-importer.js");
57
59
  const { invalidateStateCache } = await import("../../state.js");
58
- // Scoped re-import. migrateHierarchyToDb walks the whole tree but is a
59
- // cheap upsert; per-entity scoping would require decomposing it, which is
60
- // out of scope for v1. The cost is bounded by tree size and runs at most
61
- // once per reconcile pass (MAX_PASSES=2).
62
- migrateHierarchyToDb(ctx.basePath);
60
+ // #027: migrateHierarchyToDb walks the whole tree (a cheap upsert), so
61
+ // without scoping every projection rides along with markdown *status*
62
+ // authority. That lets an unrelated file that is stale from gsd-pi's own
63
+ // miss silently revert a reopened slice/milestone. Scope status authority to
64
+ // exactly the milestone(s) this drifted file projects.
65
+ //
66
+ // Step 1: the marker's `entities` are DB ids (`M001`, `M001/S01`,
67
+ // `M001/S01/T01`), so the milestone id is always the first `/`-segment —
68
+ // layout-independent (legacy `milestones/<MID>/…` and flat `phases/NN-slug/…`
69
+ // both resolve the same way), no projectionPath parsing needed. An empty set
70
+ // (no resolvable entities) preserves DB status everywhere: fail toward
71
+ // protecting the DB, not toward markdown authority.
72
+ const statusAuthoritativeMilestones = milestoneIdsFromEntities(record.entities);
73
+ if (statusAuthoritativeMilestones.size === 0) {
74
+ logWarning("reconcile", `external-markdown-edit: no milestone scope resolved for ${record.projectionPath}; preserving DB status for all milestones`);
75
+ }
76
+ migrateHierarchyToDb(ctx.basePath, { statusAuthoritativeMilestones });
63
77
  invalidateStateCache();
64
78
  }
65
79
  catch (err) {
@@ -3,7 +3,8 @@
3
3
  // Parallel to external-markdown-edit.ts. Detects sha drift between the compat
4
4
  // marker's planning.projections/passthrough and current .planning/ files.
5
5
  //
6
- // Modeled files (projections): re-imported via parsePlanningDirectory → DB.
6
+ // Modeled files (projections): re-imported via parsePlanningDirectory → DB,
7
+ // with markdown status authority scoped to their marker entity milestone ids.
7
8
  // Passthrough files (un-modeled docs): sha refreshed, content untouched.
8
9
  import { existsSync, readFileSync } from "node:fs";
9
10
  import { join } from "node:path";
@@ -115,12 +116,21 @@ async function repairExternalPlanningEdit(record, ctx) {
115
116
  const { parsePlanningDirectory } = await import("../../migrate/parser.js");
116
117
  const { transformToGSD } = await import("../../migrate/transformer.js");
117
118
  const { writeGSDDirectory } = await import("../../migrate/writer.js");
118
- const { migrateHierarchyToDb } = await import("../../md-importer.js");
119
+ const { migrateHierarchyToDb, milestoneIdsFromEntities } = await import("../../md-importer.js");
119
120
  const { invalidateStateCache } = await import("../../state.js");
120
121
  const parsed = await parsePlanningDirectory(join(ctx.basePath, ".planning"));
121
122
  const gsdProject = transformToGSD(parsed);
122
123
  await writeGSDDirectory(gsdProject, ctx.basePath);
123
- migrateHierarchyToDb(ctx.basePath);
124
+ // #027: scope status authority to the milestone(s) this drifted .planning/
125
+ // file projects (first `/`-segment of its DB entity ids), so a stale
126
+ // checkbox in an unrelated projection can't revert a reopened slice/milestone
127
+ // in the DB. Unseeded files carry no entities → empty set → preserve DB
128
+ // status everywhere (fail toward the DB, log the breadcrumb).
129
+ const statusAuthoritativeMilestones = milestoneIdsFromEntities(record.entities);
130
+ if (statusAuthoritativeMilestones.size === 0) {
131
+ logWarning("reconcile", `external-planning-edit: no milestone scope resolved for ${record.projectionPath}; preserving DB status for all milestones`);
132
+ }
133
+ migrateHierarchyToDb(ctx.basePath, { statusAuthoritativeMilestones });
124
134
  invalidateStateCache();
125
135
  }
126
136
  catch (err) {
@@ -50,8 +50,22 @@ export function isTerminalNotice(message) {
50
50
  export function isNonBlockingPauseNotice(message) {
51
51
  return message.includes("idempotent advance: unit already active");
52
52
  }
53
+ /**
54
+ * A picker / next-action menu that could not render in a non-interactive
55
+ * (headless / RPC) session. Both wordings originate from the menu-unavailable
56
+ * helpers (`notifyCommandMenuUnavailable` and `notifyPickerCommandNeedsInteractiveMenu`
57
+ * in next-action-ui.ts / command-feedback.ts). Headless `auto`/`next` cannot
58
+ * answer such a menu, so the run has dead-ended and needs operator intervention.
59
+ * Classify it as blocked so the headless host exits 10 instead of idling
60
+ * forever waiting for a completion signal that never comes. (#1294)
61
+ */
62
+ export function isInteractiveMenuUnavailableNotice(message) {
63
+ return (message.includes("menu could not be shown in this session") ||
64
+ message.includes("did not start:"));
65
+ }
53
66
  export function isBlockedNoticeMessage(message) {
54
67
  return (message.includes("blocked:") ||
55
68
  (isPauseNotice(message) && !isNonBlockingPauseNotice(message)) ||
56
- isManualResolutionNotice(message));
69
+ isManualResolutionNotice(message) ||
70
+ isInteractiveMenuUnavailableNotice(message));
57
71
  }
@@ -6,8 +6,9 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, statSync, unl
6
6
  import { dirname, join } from "node:path";
7
7
  const STALE_THRESHOLD_MS = 60_000; // 60 seconds
8
8
  const DEFAULT_TIMEOUT_MS = 0; // fail fast; sync waits block the JS event loop
9
- function lockFilePath(basePath) {
10
- return join(basePath, ".gsd", "sync.lock");
9
+ const DEFAULT_LOCK_NAME = "sync.lock";
10
+ function lockFilePath(basePath, lockName = DEFAULT_LOCK_NAME) {
11
+ return join(basePath, ".gsd", lockName);
11
12
  }
12
13
  /** True if the given PID is alive in the current process namespace. */
13
14
  function isPidAlive(pid) {
@@ -71,8 +72,8 @@ function tryCreateLockFile(lp, payload) {
71
72
  * I/O, and UI work on Node's single JS thread. The timeout argument remains
72
73
  * accepted for compatibility but is intentionally not used for sync waiting.
73
74
  */
74
- export function acquireSyncLock(basePath, _timeoutMs = DEFAULT_TIMEOUT_MS) {
75
- const lp = lockFilePath(basePath);
75
+ export function acquireSyncLock(basePath, _timeoutMs = DEFAULT_TIMEOUT_MS, lockName = DEFAULT_LOCK_NAME) {
76
+ const lp = lockFilePath(basePath, lockName);
76
77
  const lockData = JSON.stringify({ pid: process.pid, acquired_at: new Date().toISOString() }, null, 2);
77
78
  while (true) {
78
79
  // First try the fast path: atomic create. No check-then-write race.
@@ -82,7 +83,12 @@ export function acquireSyncLock(basePath, _timeoutMs = DEFAULT_TIMEOUT_MS) {
82
83
  }
83
84
  }
84
85
  catch {
85
- /* unexpected fall through to retry */
86
+ // tryCreateLockFile only throws for non-EEXIST errors — a persistently
87
+ // uncreatable lock path (dangling-symlink parent, ENOTDIR, read-only FS).
88
+ // Retrying would spin the loop forever with no progress condition,
89
+ // blocking the JS event loop. Fail open like a contended lock so callers
90
+ // proceed (racy but functional) instead of hanging.
91
+ return { acquired: false };
86
92
  }
87
93
  // File exists. Decide whether to steal (stale + owner dead) or skip.
88
94
  let canSteal = false;
@@ -125,8 +131,8 @@ export function acquireSyncLock(basePath, _timeoutMs = DEFAULT_TIMEOUT_MS) {
125
131
  /**
126
132
  * Release the advisory sync lock. No-op if lock file does not exist.
127
133
  */
128
- export function releaseSyncLock(basePath) {
129
- const lp = lockFilePath(basePath);
134
+ export function releaseSyncLock(basePath, lockName = DEFAULT_LOCK_NAME) {
135
+ const lp = lockFilePath(basePath, lockName);
130
136
  try {
131
137
  if (existsSync(lp)) {
132
138
  unlinkSync(lp);