@opengsd/gsd-pi 1.9.0 → 1.10.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 (300) hide show
  1. package/README.md +10 -10
  2. package/dist/headless.js +2 -1
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/GSD-WORKFLOW.md +2 -2
  5. package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
  6. package/dist/resources/extensions/gsd/auto/loop.js +24 -4
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
  8. package/dist/resources/extensions/gsd/auto/unit-phase.js +37 -5
  9. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -8
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
  11. package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
  12. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  13. package/dist/resources/extensions/gsd/auto-runtime-state.js +5 -5
  14. package/dist/resources/extensions/gsd/auto-start.js +29 -9
  15. package/dist/resources/extensions/gsd/auto-tool-tracking.js +40 -0
  16. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
  17. package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
  18. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
  19. package/dist/resources/extensions/gsd/auto.js +15 -5
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -0
  21. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +33 -7
  22. package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
  23. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -24
  24. package/dist/resources/extensions/gsd/commands-verdict.js +10 -9
  25. package/dist/resources/extensions/gsd/compat/compat-marker.js +80 -5
  26. package/dist/resources/extensions/gsd/constants.js +12 -13
  27. package/dist/resources/extensions/gsd/context-masker.js +97 -21
  28. package/dist/resources/extensions/gsd/db/engine.js +5 -2
  29. package/dist/resources/extensions/gsd/docs/preferences-reference.md +22 -0
  30. package/dist/resources/extensions/gsd/doctor-engine-checks.js +24 -8
  31. package/dist/resources/extensions/gsd/error-classifier.js +12 -2
  32. package/dist/resources/extensions/gsd/exec-sandbox.js +1 -0
  33. package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
  34. package/dist/resources/extensions/gsd/git-service.js +19 -7
  35. package/dist/resources/extensions/gsd/gsd-db.js +75 -31
  36. package/dist/resources/extensions/gsd/guided-flow.js +11 -5
  37. package/dist/resources/extensions/gsd/markdown-renderer.js +5 -21
  38. package/dist/resources/extensions/gsd/md-importer.js +5 -6
  39. package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
  40. package/dist/resources/extensions/gsd/model-router.js +11 -0
  41. package/dist/resources/extensions/gsd/paths.js +66 -15
  42. package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
  43. package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
  44. package/dist/resources/extensions/gsd/preferences-types.js +5 -0
  45. package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
  46. package/dist/resources/extensions/gsd/preferences.js +17 -0
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
  48. package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
  49. package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
  50. package/dist/resources/extensions/gsd/skill-activation.js +17 -3
  51. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.js +98 -2
  52. package/dist/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.js +4 -1
  53. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +5 -2
  54. package/dist/resources/extensions/gsd/stop-notice.js +9 -0
  55. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
  56. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -5
  57. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
  58. package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
  59. package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
  60. package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
  61. package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
  62. package/dist/resources/extensions/gsd/workspace.js +2 -1
  63. package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
  64. package/dist/resources/extensions/shared/interview-ui.js +43 -6
  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.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.html +1 -1
  86. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  93. package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
  94. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  95. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  96. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  98. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  99. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  100. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  101. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  102. package/integrations/hermes/pyproject.toml +1 -1
  103. package/package.json +7 -6
  104. package/packages/cloud-mcp-gateway/package.json +2 -2
  105. package/packages/contracts/package.json +1 -1
  106. package/packages/daemon/package.json +4 -4
  107. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  108. package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
  109. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  110. package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
  111. package/packages/gsd-agent-core/dist/sdk.js +1 -0
  112. package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
  113. package/packages/gsd-agent-core/package.json +5 -5
  114. package/packages/gsd-agent-modes/package.json +7 -7
  115. package/packages/gsd-cloud/README.md +9 -3
  116. package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
  117. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
  118. package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
  119. package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
  120. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  121. package/packages/gsd-cloud/dist/cli.js +121 -15
  122. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  123. package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
  124. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
  125. package/packages/gsd-cloud/dist/cloud-config.js +7 -1
  126. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
  127. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
  128. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  129. package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
  130. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  131. package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
  132. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
  133. package/packages/gsd-cloud/dist/runtime-process.js +272 -0
  134. package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
  135. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
  136. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
  137. package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
  138. package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
  139. package/packages/gsd-cloud/package.json +2 -2
  140. package/packages/mcp-server/README.md +7 -2
  141. package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
  142. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
  143. package/packages/mcp-server/dist/cli-runner.js +119 -33
  144. package/packages/mcp-server/dist/cli-runner.js.map +1 -1
  145. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  146. package/packages/mcp-server/dist/server.js +5 -5
  147. package/packages/mcp-server/dist/server.js.map +1 -1
  148. package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
  149. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  150. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  151. package/packages/mcp-server/package.json +4 -4
  152. package/packages/native/package.json +1 -1
  153. package/packages/pi-agent-core/dist/agent.d.ts +5 -1
  154. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  155. package/packages/pi-agent-core/dist/agent.js +2 -0
  156. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  157. package/packages/pi-agent-core/package.json +1 -1
  158. package/packages/pi-ai/dist/models.generated.d.ts +76 -0
  159. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  160. package/packages/pi-ai/dist/models.generated.js +70 -0
  161. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  162. package/packages/pi-ai/dist/providers/anthropic.js +6 -1
  163. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  164. package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
  165. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  166. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  167. package/packages/pi-ai/dist/providers/openai-completions.js +103 -0
  168. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  169. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  170. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  171. package/packages/pi-ai/package.json +1 -1
  172. package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
  173. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
  175. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
  177. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  179. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
  181. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/skills.js +54 -0
  183. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  184. package/packages/pi-coding-agent/package.json +7 -7
  185. package/packages/pi-tui/package.json +2 -2
  186. package/packages/rpc-client/package.json +2 -2
  187. package/pkg/package.json +1 -1
  188. package/src/resources/GSD-WORKFLOW.md +2 -2
  189. package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
  190. package/src/resources/extensions/gsd/auto/loop.ts +48 -4
  191. package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
  192. package/src/resources/extensions/gsd/auto/unit-phase.ts +41 -5
  193. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -7
  194. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
  195. package/src/resources/extensions/gsd/auto-prompts.ts +118 -19
  196. package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
  197. package/src/resources/extensions/gsd/auto-runtime-state.ts +12 -7
  198. package/src/resources/extensions/gsd/auto-start.ts +31 -8
  199. package/src/resources/extensions/gsd/auto-tool-tracking.ts +56 -0
  200. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
  201. package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
  202. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
  203. package/src/resources/extensions/gsd/auto.ts +20 -7
  204. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -0
  205. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +41 -12
  206. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
  207. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -29
  208. package/src/resources/extensions/gsd/commands-verdict.ts +16 -9
  209. package/src/resources/extensions/gsd/compat/compat-marker.ts +99 -5
  210. package/src/resources/extensions/gsd/constants.ts +14 -13
  211. package/src/resources/extensions/gsd/context-masker.ts +98 -2
  212. package/src/resources/extensions/gsd/db/engine.ts +5 -2
  213. package/src/resources/extensions/gsd/docs/preferences-reference.md +22 -0
  214. package/src/resources/extensions/gsd/doctor-engine-checks.ts +30 -7
  215. package/src/resources/extensions/gsd/error-classifier.ts +13 -2
  216. package/src/resources/extensions/gsd/exec-sandbox.ts +1 -0
  217. package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
  218. package/src/resources/extensions/gsd/git-service.ts +19 -6
  219. package/src/resources/extensions/gsd/gsd-db.ts +85 -33
  220. package/src/resources/extensions/gsd/guided-flow.ts +11 -4
  221. package/src/resources/extensions/gsd/markdown-renderer.ts +5 -23
  222. package/src/resources/extensions/gsd/md-importer.ts +5 -5
  223. package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
  224. package/src/resources/extensions/gsd/model-router.ts +12 -0
  225. package/src/resources/extensions/gsd/paths.ts +73 -15
  226. package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
  227. package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
  228. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  229. package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
  230. package/src/resources/extensions/gsd/preferences.ts +25 -0
  231. package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -5
  232. package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
  233. package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
  234. package/src/resources/extensions/gsd/skill-activation.ts +17 -3
  235. package/src/resources/extensions/gsd/state-reconciliation/drift/external-markdown-edit.ts +132 -2
  236. package/src/resources/extensions/gsd/state-reconciliation/drift/external-planning-edit.ts +4 -1
  237. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +4 -2
  238. package/src/resources/extensions/gsd/stop-notice.ts +11 -0
  239. package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
  240. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
  241. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +264 -0
  242. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
  243. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
  244. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
  245. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
  246. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +10 -0
  247. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +25 -0
  248. package/src/resources/extensions/gsd/tests/complete-task.test.ts +76 -1
  249. package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
  250. package/src/resources/extensions/gsd/tests/db-engine-cjs-compat.test.ts +12 -0
  251. package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
  252. package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
  253. package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
  254. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
  255. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +33 -1
  256. package/src/resources/extensions/gsd/tests/external-markdown-edit.test.ts +38 -1
  257. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
  258. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +20 -1
  259. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
  260. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +22 -0
  261. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
  262. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
  263. package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
  264. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
  265. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  266. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
  268. package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
  269. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
  270. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +87 -0
  271. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +121 -1
  272. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
  273. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +105 -0
  274. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  275. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
  276. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
  277. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
  278. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +21 -1
  279. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
  280. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +14 -0
  281. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +19 -0
  282. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
  283. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +39 -0
  284. package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +7 -0
  285. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
  286. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  287. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
  288. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
  289. package/src/resources/extensions/gsd/tools/complete-task.ts +20 -5
  290. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
  291. package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
  292. package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
  293. package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
  294. package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
  295. package/src/resources/extensions/gsd/workspace.ts +2 -1
  296. package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
  297. package/src/resources/extensions/shared/interview-ui.ts +47 -8
  298. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +29 -0
  299. /package/dist/web/standalone/.next/static/{BE5XoklToI6lZjRzpXih8 → EAhnhYMt5UVOiG9-iPDjc}/_buildManifest.js +0 -0
  300. /package/dist/web/standalone/.next/static/{BE5XoklToI6lZjRzpXih8 → EAhnhYMt5UVOiG9-iPDjc}/_ssgManifest.js +0 -0
@@ -5,8 +5,8 @@
5
5
  // distinguish gsd-pi's own writes (expected) from external edits made by gsd-core
6
6
  // (drift to import). gsd-core is oblivious to this file and ignores it.
7
7
  import { createHash } from "node:crypto";
8
- import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
9
- import { dirname, join, isAbsolute, normalize } from "node:path";
8
+ import { existsSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync, mkdirSync } from "node:fs";
9
+ import { dirname, join, isAbsolute, normalize, relative, resolve } from "node:path";
10
10
  /** Current marker schema version. Bump on breaking format changes + migrate. */
11
11
  export const COMPAT_MARKER_SCHEMA = 2;
12
12
  /** Marker returned when no marker exists yet (fresh project, first gsd-pi run). */
@@ -32,6 +32,35 @@ export function normalizeForHash(content) {
32
32
  export function computeProjectionSha(content) {
33
33
  return createHash("sha256").update(normalizeForHash(content)).digest("hex").slice(0, 16);
34
34
  }
35
+ function normalizeRealPath(p) {
36
+ try {
37
+ return realpathSync.native(p);
38
+ }
39
+ catch {
40
+ return resolve(p);
41
+ }
42
+ }
43
+ function rootRelativeKey(rootPath, absPath) {
44
+ const root = normalizeRealPath(rootPath);
45
+ const abs = normalizeRealPath(absPath);
46
+ const rel = relative(root, abs);
47
+ if (!rel || rel === ".." || rel.startsWith(`..${sepForRelative(rel)}`) || isAbsolute(rel)) {
48
+ return null;
49
+ }
50
+ return rel.replace(/\\/g, "/");
51
+ }
52
+ function sepForRelative(rel) {
53
+ return rel.includes("\\") ? "\\" : "/";
54
+ }
55
+ export function deriveCompatProjectionKey(absPath, roots) {
56
+ for (const root of roots) {
57
+ const key = rootRelativeKey(root, absPath);
58
+ if (key)
59
+ return key;
60
+ }
61
+ const fallbackRoot = roots[roots.length - 1] ?? dirname(absPath);
62
+ return relative(fallbackRoot, absPath).replace(/\\/g, "/");
63
+ }
35
64
  /**
36
65
  * Read & validate the marker. A missing marker → EMPTY_MARKER (treat every
37
66
  * projection as external on next reconcile). A malformed marker is quarantined
@@ -39,7 +68,9 @@ export function computeProjectionSha(content) {
39
68
  * EMPTY_MARKER. A schema-mismatch returns EMPTY_MARKER (forward-compat: refuse
40
69
  * to act on a future format we don't understand).
41
70
  */
42
- export function readCompatMarker(basePath) {
71
+ export function readCompatMarker(basePath, options = {}) {
72
+ const healInvalidKeys = options.healInvalidKeys ?? true;
73
+ const quarantineInvalid = options.quarantineInvalid ?? true;
43
74
  const path = compatMarkerPath(basePath);
44
75
  if (!existsSync(path))
45
76
  return emptyMarker();
@@ -55,11 +86,16 @@ export function readCompatMarker(basePath) {
55
86
  parsed = JSON.parse(raw);
56
87
  }
57
88
  catch {
58
- quarantine(basePath, raw);
89
+ if (quarantineInvalid)
90
+ quarantine(basePath, raw);
59
91
  return emptyMarker();
60
92
  }
93
+ if (healInvalidKeys && healMarkerProjectionKeys(basePath, parsed) && isValidMarker(parsed)) {
94
+ writeCompatMarker(basePath, parsed);
95
+ }
61
96
  if (!isValidMarker(parsed)) {
62
- quarantine(basePath, raw);
97
+ if (quarantineInvalid)
98
+ quarantine(basePath, raw);
63
99
  return emptyMarker();
64
100
  }
65
101
  // Promote older markers by defaulting absent fields. Schema 1 → 2 only adds
@@ -196,6 +232,45 @@ function isValidProjectionMap(x) {
196
232
  }
197
233
  return true;
198
234
  }
235
+ function healMarkerProjectionKeys(basePath, marker) {
236
+ if (typeof marker !== "object" || marker === null)
237
+ return false;
238
+ const m = marker;
239
+ let changed = false;
240
+ if (typeof m.projections === "object" && m.projections !== null) {
241
+ changed = healProjectionMapKeys(basePath, ".gsd", m.projections) || changed;
242
+ }
243
+ const planning = m.planning;
244
+ if (typeof planning === "object" && planning !== null) {
245
+ const p = planning;
246
+ if (typeof p.projections === "object" && p.projections !== null) {
247
+ changed = healProjectionMapKeys(basePath, ".planning", p.projections) || changed;
248
+ }
249
+ if (typeof p.passthrough === "object" && p.passthrough !== null) {
250
+ changed = healProjectionMapKeys(basePath, ".planning", p.passthrough) || changed;
251
+ }
252
+ }
253
+ return changed;
254
+ }
255
+ function healProjectionMapKeys(basePath, rootName, map) {
256
+ let changed = false;
257
+ const root = join(basePath, rootName);
258
+ for (const key of Object.keys(map)) {
259
+ if (isSafeProjectionKey(key))
260
+ continue;
261
+ if (key.includes("\0") || isAbsolute(key) || /^[A-Za-z]:/.test(key))
262
+ continue;
263
+ const healedKey = rootRelativeKey(root, resolve(root, key));
264
+ if (!healedKey || !isSafeProjectionKey(healedKey))
265
+ continue;
266
+ if (!map[healedKey]) {
267
+ map[healedKey] = map[key];
268
+ }
269
+ delete map[key];
270
+ changed = true;
271
+ }
272
+ return changed;
273
+ }
199
274
  function isValidPlanningMarker(x) {
200
275
  if (typeof x !== "object" || x === null)
201
276
  return false;
@@ -24,36 +24,35 @@ export const CACHE_MAX = 50;
24
24
  * By scoping tools to this allowlist during discuss dispatches, the grammar
25
25
  * sent to the provider stays well under provider limits.
26
26
  *
27
- * Included tools and why:
27
+ * Included tools and why (canonical names only — aliases are no longer
28
+ * advertised to models by default, see plan 035):
28
29
  * - gsd_summary_save: writes CONTEXT.md artifacts (all discuss prompts)
29
- * - gsd_save_summary: alias for above
30
30
  * - gsd_decision_save: records decisions (discuss.md output phase)
31
- * - gsd_save_decision: alias for above
32
31
  * - gsd_plan_milestone: writes roadmap (discuss.md single/multi milestone)
33
- * - gsd_milestone_plan: alias for above
34
32
  * - gsd_milestone_generate_id: generates milestone IDs (discuss.md multi-milestone)
35
- * - gsd_generate_milestone_id: alias for above
36
33
  * - gsd_requirement_save: creates requirements during discuss
37
- * - gsd_save_requirement: alias for above
38
34
  * - gsd_requirement_update: updates requirements during discuss
39
- * - gsd_update_requirement: alias for above
40
35
  */
41
36
  export const DISCUSS_TOOLS_ALLOWLIST = [
42
37
  // Context / summary writing
43
38
  "gsd_summary_save",
44
- "gsd_save_summary",
45
39
  // Decision recording
46
40
  "gsd_decision_save",
47
- "gsd_save_decision",
48
41
  // Milestone planning (needed for discuss.md output phase)
49
42
  "gsd_plan_milestone",
50
- "gsd_milestone_plan",
51
43
  // Milestone ID generation (multi-milestone flow)
52
44
  "gsd_milestone_generate_id",
53
- "gsd_generate_milestone_id",
54
45
  // Requirement updates
55
46
  "gsd_requirement_save",
56
- "gsd_save_requirement",
57
47
  "gsd_requirement_update",
58
- "gsd_update_requirement",
59
48
  ];
49
+ // ─── Context Injection ────────────────────────────────────────────────────────
50
+ /**
51
+ * Leading marker stamped on every buildContextMessage() output
52
+ * (bootstrap/system-context.ts). Single source of truth for the producer and
53
+ * the consumer: the provider payload policy (filterSupersededContextInjections
54
+ * in context-masker.ts) matches this prefix to find and dedupe injected
55
+ * memory/guided/forensics messages after convertToLlm strips their customType.
56
+ * Both sides import it from here — do not inline the literal in either module.
57
+ */
58
+ export const GSD_CONTEXT_MESSAGE_SENTINEL = "[GSD Context Injection]";
@@ -1,22 +1,4 @@
1
- /**
2
- * Observation masking for GSD auto-mode sessions.
3
- *
4
- * Replaces tool result content older than N turns with a placeholder.
5
- * Reduces context bloat between compactions with zero LLM overhead.
6
- * Preserves message ordering, roles, and all assistant/user messages.
7
- *
8
- * Operates on provider payloads after convertToLlm:
9
- *
10
- * pi-ai Message[] payloads:
11
- * - toolResult messages: { role: "toolResult", content: TextContent[] }
12
- * - bash results are already converted to: { role: "user", content: [{type:"text",text:"..."}] }
13
- * and start with "Ran `" from bashExecutionToText.
14
- *
15
- * OpenAI/Codex Responses payloads:
16
- * - conversation items live in `input`, not `messages`
17
- * - tool results are { type: "function_call_output", output: string | content[] }
18
- * - bash results are user items with input_text content starting with "Ran `"
19
- */
1
+ import { GSD_CONTEXT_MESSAGE_SENTINEL } from "./constants.js";
20
2
  const MASK_PLACEHOLDER = "[result masked — within summarized history]";
21
3
  const MASK_CONTENT_BLOCK = [{ type: "text", text: MASK_PLACEHOLDER }];
22
4
  const RESPONSES_MASK_CONTENT_BLOCK = [{ type: "input_text", text: MASK_PLACEHOLDER }];
@@ -52,6 +34,26 @@ function findTurnBoundary(messages, keepRecentTurns) {
52
34
  }
53
35
  return 0;
54
36
  }
37
+ function countRealUserTurns(messages) {
38
+ let count = 0;
39
+ for (const m of messages) {
40
+ if (m.role === "user" && !isBashResultUserMessage(m))
41
+ count++;
42
+ }
43
+ return count;
44
+ }
45
+ /**
46
+ * Quantizes how many recent turns stay unmasked so the boundary only moves
47
+ * forward once per full block of `keepRecentTurns` new turns, instead of
48
+ * every turn. Keeps the masked prefix byte-stable within a block, which is
49
+ * what lets the LLM provider's prompt cache survive across turns.
50
+ */
51
+ function quantizedKeepTurns(totalTurns, keepRecentTurns) {
52
+ if (keepRecentTurns <= 0 || totalTurns < 2 * keepRecentTurns)
53
+ return totalTurns;
54
+ const maskedTurns = Math.floor((totalTurns - keepRecentTurns) / keepRecentTurns) * keepRecentTurns;
55
+ return totalTurns - maskedTurns;
56
+ }
55
57
  /**
56
58
  * Detect user messages that originated from bashExecution.
57
59
  * After convertToLlm, these are {role: "user", content: [{type:"text", text:"Ran `cmd`\n..."}]}.
@@ -73,7 +75,8 @@ function isMaskableMessage(m) {
73
75
  }
74
76
  export function createObservationMask(keepRecentTurns = 8) {
75
77
  return (messages) => {
76
- const boundary = findTurnBoundary(messages, keepRecentTurns);
78
+ const totalTurns = countRealUserTurns(messages);
79
+ const boundary = findTurnBoundary(messages, quantizedKeepTurns(totalTurns, keepRecentTurns));
77
80
  if (boundary === 0)
78
81
  return messages;
79
82
  return messages.map((m, i) => {
@@ -104,6 +107,14 @@ function findResponsesTurnBoundary(items, keepRecentTurns) {
104
107
  }
105
108
  return 0;
106
109
  }
110
+ function countResponsesRealUserTurns(items) {
111
+ let count = 0;
112
+ for (const item of items) {
113
+ if (item.role === "user" && !isResponsesBashResultUserItem(item))
114
+ count++;
115
+ }
116
+ return count;
117
+ }
107
118
  /**
108
119
  * Observation masking for OpenAI/Codex Responses API payloads.
109
120
  *
@@ -113,7 +124,8 @@ function findResponsesTurnBoundary(items, keepRecentTurns) {
113
124
  */
114
125
  export function createResponsesInputObservationMask(keepRecentTurns = 8) {
115
126
  return (items) => {
116
- const boundary = findResponsesTurnBoundary(items, keepRecentTurns);
127
+ const totalTurns = countResponsesRealUserTurns(items);
128
+ const boundary = findResponsesTurnBoundary(items, quantizedKeepTurns(totalTurns, keepRecentTurns));
117
129
  if (boundary === 0)
118
130
  return items;
119
131
  return items.map((item, i) => {
@@ -190,3 +202,67 @@ export function truncateResponsesInputResultItems(items, maxChars = 800) {
190
202
  return item;
191
203
  });
192
204
  }
205
+ // GSD injects at most one context message per turn (memory/guided/forensics —
206
+ // see buildContextMessage in bootstrap/system-context.ts), marked with
207
+ // GSD_CONTEXT_MESSAGE_SENTINEL. convertToLlm strips the distinguishing
208
+ // customType before the payload reaches this hook, so detection is by content
209
+ // prefix instead. Pre-sentinel session history lacks the sentinel, so we also
210
+ // match the stable *bracketed* GSD block labels those injections begin with,
211
+ // letting resumed sessions dedupe older memory/guided blocks.
212
+ //
213
+ // Only GSD-specific bracketed markers belong here — never generic prose. A
214
+ // message is dropped from the payload when it matches, so a natural-language
215
+ // prefix (e.g. the forensics prompt "Debug GSD itself.") could silently delete
216
+ // a real user message that happens to start the same way. Legacy forensics
217
+ // injections without memory are therefore left un-deduped by design; those
218
+ // with memory still match via the "[GSD Context Metadata]" wrapper.
219
+ const LEGACY_GSD_CONTEXT_INJECTION_PREFIXES = [
220
+ "[GSD Context Metadata]",
221
+ "[MEMORY — Critical and prompt-relevant memories from the GSD memory store]",
222
+ "[GSD Guided Execute Context]",
223
+ ];
224
+ function isGsdContextInjectionText(text) {
225
+ if (typeof text !== "string")
226
+ return false;
227
+ if (text.startsWith(GSD_CONTEXT_MESSAGE_SENTINEL))
228
+ return true;
229
+ return LEGACY_GSD_CONTEXT_INJECTION_PREFIXES.some((prefix) => text.startsWith(prefix));
230
+ }
231
+ function isGsdContextInjectionMessage(m) {
232
+ if (m.role !== "user")
233
+ return false;
234
+ return isGsdContextInjectionText(firstTextFromContent(m.content));
235
+ }
236
+ /**
237
+ * Removes every GSD context-injection user message except the latest one.
238
+ * Each turn re-injects a near-identical memory/guided/forensics block; left
239
+ * in place they duplicate verbatim across an N-turn session. Removal (not
240
+ * masking) — an empty placeholder still costs tokens and shifts message
241
+ * positions, breaking cache byte-stability. Pure function: stored history is
242
+ * never mutated, only the outgoing payload array.
243
+ */
244
+ export function filterSupersededContextInjections(messages) {
245
+ let lastIndex = -1;
246
+ for (let i = 0; i < messages.length; i++) {
247
+ if (isGsdContextInjectionMessage(messages[i]))
248
+ lastIndex = i;
249
+ }
250
+ if (lastIndex === -1)
251
+ return messages;
252
+ return messages.filter((m, i) => i === lastIndex || !isGsdContextInjectionMessage(m));
253
+ }
254
+ function isResponsesGsdContextInjectionItem(item) {
255
+ if (item.role !== "user")
256
+ return false;
257
+ return isGsdContextInjectionText(firstTextFromContent(item.content));
258
+ }
259
+ export function filterSupersededResponsesContextInjections(items) {
260
+ let lastIndex = -1;
261
+ for (let i = 0; i < items.length; i++) {
262
+ if (isResponsesGsdContextInjectionItem(items[i]))
263
+ lastIndex = i;
264
+ }
265
+ if (lastIndex === -1)
266
+ return items;
267
+ return items.filter((item, i) => i === lastIndex || !isResponsesGsdContextInjectionItem(item));
268
+ }
@@ -10,7 +10,7 @@
10
10
  // writer layer.
11
11
  import { createRequire } from "node:module";
12
12
  import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
13
- import { dirname, join } from "node:path";
13
+ import { dirname, join, resolve } from "node:path";
14
14
  import { GSDError, GSD_STALE_STATE } from "../errors.js";
15
15
  import { logError, logWarning } from "../workflow-logger.js";
16
16
  import { createDbAdapter } from "../db-adapter.js";
@@ -31,7 +31,10 @@ function getGsdRequire() {
31
31
  if (_gsdRequire !== undefined)
32
32
  return _gsdRequire;
33
33
  try {
34
- _gsdRequire = createRequire(import.meta.url);
34
+ // Next.js may emit this module into a CommonJS chunk. Avoid ESM-only module
35
+ // metadata syntax here; it is a hard parse error there.
36
+ const packageRoot = process.env.GSD_WEB_PACKAGE_ROOT || process.env.GSD_PKG_ROOT || process.cwd();
37
+ _gsdRequire = createRequire(resolve(packageRoot, "package.json"));
35
38
  }
36
39
  catch {
37
40
  _gsdRequire = null;
@@ -357,6 +357,28 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
357
357
  - `"replace"` requires `prompt`.
358
358
  - `"skip"` is valid with no additional fields.
359
359
 
360
+ - `planning_subagents`: object — project-local widening for read-only planning subagent dispatch. Supported unit keys are `plan-milestone` and `plan-slice`; each has:
361
+ - `allowed`: string[] — additional planning-safe agents for that unit.
362
+
363
+ Agents must already be classified by GSD as read-only planning specialists: `mnemo`, `scout`, `planner`, `reviewer`, `security`, or `tester`. The write gate still blocks source writes outside `.gsd/**`, unrestricted bash, stale subagent calls without agent identities, agents outside the global read-only registry, and agents not listed for the active unit.
364
+
365
+ Example:
366
+
367
+ ```yaml
368
+ planning_subagents:
369
+ plan-milestone:
370
+ allowed:
371
+ - scout
372
+ - planner
373
+ - security
374
+ plan-slice:
375
+ allowed:
376
+ - scout
377
+ - planner
378
+ - reviewer
379
+ - security
380
+ ```
381
+
360
382
  **Known unit types for `before`/`after`:** `research-milestone`, `plan-milestone`, `research-slice`, `plan-slice`, `execute-task`, `complete-slice`, `replan-slice`, `reassess-roadmap`, `run-uat`.
361
383
 
362
384
  - `experimental`: opt-in experimental features. All features here are **off by default** — you must explicitly set each one to `true` to enable it. Features in this block may change or be removed without a deprecation cycle while in experimental status. Keys:
@@ -3,7 +3,7 @@ import { isAbsolute, join, relative, sep } from "node:path";
3
3
  import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
4
4
  import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
5
5
  import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
6
- import { buildTaskFileName, gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, } from "./paths.js";
6
+ import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile, resolveTaskFile, } from "./paths.js";
7
7
  import { deriveState } from "./state.js";
8
8
  import { isClosedStatus } from "./status-guards.js";
9
9
  import { workflowEventLogPath } from "./workflow-event-ledger.js";
@@ -58,6 +58,23 @@ function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, uni
58
58
  fixable: false,
59
59
  });
60
60
  }
61
+ function bareDuplicateMilestoneId(milestoneId) {
62
+ const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
63
+ return match?.[1]?.toUpperCase() ?? null;
64
+ }
65
+ function checkboxDbStatusMilestoneIds(basePath, milestoneIds) {
66
+ if (!hasFlatPhaseLayout(basePath))
67
+ return milestoneIds;
68
+ // Flat-phase directories are keyed by phase number. A restored
69
+ // M003-xxxxxx row alongside M003 is a stale duplicate for the same
70
+ // phases/03-* projection, so checking both can compare DB status against two
71
+ // competing PLAN files and report a persistent false divergence.
72
+ const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
73
+ return milestoneIds.filter((milestoneId) => {
74
+ const bareId = bareDuplicateMilestoneId(milestoneId);
75
+ return !bareId || !allIds.has(bareId);
76
+ });
77
+ }
61
78
  function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
62
79
  for (const milestoneId of milestoneIds) {
63
80
  const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
@@ -152,11 +169,8 @@ function resolveFlatPhaseArtifactDiskPath(basePath, row) {
152
169
  if (!artifactType)
153
170
  return null;
154
171
  if (row.slice_id && row.task_id) {
155
- const phaseDir = resolveMilestonePath(basePath, row.milestone_id);
156
- if (!phaseDir || !isFlatPhaseDiskPath(basePath, phaseDir))
157
- return null;
158
- const candidate = join(phaseDir, buildTaskFileName(row.task_id, artifactType));
159
- return existsSync(candidate) ? candidate : null;
172
+ const candidate = resolveTaskFile(basePath, row.milestone_id, row.slice_id, row.task_id, artifactType);
173
+ return candidate && isFlatPhaseDiskPath(basePath, candidate) ? candidate : null;
160
174
  }
161
175
  const candidate = row.slice_id
162
176
  ? resolveSliceFile(basePath, row.milestone_id, row.slice_id, artifactType)
@@ -325,7 +339,9 @@ function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
325
339
  }
326
340
  function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
327
341
  const issuePath = artifactPathRelativeToGsd(row.path);
328
- return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
342
+ return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
343
+ (hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
344
+ hasPresentMilestonesReplacement(basePath, row, artifactRows));
329
345
  }
330
346
  function staleArtifactRowFixable(basePath, row, artifactRows) {
331
347
  return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
@@ -661,7 +677,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
661
677
  // block doctor.
662
678
  try {
663
679
  if (isDbAvailable()) {
664
- checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
680
+ checkProjectionCheckboxDbStatus(basePath, checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)), issues);
665
681
  }
666
682
  }
667
683
  catch {
@@ -38,8 +38,12 @@ const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|web
38
38
  // transient errors so auto-mode can retry with reduced budget or fall back to a larger-context model.
39
39
  // See: https://github.com/open-gsd/gsd-pi/issues/4528
40
40
  const SERVER_RE = /internal(?: server)?[ _-]?error|server[ _-]?error|500|502|503|overloaded|server_error|api_error|service.?unavailable|context (?:window|length) exceed|context window exceed/i;
41
+ // Provider adapters throw this when an upstream stop reason reaches Pi before
42
+ // the adapter knows how to map it. Keep the match scoped so plain "unknown"
43
+ // runtime errors still pause for inspection instead of being retried forever.
44
+ const PROVIDER_STOP_REASON_RE = /unhandled stop reason:\s*(?:abort|unknown)\b/i;
41
45
  // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
42
- const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
46
+ const CONNECTION_RE = /terminated|\babort\b|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
43
47
  // Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
44
48
  // This eliminates the need to enumerate every error message variant individually.
45
49
  const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
@@ -72,7 +76,7 @@ const UNSUPPORTED_MODEL_SCOPE_RE = /\b(?:account|plan|tier|subscription)\b/i;
72
76
  * 3. Network (ECONNRESET, ETIMEDOUT, socket hang up, fetch failed, dns)
73
77
  * 4. Stream truncation (malformed JSON from mid-stream cut)
74
78
  * 5. Server (500/502/503, overloaded, server_error)
75
- * 6. Connection (terminated, ECONNREFUSED, EPIPE, other side closed)
79
+ * 6. Connection (terminated, abort/unknown stop reason, ECONNREFUSED, EPIPE, other side closed)
76
80
  * 7. Model error (400 invalid argument / payload rejected for this model)
77
81
  * 8. Unknown
78
82
  */
@@ -124,6 +128,12 @@ export function classifyError(errorMsg, retryAfterMs) {
124
128
  const delayMs = resetMatch ? Number(resetMatch[1]) * 1000 : 60_000;
125
129
  return { kind: "rate-limit", retryAfterMs: delayMs };
126
130
  }
131
+ // Provider stop-reason adapter gaps are usually request/stream failures from
132
+ // the current model. Treat the scoped adapter message as fallback-eligible,
133
+ // but keep generic unknown errors on the indefinite pause path.
134
+ if (PROVIDER_STOP_REASON_RE.test(errorMsg)) {
135
+ return { kind: "connection", retryAfterMs: retryAfterMs ?? 15_000 };
136
+ }
127
137
  // 3. Network errors — same-model retry candidate
128
138
  if (NETWORK_RE.test(errorMsg)) {
129
139
  // Exclude if also matches permanent signals (already handled above for
@@ -33,6 +33,7 @@ export const EXEC_DEFAULTS = {
33
33
  "LOGNAME",
34
34
  "TMPDIR",
35
35
  "NODE_OPTIONS",
36
+ "NODE_PATH",
36
37
  "PYTHONPATH",
37
38
  "PYTHONIOENCODING",
38
39
  ],
@@ -130,6 +130,17 @@ function isInsideFlatProjectionBackup(backupDir, src) {
130
130
  const phaseBackupDir = join(backupDir, "__phases");
131
131
  return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
132
132
  }
133
+ function removeFlatProjectionBackup(backupDir) {
134
+ const phaseBackupDir = join(backupDir, "__phases");
135
+ if (!existsSync(phaseBackupDir))
136
+ return;
137
+ try {
138
+ removePathWithRetries(phaseBackupDir);
139
+ }
140
+ catch (err) {
141
+ logWarning("migration", `flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${err.message}`);
142
+ }
143
+ }
133
144
  function restoreFlatProjectionFromBackup(basePath, backupDir) {
134
145
  const phaseBackupDir = join(backupDir, "__phases");
135
146
  if (!existsSync(phaseBackupDir))
@@ -433,4 +444,5 @@ export async function migrateToFlatPhase(basePath) {
433
444
  catch (err) {
434
445
  logWarning("migration", `flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${err.message}`);
435
446
  }
447
+ removeFlatProjectionBackup(backupDir);
436
448
  }
@@ -229,10 +229,15 @@ export const RUNTIME_EXCLUSION_PATHS = [
229
229
  const runtimeFilesCleanedUpRepos = new Set();
230
230
  // ─── Integration Branch Metadata ───────────────────────────────────────────
231
231
  /**
232
- * Path to the milestone metadata file that stores the integration branch.
233
- * Format: .gsd/milestones/<MID>/<MID>-META.json
232
+ * Path to the milestone integration-branch metadata file.
233
+ * Format: .gsd/<MID>-META.json (deliberately OUTSIDE milestones/<MID>/ so it
234
+ * cannot poison isLegacyMilestonesLayout — see ADR-045).
234
235
  */
235
- function milestoneMetaPath(basePath, milestoneId) {
236
+ export function milestoneMetaPath(basePath, milestoneId) {
237
+ return join(gsdRoot(basePath), `${milestoneId}-META.json`);
238
+ }
239
+ /** Legacy location (pre-ADR-045). Read-only fallback for existing on-disk trees. */
240
+ function legacyMilestoneMetaPath(basePath, milestoneId) {
236
241
  return join(gsdRoot(basePath), "milestones", milestoneId, `${milestoneId}-META.json`);
237
242
  }
238
243
  /**
@@ -241,9 +246,13 @@ function milestoneMetaPath(basePath, milestoneId) {
241
246
  */
242
247
  export function readIntegrationBranch(basePath, milestoneId) {
243
248
  try {
244
- const metaFile = milestoneMetaPath(basePath, milestoneId);
245
- if (!existsSync(metaFile))
246
- return null;
249
+ let metaFile = milestoneMetaPath(basePath, milestoneId);
250
+ if (!existsSync(metaFile)) {
251
+ const legacy = legacyMilestoneMetaPath(basePath, milestoneId);
252
+ if (!existsSync(legacy))
253
+ return null;
254
+ metaFile = legacy;
255
+ }
247
256
  const data = JSON.parse(readFileSync(metaFile, "utf-8"));
248
257
  const branch = data?.integrationBranch;
249
258
  if (typeof branch === "string" && branch.trim() !== "" && VALID_BRANCH_NAME.test(branch)) {
@@ -295,7 +304,10 @@ export function writeIntegrationBranch(basePath, milestoneId, branch) {
295
304
  if (existingBranch === branch)
296
305
  return;
297
306
  const metaFile = milestoneMetaPath(basePath, milestoneId);
298
- mkdirSync(join(gsdRoot(basePath), "milestones", milestoneId), { recursive: true });
307
+ // Ensure the flat .gsd/ root exists (never milestones/<MID>/ that would
308
+ // poison layout detection, ADR-045). In production .gsd/ already exists; this
309
+ // guards fresh trees so the write below can't ENOENT.
310
+ mkdirSync(gsdRoot(basePath), { recursive: true });
299
311
  // Merge with existing metadata if present
300
312
  let existing = {};
301
313
  try {