@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,101 @@
1
+ // gsd-pi - /gsd migrate planning service.
2
+ // File Purpose: Resolve, validate, parse, transform, preview, and guard a migration before UI confirmation.
3
+
4
+ import { existsSync } from "node:fs";
5
+
6
+ import {
7
+ assertMigrationHasSlices,
8
+ assertMigrationTargetAvailable,
9
+ resolveMigrationPaths,
10
+ type MigrationPaths,
11
+ } from "./safety.js";
12
+ import { parsePlanningDirectory } from "./parser.js";
13
+ import { generatePreview } from "./preview.js";
14
+ import { transformToGSD } from "./transformer.js";
15
+ import { validatePlanningDirectory } from "./validator.js";
16
+ import type { GSDProject, ValidationIssue, ValidationResult } from "./types.js";
17
+ import type { MigrationPreview } from "./writer.js";
18
+
19
+ export interface SplitValidationIssues {
20
+ warnings: ValidationIssue[];
21
+ fatals: ValidationIssue[];
22
+ }
23
+
24
+ interface MigrationPlanBase extends MigrationPaths {
25
+ warnings: ValidationIssue[];
26
+ fatals: ValidationIssue[];
27
+ }
28
+
29
+ export interface MissingMigrationSourceResult extends MigrationPaths {
30
+ status: "missing-source";
31
+ }
32
+
33
+ export interface InvalidMigrationPlanResult extends MigrationPlanBase {
34
+ status: "invalid";
35
+ validation: ValidationResult;
36
+ }
37
+
38
+ export interface BlockedMigrationPlanResult extends MigrationPlanBase {
39
+ status: "blocked";
40
+ validation: ValidationResult;
41
+ message: string;
42
+ }
43
+
44
+ export interface ReadyMigrationPlanResult extends MigrationPlanBase {
45
+ status: "ready";
46
+ validation: ValidationResult;
47
+ project: GSDProject;
48
+ preview: MigrationPreview;
49
+ }
50
+
51
+ export type MigrationPlanResult =
52
+ | MissingMigrationSourceResult
53
+ | InvalidMigrationPlanResult
54
+ | BlockedMigrationPlanResult
55
+ | ReadyMigrationPlanResult;
56
+
57
+ export function splitValidationIssues(validation: ValidationResult): SplitValidationIssues {
58
+ return {
59
+ warnings: validation.issues.filter((issue) => issue.severity === "warning"),
60
+ fatals: validation.issues.filter((issue) => issue.severity === "fatal"),
61
+ };
62
+ }
63
+
64
+ export async function createMigrationPlan(args: string, cwd: string = process.cwd()): Promise<MigrationPlanResult> {
65
+ const paths = resolveMigrationPaths(args, cwd);
66
+ if (!existsSync(paths.sourcePath)) {
67
+ return { status: "missing-source", ...paths };
68
+ }
69
+
70
+ const validation = await validatePlanningDirectory(paths.sourcePath);
71
+ const issues = splitValidationIssues(validation);
72
+ if (!validation.valid) {
73
+ return { status: "invalid", ...paths, validation, ...issues };
74
+ }
75
+
76
+ const parsed = await parsePlanningDirectory(paths.sourcePath);
77
+ const project = transformToGSD(parsed);
78
+ const preview = generatePreview(project);
79
+
80
+ try {
81
+ assertMigrationHasSlices(preview);
82
+ await assertMigrationTargetAvailable(paths.targetRoot);
83
+ } catch (error) {
84
+ return {
85
+ status: "blocked",
86
+ ...paths,
87
+ validation,
88
+ ...issues,
89
+ message: error instanceof Error ? error.message : String(error),
90
+ };
91
+ }
92
+
93
+ return {
94
+ status: "ready",
95
+ ...paths,
96
+ validation,
97
+ ...issues,
98
+ project,
99
+ preview,
100
+ };
101
+ }
@@ -186,17 +186,14 @@ export async function writePlanningDirectory(
186
186
  });
187
187
 
188
188
  if (tasks.length === 0) {
189
- // Empty phase write a single placeholder plan so the dir isn't empty.
190
- const planPath = join(phaseDir, `${pad(phaseNum)}-01-PLAN.md`);
191
- await saveFile(
192
- planPath,
193
- formatPlanningPlan(phaseNum, 1, slice.title || slice.id, []),
194
- );
195
- paths.push(planPath);
196
- projectionWrites.push({
197
- relPath: toPlanningRel(planPath),
198
- entities: [`${milestone.id}/${slice.id}`],
199
- });
189
+ // Sketch / undecomposed slice zero tasks. Do NOT emit an ingestible
190
+ // *-PLAN.md here: the reverse transform maps one GSDTask per plan file
191
+ // (transformer.ts mapSlice), so a placeholder would materialize a
192
+ // phantom "Plan NN" task and flip the slice from "needs planning" to
193
+ // "has a planned task", causing auto-mode to skip planning (issue #1285).
194
+ // The phase dir is already created (mkdirSync above) and the slice is
195
+ // listed in ROADMAP.md, so it round-trips with tasks = [] — the correct
196
+ // sketch-slice shape.
200
197
  } else {
201
198
  for (let ti = 0; ti < tasks.length; ti++) {
202
199
  const task = tasks[ti]!;
@@ -223,19 +220,14 @@ export async function writePlanningDirectory(
223
220
  }
224
221
  }
225
222
 
226
- // If no slices produced any phases, write a single empty phase so ROADMAP.md
227
- // isn't blank and the layout is still discoverable.
223
+ // If no slices produced any phases, emit a single empty phase dir + ROADMAP
224
+ // entry so the layout stays discoverable. Do NOT write a placeholder
225
+ // *-PLAN.md — an ingestible plan file would round-trip into a phantom task
226
+ // (see the tasks.length === 0 branch above, issue #1285).
228
227
  if (roadmapEntries.length === 0) {
229
228
  const phaseDirName = `${pad(1)}-milestone`;
230
229
  const phaseDir = join(root, "phases", phaseDirName);
231
230
  mkdirSync(phaseDir, { recursive: true });
232
- const planPath = join(phaseDir, `${pad(1)}-01-PLAN.md`);
233
- await saveFile(planPath, formatPlanningPlan(1, 1, milestones[0]!.title || milestones[0]!.id, []));
234
- paths.push(planPath);
235
- projectionWrites.push({
236
- relPath: toPlanningRel(planPath),
237
- entities: [milestones[0]!.id],
238
- });
239
231
  roadmapEntries.push({ number: 1, title: milestones[0]!.title || milestones[0]!.id, done: false });
240
232
  }
241
233
 
@@ -0,0 +1,83 @@
1
+ // gsd-pi - /gsd migrate presentation helpers.
2
+ // File Purpose: Pure formatting for migration previews and post-write review prompts.
3
+
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import { gsdRoot } from "../paths.js";
9
+ import type { MigrationPreview } from "./writer.js";
10
+
11
+ export interface ReviewPromptInput {
12
+ sourcePath: string;
13
+ gsdPath: string;
14
+ preview: MigrationPreview;
15
+ templatePath?: string;
16
+ }
17
+
18
+ function formatRequirements(preview: MigrationPreview): string | null {
19
+ if (preview.requirements.total === 0) return null;
20
+ return `Requirements: ${preview.requirements.total} (${preview.requirements.validated} validated, ${preview.requirements.active} active, ${preview.requirements.deferred} deferred, ${preview.requirements.outOfScope} out of scope)`;
21
+ }
22
+
23
+ function formatLegacyInputs(preview: MigrationPreview): string | null {
24
+ if (!preview.migrationInputs) return null;
25
+ return `Legacy inputs: ${preview.migrationInputs.milestonePhaseDirs} milestone phase dir(s), ${preview.migrationInputs.decisions} decision file(s), ${preview.migrationInputs.seeds} seed file(s)`;
26
+ }
27
+
28
+ export function buildMigrationPreviewSummary(preview: MigrationPreview, targetRoot: string): string[] {
29
+ const lines: string[] = [
30
+ `Decisions: ${preview.decisions.total}`,
31
+ `Milestones: ${preview.milestoneCount}`,
32
+ `Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
33
+ `Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
34
+ ];
35
+
36
+ const requirements = formatRequirements(preview);
37
+ if (requirements) lines.push(requirements);
38
+
39
+ const legacyInputs = formatLegacyInputs(preview);
40
+ if (legacyInputs) lines.push(legacyInputs);
41
+
42
+ const targetGsdPath = gsdRoot(targetRoot);
43
+ if (existsSync(targetGsdPath)) {
44
+ lines.push("");
45
+ lines.push(`⚠ A .gsd directory already exists at ${targetGsdPath}.`);
46
+ lines.push("It will be backed up, deleted, and rewritten fresh before DB import.");
47
+ }
48
+
49
+ return lines;
50
+ }
51
+
52
+ export function formatMigrationPreviewStats(preview: MigrationPreview): string {
53
+ const lines = [
54
+ `- Decisions: ${preview.decisions.total}`,
55
+ `- Milestones: ${preview.milestoneCount}`,
56
+ `- Slices: ${preview.totalSlices} (${preview.doneSlices} done — ${preview.sliceCompletionPct}%)`,
57
+ `- Tasks: ${preview.totalTasks} (${preview.doneTasks} done — ${preview.taskCompletionPct}%)`,
58
+ ];
59
+
60
+ const requirements = formatRequirements(preview);
61
+ if (requirements) lines.push(`- ${requirements}`);
62
+
63
+ const legacyInputs = formatLegacyInputs(preview);
64
+ if (legacyInputs) lines.push(`- ${legacyInputs}`);
65
+
66
+ return lines.join("\n");
67
+ }
68
+
69
+ function defaultReviewTemplatePath(): string {
70
+ const promptsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "prompts");
71
+ return join(promptsDir, "review-migration.md");
72
+ }
73
+
74
+ export function buildReviewPrompt(input: ReviewPromptInput): string {
75
+ const templatePath = input.templatePath ?? defaultReviewTemplatePath();
76
+ let content = readFileSync(templatePath, "utf-8");
77
+
78
+ content = content.replaceAll("{{sourcePath}}", input.sourcePath);
79
+ content = content.replaceAll("{{gsdPath}}", input.gsdPath);
80
+ content = content.replaceAll("{{previewStats}}", formatMigrationPreviewStats(input.preview));
81
+
82
+ return content.trim();
83
+ }
@@ -8,7 +8,7 @@
8
8
  import { deriveState } from "./state.js";
9
9
  import { resolveMilestoneFile, resolveSliceFile } from "./paths.js";
10
10
  import { findMilestoneIds } from "./guided-flow.js";
11
- import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
11
+ import { isDbAvailable, getMilestoneSlices, getTasksBySliceIds } from "./gsd-db.js";
12
12
  import { openExistingWorkflowDatabase } from "./db-workspace.js";
13
13
  import type { MilestoneRegistryEntry } from "./types.js";
14
14
 
@@ -40,10 +40,11 @@ async function collectTouchedFiles(
40
40
  const files = new Set<string>();
41
41
 
42
42
  if (isDbAvailable()) {
43
- // DB path: query slices and their tasks for file lists
43
+ // DB path: query slices and their tasks for file lists (one batched task
44
+ // query instead of one per slice; file order is irrelevant here — Set dedup)
44
45
  const slices = getMilestoneSlices(milestoneId);
45
- for (const slice of slices) {
46
- const tasks = getSliceTasks(milestoneId, slice.id);
46
+ const tasksBySlice = getTasksBySliceIds(slices.map((slice) => ({ milestoneId, sliceId: slice.id })));
47
+ for (const tasks of tasksBySlice.values()) {
47
48
  for (const task of tasks) {
48
49
  if (Array.isArray(task.files)) {
49
50
  for (const f of task.files) {
@@ -72,6 +72,7 @@ interface WorkerView {
72
72
  totalSlices: number;
73
73
  doneSlices: number;
74
74
  slices: SliceProgress[];
75
+ completions: string[];
75
76
  errors: string[];
76
77
  }
77
78
 
@@ -143,14 +144,21 @@ function discoverWorkers(basePath: string): string[] {
143
144
  return [...mids].sort();
144
145
  }
145
146
 
146
- function querySliceProgress(basePath: string, mid: string, workRoot: string = worktreePathFor(basePath, mid)): SliceProgress[] {
147
+ // Slice progress + recent completions share one DB connection per worker per
148
+ // tick. Opening the isolated worker DB twice per refresh (once for each) was
149
+ // pure churn on the 5s interval.
150
+ function queryWorkerProgress(
151
+ basePath: string,
152
+ mid: string,
153
+ workRoot: string = worktreePathFor(basePath, mid),
154
+ ): { slices: SliceProgress[]; completions: string[] } {
147
155
  const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
148
- if (!existsSync(dbPath)) return [];
156
+ if (!existsSync(dbPath)) return { slices: [], completions: [] };
149
157
 
150
158
  const db = openWorkflowDatabaseIsolated(dbPath);
151
- if (!db) return [];
159
+ if (!db) return { slices: [], completions: [] };
152
160
  try {
153
- const rows = db.prepare(
161
+ const sliceRows = db.prepare(
154
162
  `SELECT
155
163
  s.id AS id,
156
164
  s.status AS status,
@@ -162,14 +170,32 @@ function querySliceProgress(basePath: string, mid: string, workRoot: string = wo
162
170
  GROUP BY s.id
163
171
  ORDER BY s.id`,
164
172
  ).all({ ":mid": mid });
165
- return rows.map((row) => ({
173
+ const slices = sliceRows.map((row) => ({
166
174
  id: String(row["id"] ?? ""),
167
175
  status: String(row["status"] ?? ""),
168
176
  total: Number(row["total"] ?? 0),
169
177
  done: Number(row["done"] ?? 0),
170
178
  }));
179
+
180
+ const completionRows = db.prepare(
181
+ `SELECT id, slice_id, one_liner
182
+ FROM tasks
183
+ WHERE milestone_id=:mid
184
+ AND status='complete'
185
+ AND completed_at IS NOT NULL
186
+ ORDER BY completed_at DESC
187
+ LIMIT 5`,
188
+ ).all({ ":mid": mid });
189
+ const completions = completionRows.map((row) => {
190
+ const taskId = String(row["id"] ?? "");
191
+ const sliceId = String(row["slice_id"] ?? "");
192
+ const oneLiner = String(row["one_liner"] ?? "");
193
+ return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
194
+ });
195
+
196
+ return { slices, completions };
171
197
  } catch {
172
- return [];
198
+ return { slices: [], completions: [] };
173
199
  } finally {
174
200
  try { db.close(); } catch { /* ignore */ }
175
201
  }
@@ -276,36 +302,6 @@ function extractCostFromNdjson(basePath: string, mid: string): number {
276
302
  }
277
303
  }
278
304
 
279
- function queryRecentCompletions(basePath: string, mid: string): string[] {
280
- const workRoot = worktreePathFor(basePath, mid);
281
- const dbPath = resolveGsdPathContract(workRoot, basePath).projectDb;
282
- if (!existsSync(dbPath)) return [];
283
-
284
- const db = openWorkflowDatabaseIsolated(dbPath);
285
- if (!db) return [];
286
- try {
287
- const rows = db.prepare(
288
- `SELECT id, slice_id, one_liner
289
- FROM tasks
290
- WHERE milestone_id=:mid
291
- AND status='complete'
292
- AND completed_at IS NOT NULL
293
- ORDER BY completed_at DESC
294
- LIMIT 5`,
295
- ).all({ ":mid": mid });
296
- return rows.map((row) => {
297
- const taskId = String(row["id"] ?? "");
298
- const sliceId = String(row["slice_id"] ?? "");
299
- const oneLiner = String(row["one_liner"] ?? "");
300
- return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
301
- });
302
- } catch {
303
- return [];
304
- } finally {
305
- try { db.close(); } catch { /* ignore */ }
306
- }
307
- }
308
-
309
305
  function collectWorkerData(basePath: string): WorkerView[] {
310
306
  const mids = discoverWorkers(basePath);
311
307
  const parallelDir = join(basePath, ".gsd", "parallel");
@@ -317,7 +313,7 @@ function collectWorkerData(basePath: string): WorkerView[] {
317
313
  const workRoot = worktreePathFor(basePath, mid);
318
314
  const status = readJsonSafe<StatusJson>(join(parallelDir, `${mid}.status.json`));
319
315
  const lock = readJsonSafe<AutoLock>(join(workRoot, ".gsd", "auto.lock"));
320
- const slices = querySliceProgress(basePath, mid, workRoot);
316
+ const { slices, completions } = queryWorkerProgress(basePath, mid, workRoot);
321
317
 
322
318
  const pid = lock?.pid || status?.pid || 0;
323
319
  const alive = pid ? isPidAlive(pid) : false;
@@ -378,6 +374,7 @@ function collectWorkerData(basePath: string): WorkerView[] {
378
374
  totalSlices: slices.length,
379
375
  doneSlices,
380
376
  slices,
377
+ completions,
381
378
  errors,
382
379
  });
383
380
  }
@@ -443,10 +440,10 @@ export class ParallelMonitorOverlay {
443
440
  if (this.disposed) return;
444
441
  this.workers = collectWorkerData(this.basePath);
445
442
 
446
- // Collect completion events
443
+ // Collect completion events (already read during collectWorkerData's single
444
+ // DB open per worker — no second connection here).
447
445
  for (const wk of this.workers) {
448
- const completions = queryRecentCompletions(this.basePath, wk.mid);
449
- for (const evt of completions) {
446
+ for (const evt of wk.completions) {
450
447
  if (!this.events.includes(evt)) this.events.push(evt);
451
448
  }
452
449
  }
@@ -618,17 +618,20 @@ function legacyMilestonesHasSubdirs(basePath: string): boolean {
618
618
  * See the matching TODO in markdown-renderer.ts detectStaleRenders, which
619
619
  * disabled stale-render detection for the same reason.
620
620
  */
621
+ const LEGACY_MILESTONE_RUNTIME_DIRS = new Set(["anchors"]);
622
+
621
623
  export function dirIsContentBearingLegacyMilestone(dir: string): boolean {
622
624
  try {
623
625
  const entries = readdirSync(dir, { withFileTypes: true });
624
626
  // 1. Any non-META regular file → real legacy content.
625
627
  if (entries.some(e => e.isFile() && !e.name.endsWith("-META.json"))) return true;
626
- // 2. A non-empty subdirectory → real legacy content (e.g. slices/ with slice dirs).
628
+ // 2. A non-empty non-runtime subdirectory → real legacy content (e.g. slices/ with slice dirs).
627
629
  // An *empty* subdir is treated as scaffolding (e.g. git-service.ts may create
628
630
  // an empty slices/ alongside the integration META file) and must NOT flip the
629
631
  // layout — that is the Bugbot finding this guard addresses.
630
632
  return entries.some(e => {
631
633
  if (!e.isDirectory()) return false;
634
+ if (LEGACY_MILESTONE_RUNTIME_DIRS.has(e.name)) return false;
632
635
  try { return readdirSync(join(dir, e.name)).length > 0; } catch { return false; }
633
636
  });
634
637
  } catch {
@@ -19,13 +19,17 @@ export interface PhaseAnchor {
19
19
  }
20
20
 
21
21
  function anchorsDir(basePath: string, milestoneId: string): string {
22
- return join(gsdRoot(basePath), "milestones", milestoneId, "anchors");
22
+ return join(gsdRoot(basePath), "runtime", "anchors", milestoneId);
23
23
  }
24
24
 
25
25
  function anchorPath(basePath: string, milestoneId: string, phase: string): string {
26
26
  return join(anchorsDir(basePath, milestoneId), `${phase}.json`);
27
27
  }
28
28
 
29
+ function legacyAnchorPath(basePath: string, milestoneId: string, phase: string): string {
30
+ return join(gsdRoot(basePath), "milestones", milestoneId, "anchors", `${phase}.json`);
31
+ }
32
+
29
33
  export function writePhaseAnchor(basePath: string, milestoneId: string, anchor: PhaseAnchor): void {
30
34
  const dir = anchorsDir(basePath, milestoneId);
31
35
  if (!existsSync(dir)) {
@@ -35,7 +39,9 @@ export function writePhaseAnchor(basePath: string, milestoneId: string, anchor:
35
39
  }
36
40
 
37
41
  export function readPhaseAnchor(basePath: string, milestoneId: string, phase: string): PhaseAnchor | null {
38
- const path = anchorPath(basePath, milestoneId, phase);
42
+ const path = existsSync(anchorPath(basePath, milestoneId, phase))
43
+ ? anchorPath(basePath, milestoneId, phase)
44
+ : legacyAnchorPath(basePath, milestoneId, phase);
39
45
  if (!existsSync(path)) return null;
40
46
  try {
41
47
  return JSON.parse(readFileSync(path, "utf-8")) as PhaseAnchor;
@@ -318,12 +318,12 @@ export function checkImportResolution(
318
318
  const imports = extractRelativeImports(source);
319
319
 
320
320
  for (const { importPath, lineNum } of imports) {
321
- // Framework-generated type modules may not exist on disk during post-exec
322
- // checks (typically generated during framework builds/typegen). Don't
323
- // block task completion on these imports.
321
+ // Framework/codegen-generated modules may not exist on disk during
322
+ // post-exec checks. Don't block task completion on these imports.
324
323
  if (
325
324
  /^\.{1,2}\/\+types\//.test(importPath) ||
326
- /^(?:\.{1,2}\/)+(?:[^/]+\/)*\$types(?:$|\/)/.test(importPath)
325
+ /^(?:\.{1,2}\/)+(?:[^/]+\/)*\$types(?:$|\/)/.test(importPath) ||
326
+ /^(?:\.{1,2}\/)+(?:[^/]+\/)*_generated(?:$|\/)/.test(importPath)
327
327
  ) {
328
328
  continue;
329
329
  }
@@ -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
 
@@ -232,7 +232,7 @@ export function recordToolCall(toolCallId: string, toolName: string, input: Reco
232
232
  toolCallId,
233
233
  // gsd_exec / gsd_uat_exec carry the script body in `script` (or `code`);
234
234
  // bash-style tools use `command`/`cmd`; gsd_exec_search uses `query`.
235
- command: String(input.command ?? input.script ?? input.cmd ?? input.code ?? input.query ?? ""),
235
+ command: formatExecutionEvidenceCommand(toolName, input),
236
236
  exitCode: -1,
237
237
  outputSnippet: "",
238
238
  timestamp: Date.now(),
@@ -254,6 +254,33 @@ export function recordToolCall(toolCallId: string, toolName: string, input: Reco
254
254
  }
255
255
  }
256
256
 
257
+ function pickString(input: Record<string, unknown>, ...keys: string[]): string {
258
+ for (const key of keys) {
259
+ const value = input[key];
260
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
261
+ }
262
+ return "";
263
+ }
264
+
265
+ function canonicalExecutionToolLabel(toolName: string): string {
266
+ const normalized = toolName.trim().toLowerCase();
267
+ const mcpMatch = normalized.match(/^mcp__.+__(gsd_(?:uat_)?exec(?:_search)?)$/);
268
+ return mcpMatch?.[1] ?? normalized;
269
+ }
270
+
271
+ function formatExecutionEvidenceCommand(toolName: string, input: Record<string, unknown>): string {
272
+ const body = pickString(input, "command", "script", "cmd", "code", "query");
273
+ const tool = canonicalExecutionToolLabel(toolName);
274
+ const purpose = pickString(input, "purpose");
275
+ const runtime = pickString(input, "runtime").toLowerCase();
276
+ const label = purpose && (tool === "gsd_exec" || tool === "gsd_uat_exec")
277
+ ? `${tool}${runtime ? ` ${runtime}` : ""}: ${purpose}`
278
+ : "";
279
+
280
+ if (label && body) return `${label}\n${body}`;
281
+ return body || label;
282
+ }
283
+
257
284
  /**
258
285
  * Record a tool execution result. Matches the entry by toolCallId (assigned
259
286
  * at dispatch time) and fills in exit code + output. Prior versions matched
@@ -161,9 +161,18 @@ function findMatches(
161
161
  ): BashEvidence[] {
162
162
  const normalized = claimedCommand.trim();
163
163
 
164
- // Exact matches first
165
- const exact = bashCalls.filter(b => b.command.trim() === normalized);
166
- if (exact.length > 0) return exact;
164
+ const exact = bashCalls.filter((b) => b.command.trim() === normalized);
165
+
166
+ // When an exact run exists, also consider wrapper-equivalent reruns (e.g.
167
+ // `cd ... && <command>`) so a newer pass is not shadowed by a stale exact
168
+ // failure. Do not merge arbitrary containing scripts: their exit code may
169
+ // belong to later work, not to the claimed verification command.
170
+ const scriptWrapped = bashCalls.filter((b) => {
171
+ const command = b.command.trim();
172
+ if (command.length === 0 || command === normalized) return false;
173
+ return isWrapperEquivalentCommand(command, normalized);
174
+ });
175
+ if (exact.length > 0) return [...exact, ...scriptWrapped];
167
176
 
168
177
  // Substring match: claimed is contained in actual or actual in claimed.
169
178
  // A claimed verification command typically appears verbatim inside a
@@ -171,7 +180,7 @@ function findMatches(
171
180
  // script-containing-claim is the common direction. Blank-command entries
172
181
  // must be excluded — `"x".includes("")` is true, so they'd match anything.
173
182
  const substring = bashCalls.filter(
174
- b => b.command.trim().length > 0 &&
183
+ (b) => b.command.trim().length > 0 &&
175
184
  (b.command.includes(normalized) || normalized.includes(b.command)),
176
185
  );
177
186
  if (substring.length > 0) return substring;
@@ -199,6 +208,23 @@ function findMatches(
199
208
 
200
209
  function latestMatch(matches: readonly BashEvidence[]): BashEvidence {
201
210
  return matches.reduce((latest, match) => (
202
- match.timestamp > latest.timestamp ? match : latest
211
+ match.timestamp >= latest.timestamp ? match : latest
203
212
  ));
204
213
  }
214
+
215
+ /** True when `actual` is the same command with only shell wrapper noise. */
216
+ function isWrapperEquivalentCommand(actual: string, claimed: string): boolean {
217
+ const claimIndex = actual.lastIndexOf(claimed);
218
+ if (claimIndex < 0) return false;
219
+
220
+ const prefix = actual.slice(0, claimIndex).trim();
221
+ if (prefix.length > 0 && !/^cd\s+.+\s+&&$/.test(prefix)) return false;
222
+
223
+ const suffix = actual.slice(claimIndex + claimed.length).trim();
224
+ return suffix.length === 0 || isBenignWrapperSuffix(suffix);
225
+ }
226
+
227
+ function isBenignWrapperSuffix(suffix: string): boolean {
228
+ if (/^;\s*echo\s+["']?[A-Z_]*EXIT=\$\?["']?$/.test(suffix)) return true;
229
+ return /^(?:(?:\d?>>?|&>)\s*\S+|\d?>&\d)(?:\s+(?:(?:\d?>>?|&>)\s*\S+|\d?>&\d))*$/.test(suffix);
230
+ }
@@ -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
 
10
11
  import { existsSync, readFileSync } from "node:fs";
11
12
  import { join } from "node:path";
@@ -59,10 +60,11 @@ function detectExternalMarkdownEdit(
59
60
  }
60
61
 
61
62
  /**
62
- * Idempotent repair: re-import hierarchy for the affected entities from
63
- * markdown into the DB, then update the marker entry so the next detect pass
64
- * sees the file as expected. migrateHierarchyToDb is itself idempotent
65
- * (upsert), so re-running this repair after a successful one is a no-op.
63
+ * Idempotent repair: re-import the hierarchy from markdown while allowing
64
+ * markdown status authority only for milestones named by the drifted file's
65
+ * marker entities, then update the marker entry so the next detect pass sees
66
+ * the file as expected. migrateHierarchyToDb is itself idempotent (upsert), so
67
+ * re-running this repair after a successful one is a no-op.
66
68
  */
67
69
  async function repairExternalMarkdownEdit(
68
70
  record: ExternalMarkdownEditDrift,
@@ -72,13 +74,28 @@ async function repairExternalMarkdownEdit(
72
74
  // Dynamic imports break a module-init cycle: this handler ← registry ←
73
75
  // state.ts ← guided-flow.ts ← md-importer.ts ← (this handler's old static
74
76
  // import). Deferring to repair time keeps module load cycle-free.
75
- const { migrateHierarchyToDb } = await import("../../md-importer.js");
77
+ const { migrateHierarchyToDb, milestoneIdsFromEntities } = await import("../../md-importer.js");
76
78
  const { invalidateStateCache } = await import("../../state.js");
77
- // Scoped re-import. migrateHierarchyToDb walks the whole tree but is a
78
- // cheap upsert; per-entity scoping would require decomposing it, which is
79
- // out of scope for v1. The cost is bounded by tree size and runs at most
80
- // once per reconcile pass (MAX_PASSES=2).
81
- migrateHierarchyToDb(ctx.basePath);
79
+ // #027: migrateHierarchyToDb walks the whole tree (a cheap upsert), so
80
+ // without scoping every projection rides along with markdown *status*
81
+ // authority. That lets an unrelated file that is stale from gsd-pi's own
82
+ // miss silently revert a reopened slice/milestone. Scope status authority to
83
+ // exactly the milestone(s) this drifted file projects.
84
+ //
85
+ // Step 1: the marker's `entities` are DB ids (`M001`, `M001/S01`,
86
+ // `M001/S01/T01`), so the milestone id is always the first `/`-segment —
87
+ // layout-independent (legacy `milestones/<MID>/…` and flat `phases/NN-slug/…`
88
+ // both resolve the same way), no projectionPath parsing needed. An empty set
89
+ // (no resolvable entities) preserves DB status everywhere: fail toward
90
+ // protecting the DB, not toward markdown authority.
91
+ const statusAuthoritativeMilestones = milestoneIdsFromEntities(record.entities);
92
+ if (statusAuthoritativeMilestones.size === 0) {
93
+ logWarning(
94
+ "reconcile",
95
+ `external-markdown-edit: no milestone scope resolved for ${record.projectionPath}; preserving DB status for all milestones`,
96
+ );
97
+ }
98
+ migrateHierarchyToDb(ctx.basePath, { statusAuthoritativeMilestones });
82
99
  invalidateStateCache();
83
100
  } catch (err) {
84
101
  logWarning(