@opengsd/gsd-pi 1.4.0 → 1.5.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 (329) hide show
  1. package/README.md +11 -11
  2. package/dist/onboarding.js +11 -0
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/GSD-WORKFLOW.md +20 -25
  5. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +118 -53
  6. package/dist/resources/extensions/cursor-cli/index.js +16 -0
  7. package/dist/resources/extensions/cursor-cli/models.js +63 -0
  8. package/dist/resources/extensions/cursor-cli/package.json +11 -0
  9. package/dist/resources/extensions/cursor-cli/readiness.js +128 -0
  10. package/dist/resources/extensions/cursor-cli/stream-adapter.js +353 -0
  11. package/dist/resources/extensions/gsd/activity-log.js +4 -1
  12. package/dist/resources/extensions/gsd/artifact-verification.js +25 -9
  13. package/dist/resources/extensions/gsd/auto/closeout.js +45 -42
  14. package/dist/resources/extensions/gsd/auto/finalize.js +9 -0
  15. package/dist/resources/extensions/gsd/auto/orchestrator.js +1 -1
  16. package/dist/resources/extensions/gsd/auto/run-unit.js +5 -2
  17. package/dist/resources/extensions/gsd/auto/schedule-wakeup.js +3 -0
  18. package/dist/resources/extensions/gsd/auto-dispatch.js +15 -5
  19. package/dist/resources/extensions/gsd/auto-model-selection.js +36 -18
  20. package/dist/resources/extensions/gsd/auto-prompts.js +76 -2
  21. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  22. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +4 -0
  23. package/dist/resources/extensions/gsd/auto-worktree.js +38 -11
  24. package/dist/resources/extensions/gsd/auto.js +23 -3
  25. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +114 -5
  27. package/dist/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.js +58 -9
  28. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +10 -2
  29. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +14 -1
  30. package/dist/resources/extensions/gsd/codebase-generator.js +157 -19
  31. package/dist/resources/extensions/gsd/commands/handlers/core.js +40 -4
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +153 -1
  33. package/dist/resources/extensions/gsd/commands-worktree.js +11 -2
  34. package/dist/resources/extensions/gsd/db/queries.js +39 -0
  35. package/dist/resources/extensions/gsd/discussion-handoff.js +8 -3
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  37. package/dist/resources/extensions/gsd/doctor-engine-checks.js +206 -14
  38. package/dist/resources/extensions/gsd/doctor-providers.js +38 -10
  39. package/dist/resources/extensions/gsd/doctor.js +93 -3
  40. package/dist/resources/extensions/gsd/exec-sandbox.js +6 -2
  41. package/dist/resources/extensions/gsd/flat-phase-migration.js +74 -23
  42. package/dist/resources/extensions/gsd/git-service.js +14 -18
  43. package/dist/resources/extensions/gsd/gsd-db.js +52 -0
  44. package/dist/resources/extensions/gsd/guidance.js +1 -0
  45. package/dist/resources/extensions/gsd/guided-flow.js +40 -2
  46. package/dist/resources/extensions/gsd/key-manager.js +1 -0
  47. package/dist/resources/extensions/gsd/markdown-renderer.js +25 -8
  48. package/dist/resources/extensions/gsd/migrate-external.js +24 -7
  49. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +3 -3
  50. package/dist/resources/extensions/gsd/preferences-diagnostics.js +24 -1
  51. package/dist/resources/extensions/gsd/preferences-models.js +6 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +25 -2
  53. package/dist/resources/extensions/gsd/preferences.js +29 -2
  54. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  55. package/dist/resources/extensions/gsd/redact-secrets.js +37 -0
  56. package/dist/resources/extensions/gsd/repo-identity.js +16 -0
  57. package/dist/resources/extensions/gsd/repository-registry.js +6 -1
  58. package/dist/resources/extensions/gsd/root-write-leak-guard.js +4 -2
  59. package/dist/resources/extensions/gsd/safe-fs.js +23 -2
  60. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  61. package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  62. package/dist/resources/extensions/gsd/tools/plan-task.js +76 -8
  63. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -0
  64. package/dist/resources/extensions/gsd/unit-context-manifest.js +5 -4
  65. package/dist/resources/extensions/gsd/unit-registry.js +27 -2
  66. package/dist/resources/extensions/gsd/unit-runtime.js +21 -0
  67. package/dist/resources/extensions/gsd/workspace.js +26 -8
  68. package/dist/resources/extensions/gsd/worktree-lifecycle.js +67 -5
  69. package/dist/resources/extensions/gsd/worktree-manager.js +97 -4
  70. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  71. package/dist/web/standalone/.next/BUILD_ID +1 -1
  72. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  73. package/dist/web/standalone/.next/build-manifest.json +2 -2
  74. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/index.html +1 -1
  92. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  99. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  100. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  102. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  103. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  104. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  105. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  106. package/dist/wizard.d.ts +8 -0
  107. package/dist/wizard.js +8 -1
  108. package/integrations/hermes/CONTEXT.md +47 -0
  109. package/integrations/hermes/README.md +21 -0
  110. package/integrations/hermes/__init__.py +31 -0
  111. package/integrations/hermes/docs/issue-1162-grilling.md +197 -0
  112. package/integrations/hermes/docs/setup.md +4 -0
  113. package/integrations/hermes/open_gsd_hermes/commands.py +141 -0
  114. package/integrations/hermes/open_gsd_hermes/config.py +2 -0
  115. package/integrations/hermes/open_gsd_hermes/gsd_client.py +594 -31
  116. package/integrations/hermes/open_gsd_hermes/notifications.py +10 -1
  117. package/integrations/hermes/plugin.yaml +4 -0
  118. package/integrations/hermes/tests/test_config.py +6 -0
  119. package/integrations/hermes/tests/test_directory_loader.py +37 -0
  120. package/integrations/hermes/tests/test_gsd_client_cache.py +3 -0
  121. package/integrations/hermes/tests/test_gsd_client_execute.py +35 -0
  122. package/integrations/hermes/tests/test_gsd_client_process_cwd.py +22 -0
  123. package/integrations/hermes/tests/test_gsd_client_timeout.py +98 -0
  124. package/integrations/hermes/tests/test_milestone_client.py +447 -0
  125. package/integrations/hermes/tests/test_new_milestone_command.py +313 -0
  126. package/integrations/hermes/tests/test_supervisor_fsm.py +40 -0
  127. package/package.json +8 -8
  128. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts +1 -0
  129. package/packages/cloud-mcp-gateway/dist/auth-store.d.ts.map +1 -1
  130. package/packages/cloud-mcp-gateway/dist/auth-store.js +21 -1
  131. package/packages/cloud-mcp-gateway/dist/auth-store.js.map +1 -1
  132. package/packages/cloud-mcp-gateway/dist/auth-store.test.js +34 -0
  133. package/packages/cloud-mcp-gateway/dist/auth-store.test.js.map +1 -1
  134. package/packages/cloud-mcp-gateway/package.json +2 -2
  135. package/packages/contracts/package.json +1 -1
  136. package/packages/daemon/dist/cloud-runtime.d.ts +2 -0
  137. package/packages/daemon/dist/cloud-runtime.d.ts.map +1 -1
  138. package/packages/daemon/dist/cloud-runtime.js +25 -1
  139. package/packages/daemon/dist/cloud-runtime.js.map +1 -1
  140. package/packages/daemon/dist/cloud-runtime.test.js +51 -0
  141. package/packages/daemon/dist/cloud-runtime.test.js.map +1 -1
  142. package/packages/daemon/dist/message-batcher.d.ts +7 -0
  143. package/packages/daemon/dist/message-batcher.d.ts.map +1 -1
  144. package/packages/daemon/dist/message-batcher.js +13 -0
  145. package/packages/daemon/dist/message-batcher.js.map +1 -1
  146. package/packages/daemon/dist/message-batcher.test.js +48 -0
  147. package/packages/daemon/dist/message-batcher.test.js.map +1 -1
  148. package/packages/daemon/dist/session-manager.js +1 -1
  149. package/packages/daemon/dist/session-manager.js.map +1 -1
  150. package/packages/daemon/package.json +5 -5
  151. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts +13 -1
  152. package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
  153. package/packages/gsd-agent-core/dist/compaction/compaction.js +15 -1
  154. package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
  155. package/packages/gsd-agent-core/dist/session/agent-session-navigation.d.ts.map +1 -1
  156. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js +4 -1
  157. package/packages/gsd-agent-core/dist/session/agent-session-navigation.js.map +1 -1
  158. package/packages/gsd-agent-core/package.json +5 -5
  159. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -1
  160. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +1 -0
  161. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -1
  162. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js +1 -1
  163. package/packages/gsd-agent-modes/dist/modes/rpc/rpc-client.js.map +1 -1
  164. package/packages/gsd-agent-modes/package.json +7 -7
  165. package/packages/mcp-server/dist/server.d.ts +18 -5
  166. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/server.js +46 -5
  168. package/packages/mcp-server/dist/server.js.map +1 -1
  169. package/packages/mcp-server/dist/session-manager.js +1 -1
  170. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  171. package/packages/mcp-server/package.json +4 -4
  172. package/packages/native/package.json +1 -1
  173. package/packages/pi-agent-core/package.json +1 -1
  174. package/packages/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  175. package/packages/pi-ai/dist/env-api-keys.js +3 -0
  176. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  177. package/packages/pi-ai/dist/types.d.ts +2 -2
  178. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  179. package/packages/pi-ai/dist/types.js.map +1 -1
  180. package/packages/pi-ai/package.json +1 -1
  181. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  183. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +1 -0
  185. package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -0
  189. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/dist/rpc-client.js +1 -1
  193. package/packages/rpc-client/dist/rpc-client.js.map +1 -1
  194. package/packages/rpc-client/package.json +2 -2
  195. package/pkg/package.json +1 -1
  196. package/scripts/link-workspace-packages.cjs +14 -4
  197. package/src/resources/GSD-WORKFLOW.md +20 -25
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +130 -51
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +371 -39
  200. package/src/resources/extensions/cursor-cli/index.ts +18 -0
  201. package/src/resources/extensions/cursor-cli/models.ts +65 -0
  202. package/src/resources/extensions/cursor-cli/package.json +11 -0
  203. package/src/resources/extensions/cursor-cli/readiness.ts +143 -0
  204. package/src/resources/extensions/cursor-cli/stream-adapter.ts +440 -0
  205. package/src/resources/extensions/cursor-cli/tests/index.test.ts +54 -0
  206. package/src/resources/extensions/cursor-cli/tests/readiness.test.ts +29 -0
  207. package/src/resources/extensions/cursor-cli/tests/stream-adapter.test.ts +166 -0
  208. package/src/resources/extensions/gsd/activity-log.ts +4 -1
  209. package/src/resources/extensions/gsd/artifact-verification.ts +25 -9
  210. package/src/resources/extensions/gsd/auto/closeout.ts +54 -56
  211. package/src/resources/extensions/gsd/auto/finalize.ts +8 -0
  212. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  213. package/src/resources/extensions/gsd/auto/orchestrator.ts +1 -1
  214. package/src/resources/extensions/gsd/auto/run-unit.ts +5 -2
  215. package/src/resources/extensions/gsd/auto/schedule-wakeup.ts +8 -0
  216. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -5
  217. package/src/resources/extensions/gsd/auto-model-selection.ts +49 -18
  218. package/src/resources/extensions/gsd/auto-prompts.ts +73 -2
  219. package/src/resources/extensions/gsd/auto-start.ts +4 -3
  220. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +4 -0
  221. package/src/resources/extensions/gsd/auto-worktree.ts +50 -11
  222. package/src/resources/extensions/gsd/auto.ts +22 -3
  223. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +128 -5
  225. package/src/resources/extensions/gsd/bootstrap/schedule-wakeup-tool.ts +80 -12
  226. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +12 -2
  227. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +23 -1
  228. package/src/resources/extensions/gsd/codebase-generator.ts +179 -18
  229. package/src/resources/extensions/gsd/commands/handlers/core.ts +39 -3
  230. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +135 -1
  231. package/src/resources/extensions/gsd/commands-worktree.ts +13 -2
  232. package/src/resources/extensions/gsd/db/queries.ts +40 -0
  233. package/src/resources/extensions/gsd/discussion-handoff.ts +10 -2
  234. package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -2
  235. package/src/resources/extensions/gsd/doctor-engine-checks.ts +269 -18
  236. package/src/resources/extensions/gsd/doctor-providers.ts +39 -10
  237. package/src/resources/extensions/gsd/doctor-types.ts +5 -1
  238. package/src/resources/extensions/gsd/doctor.ts +95 -3
  239. package/src/resources/extensions/gsd/exec-sandbox.ts +6 -2
  240. package/src/resources/extensions/gsd/flat-phase-migration.ts +98 -22
  241. package/src/resources/extensions/gsd/git-service.ts +14 -17
  242. package/src/resources/extensions/gsd/gsd-db.ts +72 -0
  243. package/src/resources/extensions/gsd/guidance.ts +2 -0
  244. package/src/resources/extensions/gsd/guided-flow.ts +45 -2
  245. package/src/resources/extensions/gsd/key-manager.ts +1 -0
  246. package/src/resources/extensions/gsd/markdown-renderer.ts +25 -7
  247. package/src/resources/extensions/gsd/migrate-external.ts +23 -6
  248. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +4 -4
  249. package/src/resources/extensions/gsd/model-router.ts +4 -5
  250. package/src/resources/extensions/gsd/preferences-diagnostics.ts +24 -0
  251. package/src/resources/extensions/gsd/preferences-models.ts +6 -3
  252. package/src/resources/extensions/gsd/preferences-validation.ts +28 -2
  253. package/src/resources/extensions/gsd/preferences.ts +38 -4
  254. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  255. package/src/resources/extensions/gsd/redact-secrets.ts +40 -0
  256. package/src/resources/extensions/gsd/repo-identity.ts +17 -0
  257. package/src/resources/extensions/gsd/repository-registry.ts +7 -1
  258. package/src/resources/extensions/gsd/root-write-leak-guard.ts +3 -2
  259. package/src/resources/extensions/gsd/safe-fs.ts +22 -2
  260. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +7 -9
  261. package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -0
  262. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +131 -5
  263. package/src/resources/extensions/gsd/tests/auto-migrating-recovery.test.ts +18 -0
  264. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +178 -0
  265. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +43 -1
  266. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  267. package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +41 -0
  268. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +41 -0
  269. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +4 -4
  270. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +3 -0
  271. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-harness-abort.test.ts +55 -0
  272. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +8 -0
  273. package/src/resources/extensions/gsd/tests/batch-task-query.test.ts +85 -0
  274. package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +29 -0
  275. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +365 -0
  276. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +62 -1
  277. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +138 -1
  278. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +397 -5
  279. package/src/resources/extensions/gsd/tests/doctor-workspace.test.ts +207 -0
  280. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +38 -1
  281. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +87 -19
  282. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +89 -0
  283. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +9 -0
  284. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +4 -2
  285. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +122 -0
  286. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +4 -4
  287. package/src/resources/extensions/gsd/tests/migrate-external-worktree.test.ts +116 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +152 -22
  289. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +2 -2
  291. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +50 -6
  292. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +17 -0
  293. package/src/resources/extensions/gsd/tests/plan-task.test.ts +264 -1
  294. package/src/resources/extensions/gsd/tests/preferences.test.ts +150 -0
  295. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +142 -0
  296. package/src/resources/extensions/gsd/tests/recovery-verify-logs.test.ts +4 -4
  297. package/src/resources/extensions/gsd/tests/redact-secrets.test.ts +56 -0
  298. package/src/resources/extensions/gsd/tests/register-hooks-flat-phase-migration.test.ts +73 -0
  299. package/src/resources/extensions/gsd/tests/register-hooks-loop-guard-auto.test.ts +275 -0
  300. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +41 -2
  301. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +55 -0
  302. package/src/resources/extensions/gsd/tests/root-write-leak-guard.test.ts +31 -9
  303. package/src/resources/extensions/gsd/tests/schedule-wakeup-tool.test.ts +167 -0
  304. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -0
  305. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -1
  306. package/src/resources/extensions/gsd/tests/status-per-repo.test.ts +94 -0
  307. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +35 -0
  308. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +63 -0
  309. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +66 -18
  310. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +56 -0
  311. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  312. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +52 -0
  313. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +53 -1
  314. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +208 -1
  315. package/src/resources/extensions/gsd/tests/workspace.test.ts +18 -4
  316. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +4 -4
  317. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +2 -2
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +31 -1
  319. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +32 -0
  320. package/src/resources/extensions/gsd/tools/plan-task.ts +107 -9
  321. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +58 -0
  322. package/src/resources/extensions/gsd/unit-context-manifest.ts +11 -5
  323. package/src/resources/extensions/gsd/unit-registry.ts +34 -2
  324. package/src/resources/extensions/gsd/unit-runtime.ts +42 -0
  325. package/src/resources/extensions/gsd/workspace.ts +42 -8
  326. package/src/resources/extensions/gsd/worktree-lifecycle.ts +135 -11
  327. package/src/resources/extensions/gsd/worktree-manager.ts +143 -5
  328. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_buildManifest.js +0 -0
  329. /package/dist/web/standalone/.next/static/{zQLcXm0Za8lexoXtgDpMG → bCg3aYrFN1DKZr2dqgE7L}/_ssgManifest.js +0 -0
@@ -34,6 +34,15 @@ const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
34
34
  "pause",
35
35
  ]);
36
36
  const VALID_GATE_EVALUATE_SLICE_GATES = new Set(getGateIdsForTurn("gate-evaluate"));
37
+ /** Cross-axis warnings that only apply to the effective (merged) preference shape. */
38
+ export function crossAxisPreferenceWarnings(preferences) {
39
+ if (preferences.mode === "team" && preferences.workspace?.mode === "parent") {
40
+ return [
41
+ "mode:team + workspace.mode:parent: team branch-push/PR resolves at the project root and will not push child repositories — see docs/dev/ADR-044",
42
+ ];
43
+ }
44
+ return [];
45
+ }
37
46
  export function validatePreferences(preferences) {
38
47
  const errors = [];
39
48
  const warnings = [];
@@ -489,8 +498,12 @@ export function validatePreferences(preferences) {
489
498
  }
490
499
  // ─── Remote Questions ───────────────────────────────────────────────
491
500
  if (preferences.remote_questions !== undefined) {
492
- if (preferences.remote_questions && typeof preferences.remote_questions === "object") {
493
- validated.remote_questions = preferences.remote_questions;
501
+ const remoteQuestions = preferences.remote_questions;
502
+ if (typeof remoteQuestions === "object" && remoteQuestions !== null) {
503
+ validated.remote_questions = remoteQuestions;
504
+ }
505
+ else if (remoteQuestions === false) {
506
+ // Explicit disable for a globally-configured remote questions channel.
494
507
  }
495
508
  else {
496
509
  errors.push("remote_questions must be an object");
@@ -1524,6 +1537,15 @@ export function validatePreferences(preferences) {
1524
1537
  else {
1525
1538
  errors.push("workspace must be an object");
1526
1539
  }
1540
+ // parent mode is behavioral: it must coordinate at least one declared
1541
+ // child repository. Without one it is indistinguishable from project
1542
+ // mode, so reject it explicitly rather than silently degrading.
1543
+ if (validated.workspace?.mode === "parent") {
1544
+ const childRepoCount = validated.workspace.repositories ? Object.keys(validated.workspace.repositories).length : 0;
1545
+ if (childRepoCount === 0) {
1546
+ errors.push('workspace.mode "parent" requires at least one repository under workspace.repositories');
1547
+ }
1548
+ }
1527
1549
  }
1528
1550
  // ─── Enhanced Verification ──────────────────────────────────────────────────
1529
1551
  if (preferences.enhanced_verification !== undefined) {
@@ -1596,5 +1618,6 @@ export function validatePreferences(preferences) {
1596
1618
  errors.push(`language must be a non-empty string up to 50 characters with no newlines (e.g. "Chinese", "de", "日本語")`);
1597
1619
  }
1598
1620
  }
1621
+ warnings.push(...crossAxisPreferenceWarnings(validated));
1599
1622
  return { preferences: validated, errors, warnings };
1600
1623
  }
@@ -19,7 +19,7 @@ import { logWarning } from "./workflow-logger.js";
19
19
  import { DEFAULT_TOKEN_PROFILE, resolveProfileDefaults as _resolveProfileDefaults, VALID_TOKEN_PROFILES, resolveDisabledModelProvidersFromPreferences, } from "./preferences-models.js";
20
20
  import { nativeHasCommittedHead, nativeIsRepo } from "./native-git-bridge.js";
21
21
  import { KNOWN_PREFERENCE_KEYS, MODE_DEFAULTS, formatSkillRef, } from "./preferences-types.js";
22
- import { validatePreferences } from "./preferences-validation.js";
22
+ import { crossAxisPreferenceWarnings, validatePreferences } from "./preferences-validation.js";
23
23
  import { gsdHome } from "./gsd-home.js";
24
24
  // ─── Re-exports: validation ─────────────────────────────────────────────────
25
25
  export { validatePreferences } from "./preferences-validation.js";
@@ -203,6 +203,7 @@ function effectivePreferencesCacheKey(basePath, opts) {
203
203
  project: projectPreferencesCandidatePaths(basePath).map(preferencesFileSignature),
204
204
  availableModelIds: opts?.availableModelIds ?? null,
205
205
  preferredModelId: opts?.preferredModelId ?? null,
206
+ skipProfileDefaults: opts?.skipProfileDefaults === true,
206
207
  });
207
208
  }
208
209
  function cloneLoadedPreferences(loaded) {
@@ -264,7 +265,7 @@ export function loadEffectiveGSDPreferences(basePath, opts) {
264
265
  else {
265
266
  profileForDefaults = DEFAULT_TOKEN_PROFILE;
266
267
  }
267
- if (profileForDefaults) {
268
+ if (profileForDefaults && !opts?.skipProfileDefaults) {
268
269
  const profileDefaults = _resolveProfileDefaults(profileForDefaults, opts?.availableModelIds, result.preferences.dynamic_routing, opts?.preferredModelId);
269
270
  const defaultsToApply = explicitProfile
270
271
  ? profileDefaults
@@ -282,9 +283,35 @@ export function loadEffectiveGSDPreferences(basePath, opts) {
282
283
  };
283
284
  }
284
285
  result = stripInheritedPlanningDepth(result, projectHasPlanningDepth);
286
+ result = appendCrossAxisPreferenceWarnings(result);
285
287
  cacheEffectivePreferences(cacheKey, result);
286
288
  return result;
287
289
  }
290
+ function appendCrossAxisPreferenceWarnings(loaded) {
291
+ const crossWarnings = crossAxisPreferenceWarnings(loaded.preferences);
292
+ const existingMessages = new Set([
293
+ ...(loaded.warnings ?? []),
294
+ ...(loaded.diagnostics ?? []).map((diagnostic) => diagnostic.message),
295
+ ]);
296
+ const newWarnings = crossWarnings.filter((message) => !existingMessages.has(message));
297
+ if (newWarnings.length === 0)
298
+ return loaded;
299
+ return {
300
+ ...loaded,
301
+ warnings: [...(loaded.warnings ?? []), ...newWarnings],
302
+ diagnostics: [
303
+ ...(loaded.diagnostics ?? []),
304
+ ...newWarnings.map((message) => ({
305
+ path: loaded.path,
306
+ scope: loaded.scope,
307
+ severity: "warning",
308
+ kind: "validation",
309
+ message,
310
+ sanitized: true,
311
+ })),
312
+ ],
313
+ };
314
+ }
288
315
  function withoutProfilePhaseDefaults(defaults) {
289
316
  if (defaults.phases === undefined)
290
317
  return defaults;
@@ -32,6 +32,8 @@ Before planning, validate roadmap assumptions against code and dependency summar
32
32
 
33
33
  If slice research is inlined, trust its architectural findings, but verify every concrete file path you place in task `inputs` or `expectedOutput` against the current tree or prior/same-task outputs. Explore enough code to confirm paths, boundaries, and verification. Executors later get only task plans, slice excerpt, and prior summaries, so put required paths, steps, inputs, and outputs in task plans.
34
34
 
35
+ {{repoRegistry}}
36
+
35
37
  {{executorContextConstraints}}
36
38
 
37
39
  ## Planning Rules
@@ -45,8 +47,8 @@ If slice research is inlined, trust its architectural findings, but verify every
45
47
  6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
46
48
  7. Right-size tasks. Simple slices can be one task; split only when context, ownership, or verification boundaries justify it.
47
49
  8. Task `verify` commands must be safe, simple commands. Do not use shell pipes, redirects, semicolons, backticks, command substitution, output trimming, or grep regex alternation with `|`. If multiple checks are needed, create a small test file and run it with `node --test` or a package test script, or use separate simple commands joined only with `&&`. For absence checks, verify a pattern does not exist with `! grep -q 'pattern' file` or `! rg -q 'pattern' file`; do not use `grep -c` or `rg -c` to assert zero matches because count commands exit 1 when they find zero matches, and the verification gate treats that as failure.
48
- 9. Each task persisted with `gsd_plan_task` needs the exact shape: `milestoneId`, `sliceId`, `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. `description` should contain the Why / Do / Done-when narrative. Any npm package name in a task description's install/add instructions (`npm install`, `yarn add`, `pnpm add`) or in `require('...')` or `import ... from '...'` code examples is subject to pre-execution registry validation; reference only packages that exist on the public npm registry, and remove or correct any package name that is not real. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even when there is only one path (for example, `"inputs": ["src/index.ts"]`, never `"inputs": "src/index.ts"`). Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands. **`expectedOutput` must only list files the task actually creates or overwrites on disk.** Do NOT include files the task merely reads, verifies, tests, or describes — those belong in `inputs`, `verify`, `description`, or slice success criteria. If a task is a pure verification or test task that produces no new files, `expectedOutput` must be `[]`; if it writes a test-result log or assertion output file, list only that concrete file path. A file that does not yet exist on disk and is needed as an `input` must be produced by an earlier task's `expectedOutput` — if no prior task creates it, add a task before this one that does. 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.
49
- 10. Persist incrementally through DB-backed tools. First call `gsd_plan_slice` with `milestoneId`, `sliceId`, `goal`, and optional `successCriteria`/`proofLevel`/`integrationClosure`/`observabilityImpact`; omit `tasks` for this metadata call. Then call `gsd_plan_task` once per task, using the task shape above. Each successful tool call saves progress and re-renders `{{outputPath}}` from DB. The DB-backed tools are the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
50
+ 9. Each task persisted with `gsd_plan_task` needs the exact shape: `milestoneId`, `sliceId`, `taskId`, `title`, `description`, `estimate`, `files`, `verify`, `inputs`, `expectedOutput`, and optional `observabilityImpact`. In a parent workspace (when a Declared Repositories block is present above), also set `targetRepositories` to the repository id(s) the task touches. `description` should contain the Why / Do / Done-when narrative. Any npm package name in a task description's install/add instructions (`npm install`, `yarn add`, `pnpm add`) or in `require('...')` or `import ... from '...'` code examples is subject to pre-execution registry validation; reference only packages that exist on the public npm registry, and remove or correct any package name that is not real. `files`, `inputs`, and `expectedOutput` must be JSON arrays of strings, even when there is only one path (for example, `"inputs": ["src/index.ts"]`, never `"inputs": "src/index.ts"`). Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands. **`expectedOutput` must only list files the task actually creates or overwrites on disk.** Do NOT include files the task merely reads, verifies, tests, or describes — those belong in `inputs`, `verify`, `description`, or slice success criteria. If a task is a pure verification or test task that produces no new files, `expectedOutput` must be `[]`; if it writes a test-result log or assertion output file, list only that concrete file path. A file that does not yet exist on disk and is needed as an `input` must be produced by an earlier task's `expectedOutput` — if no prior task creates it, add a task before this one that does. 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.
51
+ 10. Persist incrementally through DB-backed tools. First call `gsd_plan_slice` with `milestoneId`, `sliceId`, `goal`, and optional `successCriteria`/`proofLevel`/`integrationClosure`/`observabilityImpact` (and `targetRepositories` in a parent workspace, to set the slice-wide default); omit `tasks` for this metadata call. Then call `gsd_plan_task` once per task, using the task shape above. Each successful tool call saves progress and re-renders `{{outputPath}}` from DB. The DB-backed tools are the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
50
52
  11. Self-audit before finishing: goal/demo closure, requirement coverage, deliverable coverage audit (cross-check every file listed in CONTEXT.md `## Scope` / `### In Scope` against task `files` or `expectedOutput`), locked decisions, concrete paths, dependency order, wiring, scope size, proof truthfulness, feature completeness, and quality gates. Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable.
51
53
  12. If planning creates structural decisions, call `gsd_decision_save` for each; the tool persists the decision and regenerates `.gsd/DECISIONS.md`.
52
54
  13. {{commitInstruction}}
@@ -0,0 +1,37 @@
1
+ // redact-secrets.ts
2
+ // Best-effort redaction of secret-shaped substrings before persisting free text
3
+ // (exec-sandbox output, activity-log session entries) to disk under .gsd/, which
4
+ // the secret scanner skips. Patterns mirror scripts/secret-scan.mjs.
5
+ const PLACEHOLDER = "«redacted»";
6
+ // Each secret shape. Global flag so replace() catches every occurrence in a line.
7
+ // Order is not significant — matches are replaced independently.
8
+ const PATTERNS = [
9
+ /AKIA[0-9A-Z]{16}/g, // AWS access key id
10
+ /(api[_-]?key|apikey|api[_-]?secret)[ \t]*[:=][ \t]*['"][0-9a-zA-Z_./-]{20,}['"]/gi,
11
+ /(secret|token|password|passwd|pwd|credential)[ \t]*[:=][ \t]*['"][^\s'"]{8,}['"]/gi,
12
+ /(authorization|bearer)[ \t]*[:=][ \t]*['"][^\s'"]{8,}['"]/gi,
13
+ /Bearer[ \t]+[0-9a-zA-Z._-]{8,}/g, // Authorization: Bearer <token> (unquoted)
14
+ /-----BEGIN\s+(RSA|DSA|EC|OPENSSH|PGP)\s+PRIVATE\s+KEY-----[\s\S]*?-----END\s+(RSA|DSA|EC|OPENSSH|PGP)\s+PRIVATE\s+KEY-----/g,
15
+ /(mysql|postgres|postgresql|mongodb|redis|amqp|mssql):\/\/[^\s'"]{8,}/gi,
16
+ /gh[pousr]_[0-9a-zA-Z]{36,}/g, // GitHub token
17
+ /glpat-[0-9a-zA-Z-]{20,}/g, // GitLab token
18
+ /xox[baprs]-[0-9a-zA-Z-]{10,}/g, // Slack token
19
+ /hooks\.slack\.com\/services\/T[0-9A-Z]{8,}\/B[0-9A-Z]{8,}\/[0-9a-zA-Z]{20,}/g,
20
+ /AIza[0-9A-Za-z_-]{35}/g, // Google API key
21
+ /[sr]k_(live|test)_[0-9a-zA-Z]{20,}/g, // Stripe key
22
+ /sk-(ant-)?[0-9a-zA-Z_-]{20,}/g, // OpenAI / Anthropic style key
23
+ /npm_[0-9a-zA-Z]{36,}/g, // npm token
24
+ /(secret|key|token|password)[ \t]*[:=][ \t]*['"]?[0-9a-f]{32,}['"]?/gi, // hex secret
25
+ ];
26
+ /**
27
+ * Replace secret-shaped substrings in `text` with a placeholder, preserving the
28
+ * surrounding content. Pure and safe to call per log line. The placeholder
29
+ * contains no quote/brace/backslash, so redacting a JSON string keeps it valid.
30
+ */
31
+ export function redactSecrets(text) {
32
+ let out = text;
33
+ for (const pattern of PATTERNS) {
34
+ out = out.replace(pattern, PLACEHOLDER);
35
+ }
36
+ return out;
37
+ }
@@ -328,6 +328,22 @@ export function externalProjectsRoot() {
328
328
  const base = process.env.GSD_STATE_DIR || gsdHome();
329
329
  return join(base, "projects");
330
330
  }
331
+ /**
332
+ * Check whether this project already has authoritative external state.
333
+ *
334
+ * Used by legacy `.gsd/` migration to avoid re-copying a re-materialized local
335
+ * directory over newer state when an already-migrated symlink was replaced.
336
+ */
337
+ export function externalStateAlreadyExistsForProject(basePath) {
338
+ const base = process.env.GSD_STATE_DIR || gsdHome();
339
+ const computedPath = join(base, "projects", repoIdentity(basePath));
340
+ if (hasProjectState(computedPath))
341
+ return true;
342
+ const markerId = readGsdIdMarker(resolveGitRoot(basePath));
343
+ if (!markerId)
344
+ return false;
345
+ return hasProjectState(join(base, "projects", markerId));
346
+ }
331
347
  // ─── Numbered Variant Cleanup ────────────────────────────────────────────────
332
348
  /**
333
349
  * macOS collision pattern: `.gsd 2`, `.gsd 3`, `.gsd 4`, etc.
@@ -5,6 +5,11 @@ import { isAbsolute, relative, resolve } from "node:path";
5
5
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
6
6
  import { resolveGsdPathContract } from "./paths.js";
7
7
  export function defaultRepositoryTargets(registry) {
8
+ // In parent mode, default to the declared child repositories so work is
9
+ // attributed to the repo it touches — not silently to the root "project".
10
+ if (registry.mode === "parent") {
11
+ return registry.repositories.filter((repo) => repo.id !== "project").map((repo) => repo.id);
12
+ }
8
13
  const project = registry.byId.get("project");
9
14
  if (project)
10
15
  return [project.id];
@@ -51,7 +56,7 @@ function resolveGitWorkingTreeRoot(basePath) {
51
56
  export function createRepositoryRegistry(basePath, workspacePrefs) {
52
57
  const contract = resolveGsdPathContract(basePath);
53
58
  const projectRoot = contract.isWorktree
54
- ? resolveGitWorkingTreeRoot(contract.workRoot) ?? contract.projectRoot
59
+ ? resolveGitWorkingTreeRoot(contract.workRoot) ?? contract.workRoot
55
60
  : contract.projectRoot;
56
61
  const mode = workspacePrefs?.mode ?? "project";
57
62
  const repoMap = new Map();
@@ -42,12 +42,14 @@ export function captureRootDirtySnapshot(rootPath) {
42
42
  if (!line.trim())
43
43
  continue;
44
44
  const code = line.slice(0, 2);
45
+ if (code !== "??")
46
+ continue;
45
47
  const path = line.slice(3).replace(/^"|"$/g, "");
46
48
  if (!path || isRootRuntimePath(path))
47
49
  continue;
48
50
  snapshot.set(path, {
49
51
  path,
50
- status: code.trim() || code,
52
+ status: code,
51
53
  fingerprint: fileFingerprint(rootPath, path),
52
54
  });
53
55
  }
@@ -58,7 +60,7 @@ export function detectRootWriteLeak(input) {
58
60
  const leaked = [];
59
61
  for (const entry of after.values()) {
60
62
  const prior = input.before?.get(entry.path);
61
- if (!prior || prior.status !== entry.status || prior.fingerprint !== entry.fingerprint) {
63
+ if (!prior || prior.fingerprint !== entry.fingerprint) {
62
64
  leaked.push(entry);
63
65
  }
64
66
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, cpSync } from "node:fs";
1
+ import { existsSync, mkdirSync, cpSync, statSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { logWarning } from "./workflow-logger.js";
4
4
  /**
@@ -40,7 +40,28 @@ export function safeCopyRecursive(src, dst, opts) {
40
40
  return false;
41
41
  try {
42
42
  mkdirSync(dirname(dst), { recursive: true });
43
- cpSync(src, dst, { ...opts, recursive: true });
43
+ const userFilter = opts?.filter;
44
+ cpSync(src, dst, {
45
+ ...opts,
46
+ recursive: true,
47
+ filter: (srcPath, dstPath) => {
48
+ if (existsSync(dstPath)) {
49
+ try {
50
+ const srcStat = statSync(srcPath);
51
+ const dstStat = statSync(dstPath);
52
+ if (srcStat.isDirectory() !== dstStat.isDirectory()) {
53
+ logWarning("fs", `recursive copy skipped type collision: ${srcPath} → ${dstPath}`);
54
+ return false;
55
+ }
56
+ }
57
+ catch (err) {
58
+ logWarning("fs", `recursive copy skipped unreadable path: ${srcPath} → ${dstPath}: ${err.message}`);
59
+ return false;
60
+ }
61
+ }
62
+ return userFilter ? userFilter(srcPath, dstPath) : true;
63
+ },
64
+ });
44
65
  return true;
45
66
  }
46
67
  catch (err) {
@@ -211,17 +211,15 @@ All state lives in `.gsd/` as markdown files (version-controllable):
211
211
 
212
212
  ```
213
213
  .gsd/
214
- milestones/M001/
215
- M001-CONTEXT.md # Requirements, scope, decisions
216
- M001-ROADMAP.md # Slices with tasks, dependencies, checkboxes
217
- M001-SUMMARY.md # Completion summary
218
- slices/S01/
219
- S01-PLAN.md # Task list
220
- S01-SUMMARY.md # Slice summary with frontmatter
221
- tasks/T01-PLAN.md # Individual task spec
214
+ phases/01-foundation/
215
+ 01-CONTEXT.md # Requirements, scope, decisions
216
+ 01-ROADMAP.md # Slices with tasks, dependencies, checkboxes
217
+ 01-SUMMARY.md # Completion summary
218
+ 01-01-PLAN.md # First slice task list
219
+ 01-01-SUMMARY.md # First slice summary with frontmatter
222
220
  ```
223
221
 
224
- State is derived from files on disk checkboxes in ROADMAP.md are the source of truth for completion.
222
+ Runtime state is stored in the project-root database; markdown files are reviewable projections written under `.gsd/phases/`. Legacy projects may still resolve to `.gsd/milestones/<MID>/` until migrated.
225
223
 
226
224
  ## All Headless Commands
227
225
 
@@ -22,6 +22,7 @@ All commands can be run via `gsd headless [command]`.
22
22
  | `status` | Progress dashboard (TUI overlay — useful interactively, not for parsing) |
23
23
  | `visualize` | Workflow visualizer (deps, metrics, timeline) |
24
24
  | `history` | Execution history (supports --cost, --phase, --model, limit) |
25
+ | `codebase` | Manage `.gsd/CODEBASE.md`; parent workspaces include declared child repositories under repo-labeled sections |
25
26
 
26
27
  ## Unit Control
27
28
 
@@ -12,6 +12,47 @@ import { appendEvent } from "../workflow-events.js";
12
12
  import { logWarning } from "../workflow-logger.js";
13
13
  import { loadEffectiveGSDPreferences } from "../preferences.js";
14
14
  import { validatePathOnlyPlanningFields, validatePlanningPathScope } from "../planning-path-scope.js";
15
+ import { createRepositoryRegistryFromPreferences, defaultRepositoryTargets } from "../repository-registry.js";
16
+ function validateRepositoryTargetIds(field, value) {
17
+ const ids = validateStringArray(value, field);
18
+ if (ids.length === 0)
19
+ throw new Error(`${field} must include at least one repository id when provided`);
20
+ const deduped = Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
21
+ if (deduped.length === 0)
22
+ throw new Error(`${field} must include at least one repository id when provided`);
23
+ return deduped;
24
+ }
25
+ function validateReferencedRepositories(targetRepositories, registry) {
26
+ if (!targetRepositories)
27
+ return null;
28
+ const known = new Set(registry.repositories.map((repo) => repo.id));
29
+ const missing = targetRepositories.filter((id) => !known.has(id));
30
+ if (missing.length === 0)
31
+ return null;
32
+ return `unknown targetRepositories: ${missing.join(", ")}. Declared repositories: ${Array.from(known).join(", ")}`;
33
+ }
34
+ function resolveAllowedRootsForPathScope(targetRepositories, registry) {
35
+ if (targetRepositories.length === 0)
36
+ return [registry.projectRoot];
37
+ const roots = targetRepositories
38
+ .map((id) => registry.byId.get(id)?.root)
39
+ .filter((root) => typeof root === "string");
40
+ return roots.length > 0 ? roots : [registry.projectRoot];
41
+ }
42
+ function validatePathScopeForTargetRepositories(params, basePath, registry, targetRepositories) {
43
+ return validatePlanningPathScope(basePath, [
44
+ { field: "files", values: params.files },
45
+ { field: "inputs", values: params.inputs },
46
+ { field: "expectedOutput", values: params.expectedOutput },
47
+ ], resolveAllowedRootsForPathScope(targetRepositories, registry));
48
+ }
49
+ function resolveEffectiveTargetRepositories(taskTargetRepositories, sliceTargetRepositories, defaultTargets) {
50
+ if (taskTargetRepositories)
51
+ return taskTargetRepositories;
52
+ if (sliceTargetRepositories?.length)
53
+ return sliceTargetRepositories;
54
+ return defaultTargets;
55
+ }
15
56
  function validateParams(params) {
16
57
  if (!isNonEmptyString(params?.milestoneId))
17
58
  throw new Error("milestoneId is required");
@@ -35,6 +76,9 @@ function validateParams(params) {
35
76
  files: validateStringArray(params.files, "files"),
36
77
  inputs: validateStringArray(params.inputs, "inputs"),
37
78
  expectedOutput: validateStringArray(params.expectedOutput, "expectedOutput"),
79
+ ...(params.targetRepositories !== undefined
80
+ ? { targetRepositories: validateRepositoryTargetIds("targetRepositories", params.targetRepositories) }
81
+ : {}),
38
82
  };
39
83
  }
40
84
  function resolveTaskGates(basePath) {
@@ -57,22 +101,18 @@ export async function handlePlanTask(rawParams, basePath) {
57
101
  if (pathOnlyError) {
58
102
  return { error: `validation failed: ${pathOnlyError}` };
59
103
  }
60
- const pathScopeError = validatePlanningPathScope(basePath, [
61
- { field: "files", values: params.files },
62
- { field: "inputs", values: params.inputs },
63
- { field: "expectedOutput", values: params.expectedOutput },
64
- ]);
65
- if (pathScopeError) {
66
- return { error: `validation failed: ${pathScopeError}` };
67
- }
68
104
  let taskGates;
105
+ let repositoryRegistry;
69
106
  try {
70
107
  taskGates = resolveTaskGates(basePath);
108
+ const loaded = loadEffectiveGSDPreferences(basePath);
109
+ repositoryRegistry = createRepositoryRegistryFromPreferences(basePath, loaded?.preferences);
71
110
  }
72
111
  catch (err) {
73
112
  const message = err instanceof Error ? err.message : String(err);
74
113
  return { error: `validation failed: ${message}` };
75
114
  }
115
+ const defaultTargets = defaultRepositoryTargets(repositoryRegistry);
76
116
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
77
117
  // Guards must be inside the transaction so the state they check cannot
78
118
  // change between the read and the write (#2723).
@@ -93,6 +133,33 @@ export async function handlePlanTask(rawParams, basePath) {
93
133
  guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
94
134
  return;
95
135
  }
136
+ let effectiveTargetRepositories = resolveEffectiveTargetRepositories(params.targetRepositories, parentSlice.target_repositories, defaultTargets);
137
+ const repoValidationError = validateReferencedRepositories(effectiveTargetRepositories, repositoryRegistry);
138
+ if (repoValidationError) {
139
+ guardError = `validation failed: ${repoValidationError}`;
140
+ return;
141
+ }
142
+ let pathScopeError = validatePathScopeForTargetRepositories(params, basePath, repositoryRegistry, effectiveTargetRepositories);
143
+ const storedTaskTargets = existingTask?.target_repositories?.length
144
+ ? existingTask.target_repositories
145
+ : undefined;
146
+ // Omitted targetRepositories inherit the current slice default first.
147
+ // Fall back to the stored task target only when that inherited scope
148
+ // rejects the replan paths, preserving explicit per-task parent-root work.
149
+ if (pathScopeError && params.targetRepositories === undefined && storedTaskTargets) {
150
+ const storedRepoValidationError = validateReferencedRepositories(storedTaskTargets, repositoryRegistry);
151
+ const storedPathScopeError = storedRepoValidationError
152
+ ? storedRepoValidationError
153
+ : validatePathScopeForTargetRepositories(params, basePath, repositoryRegistry, storedTaskTargets);
154
+ if (!storedPathScopeError) {
155
+ effectiveTargetRepositories = storedTaskTargets;
156
+ pathScopeError = null;
157
+ }
158
+ }
159
+ if (pathScopeError) {
160
+ guardError = `validation failed: ${pathScopeError}`;
161
+ return;
162
+ }
96
163
  if (!existingTask) {
97
164
  insertTask({
98
165
  id: params.taskId,
@@ -112,6 +179,7 @@ export async function handlePlanTask(rawParams, basePath) {
112
179
  expectedOutput: params.expectedOutput,
113
180
  observabilityImpact: params.observabilityImpact ?? "",
114
181
  fullPlanMd: params.fullPlanMd,
182
+ targetRepositories: effectiveTargetRepositories,
115
183
  });
116
184
  for (const gid of taskGates) {
117
185
  insertGateRow({ milestoneId: params.milestoneId, sliceId: params.sliceId, gateId: gid, scope: "task", taskId: params.taskId });
@@ -29,7 +29,9 @@ import { loadEffectiveGSDPreferences } from "../preferences.js";
29
29
  import { parseProject } from "../schemas/parsers.js";
30
30
  import { autoSession, getAutoRuntimeSnapshot, isAutoActive } from "../auto-runtime-state.js";
31
31
  import { renderPlanFromDb } from "../markdown-renderer.js";
32
+ import { readUnitHarnessAbort } from "../unit-runtime.js";
32
33
  import { prepareUatRun, saveUatAttemptArtifact, } from "../uat-run.js";
34
+ import { appendNotification } from "../notification-store.js";
33
35
  import { registerAutoWorker, markWorkerStopping, getAutoWorker } from "../db/auto-workers.js";
34
36
  import { claimMilestoneLease, releaseMilestoneLease, getMilestoneLease, refreshMilestoneLease, milestoneLeaseTtlSeconds, } from "../db/milestone-leases.js";
35
37
  export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [
@@ -65,6 +67,35 @@ function blockIfWrongAutoUnit(requiredUnitType, operation) {
65
67
  isError: true,
66
68
  };
67
69
  }
70
+ function blockIfHarnessAbortedUnit(operation, basePath) {
71
+ const snapshot = getAutoRuntimeSnapshot();
72
+ if (!snapshot.active || !snapshot.currentUnit)
73
+ return null;
74
+ const abort = readUnitHarnessAbort(snapshot.basePath || basePath, snapshot.currentUnit.type, snapshot.currentUnit.id, snapshot.currentUnit.startedAt);
75
+ if (!abort)
76
+ return null;
77
+ return harnessAbortResult(operation, snapshot.currentUnit.type, snapshot.currentUnit.id, abort);
78
+ }
79
+ function harnessAbortResult(operation, unitType, unitId, abort) {
80
+ const toolText = abort.toolName ? ` while calling ${abort.toolName}` : "";
81
+ const message = `Retryable harness abort: ${unitType} ${unitId} was truncated by ${abort.kind}${toolText}. ` +
82
+ "This gate result was not saved; leave the gate pending so auto-mode can rerun it.";
83
+ return {
84
+ content: [{ type: "text", text: message }],
85
+ details: {
86
+ operation,
87
+ error: "harness_aborted_needs_retry",
88
+ retryable: true,
89
+ unitType,
90
+ unitId,
91
+ harnessAbortKind: abort.kind,
92
+ harnessAbortReason: abort.reason,
93
+ harnessAbortToolName: abort.toolName,
94
+ harnessAbortCount: abort.count,
95
+ },
96
+ isError: true,
97
+ };
98
+ }
68
99
  function milestoneLeaseConflictResult(milestoneId, byWorker, expiresAt) {
69
100
  return {
70
101
  content: [{ type: "text", text: `Milestone ${milestoneId} is currently leased by ${byWorker}. Retry after ${expiresAt}.` }],
@@ -959,6 +990,9 @@ export async function executeReassessRoadmap(params, basePath = process.cwd()) {
959
990
  }
960
991
  }
961
992
  export async function executeSaveGateResult(params, basePath = process.cwd()) {
993
+ const harnessAbort = blockIfHarnessAbortedUnit("save_gate_result", basePath);
994
+ if (harnessAbort)
995
+ return harnessAbort;
962
996
  const dbAvailable = await ensureDbOpen(basePath);
963
997
  if (!dbAvailable) {
964
998
  return {
@@ -1023,6 +1057,9 @@ export async function executeUatResultSave(params, basePath = process.cwd()) {
1023
1057
  const unitGuard = blockIfWrongAutoUnit("run-uat", "save_uat_result");
1024
1058
  if (unitGuard)
1025
1059
  return unitGuard;
1060
+ const harnessAbort = blockIfHarnessAbortedUnit("save_uat_result", basePath);
1061
+ if (harnessAbort)
1062
+ return harnessAbort;
1026
1063
  const dbAvailable = await ensureDbOpen(basePath);
1027
1064
  if (!dbAvailable)
1028
1065
  return errorResult("save_uat_result", "GSD database is not available.", "db_unavailable");
@@ -1082,6 +1119,9 @@ export async function executeUatResultSave(params, basePath = process.cwd()) {
1082
1119
  evaluatedAt: run.evaluatedAt,
1083
1120
  });
1084
1121
  invalidateStateCache();
1122
+ if (run.hasHuman) {
1123
+ appendNotification(`UAT for ${run.params.milestoneId}/${run.params.sliceId} has NEEDS-HUMAN checks awaiting human validation`, "warning", "notify", { kind: "uat-needs-human", scope: `${run.params.milestoneId}/${run.params.sliceId}` });
1124
+ }
1085
1125
  const savedText = `UAT result saved for ${run.params.milestoneId}/${run.params.sliceId}: ${run.params.verdict}`;
1086
1126
  return {
1087
1127
  content: [{
@@ -178,9 +178,10 @@ export const UNIT_MANIFESTS = {
178
178
  codebaseMap: false,
179
179
  preferences: "active-only",
180
180
  contextMode: "verification",
181
- // Validation may need to run shell verification commands and apply source
182
- // fixes before milestone closeout can proceed.
183
- tools: TOOLS_ALL,
181
+ // Validation must persist its durable output through gsd_validate_milestone;
182
+ // generic write/bash tools can create filesystem artifacts that bypass DB
183
+ // state and trigger destructive recovery loops.
184
+ tools: { mode: "workflow-only", allowedSubagents: ["reviewer", "security", "tester"] },
184
185
  artifacts: {
185
186
  inline: ["roadmap", "requirements", "templates"],
186
187
  excerpt: ["slice-summary", "slice-assessment"],
@@ -531,7 +532,7 @@ export function compileSubagentPermissionContract(policy) {
531
532
  if (policy.mode === "all") {
532
533
  return { allowed: true, allowedSubagents: ["*"], toolsMode: policy.mode };
533
534
  }
534
- if ((policy.mode === "planning-dispatch" || policy.mode === "verification") &&
535
+ if ((policy.mode === "planning-dispatch" || policy.mode === "verification" || policy.mode === "workflow-only") &&
535
536
  Array.isArray(policy.allowedSubagents)) {
536
537
  return {
537
538
  allowed: policy.allowedSubagents.length > 0,