@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
@@ -9,7 +9,7 @@
9
9
  import { execFileSync } from "node:child_process";
10
10
  import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, statSync, symlinkSync } from "node:fs";
11
11
  import { join } from "node:path";
12
- import { externalGsdRoot, isInsideWorktree } from "./repo-identity.js";
12
+ import { externalGsdRoot, externalStateAlreadyExistsForProject, isInsideWorktree } from "./repo-identity.js";
13
13
  import { getErrorMessage } from "./error-utils.js";
14
14
  import { hasGitTrackedGsdFiles } from "./gitignore.js";
15
15
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
@@ -85,6 +85,16 @@ export function migrateToExternalState(basePath: string): MigrationResult {
85
85
  }
86
86
  }
87
87
 
88
+ // If external state already contains project data, this is not a legacy
89
+ // migration source. It is likely an already-migrated project whose symlink
90
+ // was replaced by a real directory, so copying would risk data loss.
91
+ if (externalStateAlreadyExistsForProject(basePath)) {
92
+ return {
93
+ migrated: false,
94
+ error: "External state already exists for this project; leaving local .gsd directory untouched to avoid overwriting authoritative state",
95
+ };
96
+ }
97
+
88
98
  const externalPath = externalGsdRoot(basePath);
89
99
  const migratingPath = join(basePath, ".gsd.migrating");
90
100
 
@@ -94,12 +104,12 @@ export function migrateToExternalState(basePath: string): MigrationResult {
94
104
 
95
105
  // Rename .gsd -> .gsd.migrating (atomic lock).
96
106
  // On Windows, NTFS may reject rename with EPERM if file descriptors are
97
- // open (VS Code watchers, antivirus on-access scan). Fall back to
98
- // copy+delete (#1292).
107
+ // open (VS Code watchers, antivirus on-access scan). WSL/DrvFs can report
108
+ // the same transient lock as EACCES. Fall back to copy+delete (#1292).
99
109
  try {
100
110
  renameSync(localGsd, migratingPath);
101
111
  } catch (renameErr: any) {
102
- if (renameErr?.code === "EPERM" || renameErr?.code === "EBUSY") {
112
+ if (renameErr?.code === "EPERM" || renameErr?.code === "EBUSY" || renameErr?.code === "EACCES") {
103
113
  try {
104
114
  cpSync(localGsd, migratingPath, { recursive: true, force: true });
105
115
  rmSync(localGsd, { recursive: true, force: true });
@@ -237,7 +247,7 @@ function isLocalGsdExternalStateJunction(basePath: string, localGsd: string): bo
237
247
  * Recover from a failed migration (`.gsd.migrating` exists).
238
248
  * Moves `.gsd.migrating` back to `.gsd` if `.gsd` doesn't exist, or removes an
239
249
  * orphaned staging directory when `.gsd` is already a verified external state
240
- * junction with intact current state.
250
+ * junction, or an intact real directory, with intact current state.
241
251
  */
242
252
  export function recoverFailedMigration(basePath: string): boolean {
243
253
  const localGsd = join(basePath, ".gsd");
@@ -245,8 +255,15 @@ export function recoverFailedMigration(basePath: string): boolean {
245
255
 
246
256
  if (!existsSync(migratingPath)) return false;
247
257
  if (existsSync(localGsd)) {
248
- if (!isLocalGsdExternalStateJunction(basePath, localGsd)) return false;
249
258
  if (!isCurrentGsdStateIntactForMigratingCleanup(basePath)) return false;
259
+ if (!isLocalGsdExternalStateJunction(basePath, localGsd)) {
260
+ try {
261
+ const stat = lstatSync(localGsd);
262
+ if (!stat.isDirectory()) return false;
263
+ } catch {
264
+ return false;
265
+ }
266
+ }
250
267
  try {
251
268
  rmSync(migratingPath, { recursive: true, force: true });
252
269
  return true;
@@ -21,14 +21,14 @@ export type MilestoneMergeTransactionRunner = (
21
21
  ) => MilestoneMergeTransactionResult;
22
22
 
23
23
  export interface MilestoneMergeTransactionDeps {
24
- mergeMilestoneToMain: MilestoneMergeTransactionRunner;
24
+ mergeMilestone: MilestoneMergeTransactionRunner;
25
25
  }
26
26
 
27
27
  export function runMilestoneMergeTransaction(
28
28
  deps: MilestoneMergeTransactionDeps,
29
29
  input: MilestoneMergeTransactionInput,
30
30
  ): MilestoneMergeTransactionResult {
31
- return deps.mergeMilestoneToMain(
31
+ return deps.mergeMilestone(
32
32
  input.basePath,
33
33
  input.milestoneId,
34
34
  input.roadmapContent,
@@ -36,11 +36,11 @@ export function runMilestoneMergeTransaction(
36
36
  }
37
37
 
38
38
  export function createMilestoneMergeTransaction(
39
- mergeMilestoneToMain: MilestoneMergeTransactionRunner,
39
+ mergeMilestone: MilestoneMergeTransactionRunner,
40
40
  ): MilestoneMergeTransactionRunner {
41
41
  return function mergeMilestoneTransaction(basePath, milestoneId, roadmapContent) {
42
42
  return runMilestoneMergeTransaction(
43
- { mergeMilestoneToMain },
43
+ { mergeMilestone },
44
44
  { basePath, milestoneId, roadmapContent },
45
45
  );
46
46
  };
@@ -24,11 +24,10 @@ export interface DynamicRoutingConfig {
24
24
  cross_provider?: boolean; // default: true
25
25
  hooks?: boolean; // default: true
26
26
  /**
27
- * Opt into dynamic routing for flat-rate providers (e.g. claude-code,
28
- * GitHub Copilot). Default false preserves the #3453 bypass that skips
29
- * routing when the subscription makes per-request cost identical.
30
- * Enable only when you want per-task model selection across a flat-rate
31
- * subscription (e.g. haiku for research, opus for architecture). (#4386)
27
+ * Override dynamic routing for flat-rate providers (e.g. claude-code,
28
+ * GitHub Copilot). Omit or set true to honor `dynamic_routing.enabled`;
29
+ * set false to preserve one-model dispatch when the subscription makes
30
+ * per-request cost identical.
32
31
  */
33
32
  allow_flat_rate_providers?: boolean;
34
33
  }
@@ -1,8 +1,10 @@
1
1
  import type { PreferenceDiagnostic } from "./preferences-types.js";
2
2
  import {
3
+ loadEffectiveGSDPreferences,
3
4
  loadGlobalGSDPreferences,
4
5
  loadProjectGSDPreferences,
5
6
  } from "./preferences.js";
7
+ import { crossAxisPreferenceWarnings } from "./preferences-validation.js";
6
8
 
7
9
  interface PreferenceNotificationContext {
8
10
  ui: {
@@ -30,6 +32,28 @@ export function collectPreferenceDiagnostics(basePath?: string): PreferenceDiagn
30
32
  seen.add(signature);
31
33
  unique.push(diagnostic);
32
34
  }
35
+
36
+ const effective = loadEffectiveGSDPreferences(basePath);
37
+ if (effective) {
38
+ const existingMessages = new Set(unique.map((diagnostic) => diagnostic.message));
39
+ for (const message of crossAxisPreferenceWarnings(effective.preferences)) {
40
+ if (existingMessages.has(message)) continue;
41
+ const diagnostic: PreferenceDiagnostic = {
42
+ path: effective.path,
43
+ scope: effective.scope,
44
+ severity: "warning",
45
+ kind: "validation",
46
+ message,
47
+ sanitized: true,
48
+ };
49
+ const signature = preferenceDiagnosticSignature(diagnostic);
50
+ if (seen.has(signature)) continue;
51
+ seen.add(signature);
52
+ existingMessages.add(message);
53
+ unique.push(diagnostic);
54
+ }
55
+ }
56
+
33
57
  return unique;
34
58
  }
35
59
 
@@ -99,10 +99,13 @@ export function resolveModelWithFallbacksForUnit(
99
99
  basePath?: string,
100
100
  availableModelIds?: string[],
101
101
  preferredModelId?: string,
102
+ skipProfileDefaults = false,
102
103
  ): ResolvedModelConfig | undefined {
103
- const loadOpts = availableModelIds !== undefined || preferredModelId !== undefined
104
- ? { availableModelIds, preferredModelId }
105
- : undefined;
104
+ const loadOpts = {
105
+ ...(availableModelIds !== undefined ? { availableModelIds } : {}),
106
+ ...(preferredModelId !== undefined ? { preferredModelId } : {}),
107
+ ...(skipProfileDefaults ? { skipProfileDefaults: true } : {}),
108
+ };
106
109
  const prefs = loadEffectiveGSDPreferences(basePath, loadOpts);
107
110
  const chain = phaseChainForUnit(unitType);
108
111
  if (!chain) return undefined;
@@ -51,6 +51,16 @@ const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
51
51
  ]);
52
52
  const VALID_GATE_EVALUATE_SLICE_GATES = new Set<string>(getGateIdsForTurn("gate-evaluate"));
53
53
 
54
+ /** Cross-axis warnings that only apply to the effective (merged) preference shape. */
55
+ export function crossAxisPreferenceWarnings(preferences: GSDPreferences): string[] {
56
+ if (preferences.mode === "team" && preferences.workspace?.mode === "parent") {
57
+ return [
58
+ "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",
59
+ ];
60
+ }
61
+ return [];
62
+ }
63
+
54
64
  export function validatePreferences(preferences: GSDPreferences): {
55
65
  preferences: GSDPreferences;
56
66
  errors: string[];
@@ -509,8 +519,11 @@ export function validatePreferences(preferences: GSDPreferences): {
509
519
 
510
520
  // ─── Remote Questions ───────────────────────────────────────────────
511
521
  if (preferences.remote_questions !== undefined) {
512
- if (preferences.remote_questions && typeof preferences.remote_questions === "object") {
513
- validated.remote_questions = preferences.remote_questions;
522
+ const remoteQuestions = preferences.remote_questions as unknown;
523
+ if (typeof remoteQuestions === "object" && remoteQuestions !== null) {
524
+ validated.remote_questions = remoteQuestions as GSDPreferences["remote_questions"];
525
+ } else if (remoteQuestions === false) {
526
+ // Explicit disable for a globally-configured remote questions channel.
514
527
  } else {
515
528
  errors.push("remote_questions must be an object");
516
529
  }
@@ -1495,6 +1508,17 @@ export function validatePreferences(preferences: GSDPreferences): {
1495
1508
  } else {
1496
1509
  errors.push("workspace must be an object");
1497
1510
  }
1511
+
1512
+ // parent mode is behavioral: it must coordinate at least one declared
1513
+ // child repository. Without one it is indistinguishable from project
1514
+ // mode, so reject it explicitly rather than silently degrading.
1515
+ if (validated.workspace?.mode === "parent") {
1516
+ const childRepoCount = validated.workspace.repositories ? Object.keys(validated.workspace.repositories).length : 0;
1517
+ if (childRepoCount === 0) {
1518
+ errors.push('workspace.mode "parent" requires at least one repository under workspace.repositories');
1519
+ }
1520
+ }
1521
+
1498
1522
  }
1499
1523
 
1500
1524
  // ─── Enhanced Verification ──────────────────────────────────────────────────
@@ -1568,5 +1592,7 @@ export function validatePreferences(preferences: GSDPreferences): {
1568
1592
  }
1569
1593
  }
1570
1594
 
1595
+ warnings.push(...crossAxisPreferenceWarnings(validated));
1596
+
1571
1597
  return { preferences: validated, errors, warnings };
1572
1598
  }
@@ -39,7 +39,7 @@ import {
39
39
  type SkillDiscoveryMode,
40
40
  formatSkillRef,
41
41
  } from "./preferences-types.js";
42
- import { validatePreferences } from "./preferences-validation.js";
42
+ import { crossAxisPreferenceWarnings, validatePreferences } from "./preferences-validation.js";
43
43
  import { gsdHome } from "./gsd-home.js";
44
44
 
45
45
  // ─── Re-exports: types ──────────────────────────────────────────────────────
@@ -270,6 +270,12 @@ export function normalizePreferencesShape(
270
270
  const EFFECTIVE_PREFERENCES_CACHE_MAX = 64;
271
271
  const effectivePreferencesCache = new Map<string, LoadedGSDPreferences | null>();
272
272
 
273
+ type EffectivePreferencesLoadOptions = {
274
+ availableModelIds?: string[];
275
+ preferredModelId?: string;
276
+ skipProfileDefaults?: boolean;
277
+ };
278
+
273
279
  export function clearGSDPreferencesCache(): void {
274
280
  effectivePreferencesCache.clear();
275
281
  }
@@ -301,7 +307,7 @@ function preferencesFileSignature(path: string): string {
301
307
 
302
308
  function effectivePreferencesCacheKey(
303
309
  basePath?: string,
304
- opts?: { availableModelIds?: string[]; preferredModelId?: string },
310
+ opts?: EffectivePreferencesLoadOptions,
305
311
  ): string {
306
312
  return JSON.stringify({
307
313
  basePath: basePath ?? null,
@@ -309,6 +315,7 @@ function effectivePreferencesCacheKey(
309
315
  project: projectPreferencesCandidatePaths(basePath).map(preferencesFileSignature),
310
316
  availableModelIds: opts?.availableModelIds ?? null,
311
317
  preferredModelId: opts?.preferredModelId ?? null,
318
+ skipProfileDefaults: opts?.skipProfileDefaults === true,
312
319
  });
313
320
  }
314
321
 
@@ -338,7 +345,7 @@ export function loadProjectGSDPreferences(basePath?: string): LoadedGSDPreferenc
338
345
 
339
346
  export function loadEffectiveGSDPreferences(
340
347
  basePath?: string,
341
- opts?: { availableModelIds?: string[]; preferredModelId?: string },
348
+ opts?: EffectivePreferencesLoadOptions,
342
349
  ): LoadedGSDPreferences | null {
343
350
  const cacheKey = effectivePreferencesCacheKey(basePath, opts);
344
351
  if (effectivePreferencesCache.has(cacheKey)) {
@@ -384,7 +391,7 @@ export function loadEffectiveGSDPreferences(
384
391
  } else {
385
392
  profileForDefaults = DEFAULT_TOKEN_PROFILE;
386
393
  }
387
- if (profileForDefaults) {
394
+ if (profileForDefaults && !opts?.skipProfileDefaults) {
388
395
  const profileDefaults = _resolveProfileDefaults(
389
396
  profileForDefaults,
390
397
  opts?.availableModelIds,
@@ -409,11 +416,38 @@ export function loadEffectiveGSDPreferences(
409
416
  }
410
417
 
411
418
  result = stripInheritedPlanningDepth(result, projectHasPlanningDepth);
419
+ result = appendCrossAxisPreferenceWarnings(result);
412
420
 
413
421
  cacheEffectivePreferences(cacheKey, result);
414
422
  return result;
415
423
  }
416
424
 
425
+ function appendCrossAxisPreferenceWarnings(loaded: LoadedGSDPreferences): LoadedGSDPreferences {
426
+ const crossWarnings = crossAxisPreferenceWarnings(loaded.preferences);
427
+ const existingMessages = new Set([
428
+ ...(loaded.warnings ?? []),
429
+ ...(loaded.diagnostics ?? []).map((diagnostic) => diagnostic.message),
430
+ ]);
431
+ const newWarnings = crossWarnings.filter((message) => !existingMessages.has(message));
432
+ if (newWarnings.length === 0) return loaded;
433
+
434
+ return {
435
+ ...loaded,
436
+ warnings: [...(loaded.warnings ?? []), ...newWarnings],
437
+ diagnostics: [
438
+ ...(loaded.diagnostics ?? []),
439
+ ...newWarnings.map((message): PreferenceDiagnostic => ({
440
+ path: loaded.path,
441
+ scope: loaded.scope,
442
+ severity: "warning",
443
+ kind: "validation",
444
+ message,
445
+ sanitized: true,
446
+ })),
447
+ ],
448
+ };
449
+ }
450
+
417
451
  function withoutProfilePhaseDefaults(defaults: Partial<GSDPreferences>): Partial<GSDPreferences> {
418
452
  if (defaults.phases === undefined) return defaults;
419
453
  const { phases: _phases, ...rest } = 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,40 @@
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
+
6
+ const PLACEHOLDER = "«redacted»";
7
+
8
+ // Each secret shape. Global flag so replace() catches every occurrence in a line.
9
+ // Order is not significant — matches are replaced independently.
10
+ const PATTERNS: RegExp[] = [
11
+ /AKIA[0-9A-Z]{16}/g, // AWS access key id
12
+ /(api[_-]?key|apikey|api[_-]?secret)[ \t]*[:=][ \t]*['"][0-9a-zA-Z_./-]{20,}['"]/gi,
13
+ /(secret|token|password|passwd|pwd|credential)[ \t]*[:=][ \t]*['"][^\s'"]{8,}['"]/gi,
14
+ /(authorization|bearer)[ \t]*[:=][ \t]*['"][^\s'"]{8,}['"]/gi,
15
+ /Bearer[ \t]+[0-9a-zA-Z._-]{8,}/g, // Authorization: Bearer <token> (unquoted)
16
+ /-----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,
17
+ /(mysql|postgres|postgresql|mongodb|redis|amqp|mssql):\/\/[^\s'"]{8,}/gi,
18
+ /gh[pousr]_[0-9a-zA-Z]{36,}/g, // GitHub token
19
+ /glpat-[0-9a-zA-Z-]{20,}/g, // GitLab token
20
+ /xox[baprs]-[0-9a-zA-Z-]{10,}/g, // Slack token
21
+ /hooks\.slack\.com\/services\/T[0-9A-Z]{8,}\/B[0-9A-Z]{8,}\/[0-9a-zA-Z]{20,}/g,
22
+ /AIza[0-9A-Za-z_-]{35}/g, // Google API key
23
+ /[sr]k_(live|test)_[0-9a-zA-Z]{20,}/g, // Stripe key
24
+ /sk-(ant-)?[0-9a-zA-Z_-]{20,}/g, // OpenAI / Anthropic style key
25
+ /npm_[0-9a-zA-Z]{36,}/g, // npm token
26
+ /(secret|key|token|password)[ \t]*[:=][ \t]*['"]?[0-9a-f]{32,}['"]?/gi, // hex secret
27
+ ];
28
+
29
+ /**
30
+ * Replace secret-shaped substrings in `text` with a placeholder, preserving the
31
+ * surrounding content. Pure and safe to call per log line. The placeholder
32
+ * contains no quote/brace/backslash, so redacting a JSON string keeps it valid.
33
+ */
34
+ export function redactSecrets(text: string): string {
35
+ let out = text;
36
+ for (const pattern of PATTERNS) {
37
+ out = out.replace(pattern, PLACEHOLDER);
38
+ }
39
+ return out;
40
+ }
@@ -355,6 +355,23 @@ export function externalProjectsRoot(): string {
355
355
  return join(base, "projects");
356
356
  }
357
357
 
358
+ /**
359
+ * Check whether this project already has authoritative external state.
360
+ *
361
+ * Used by legacy `.gsd/` migration to avoid re-copying a re-materialized local
362
+ * directory over newer state when an already-migrated symlink was replaced.
363
+ */
364
+ export function externalStateAlreadyExistsForProject(basePath: string): boolean {
365
+ const base = process.env.GSD_STATE_DIR || gsdHome();
366
+ const computedPath = join(base, "projects", repoIdentity(basePath));
367
+ if (hasProjectState(computedPath)) return true;
368
+
369
+ const markerId = readGsdIdMarker(resolveGitRoot(basePath));
370
+ if (!markerId) return false;
371
+
372
+ return hasProjectState(join(base, "projects", markerId));
373
+ }
374
+
358
375
  // ─── Numbered Variant Cleanup ────────────────────────────────────────────────
359
376
 
360
377
  /**
@@ -23,6 +23,12 @@ export interface RepositoryRegistry {
23
23
  }
24
24
 
25
25
  export function defaultRepositoryTargets(registry: RepositoryRegistry): string[] {
26
+ // In parent mode, default to the declared child repositories so work is
27
+ // attributed to the repo it touches — not silently to the root "project".
28
+ if (registry.mode === "parent") {
29
+ return registry.repositories.filter((repo) => repo.id !== "project").map((repo) => repo.id);
30
+ }
31
+
26
32
  const project = registry.byId.get("project");
27
33
  if (project) return [project.id];
28
34
  const first = registry.repositories[0];
@@ -78,7 +84,7 @@ export function createRepositoryRegistry(
78
84
  ): RepositoryRegistry {
79
85
  const contract = resolveGsdPathContract(basePath);
80
86
  const projectRoot = contract.isWorktree
81
- ? resolveGitWorkingTreeRoot(contract.workRoot) ?? contract.projectRoot
87
+ ? resolveGitWorkingTreeRoot(contract.workRoot) ?? contract.workRoot
82
88
  : contract.projectRoot;
83
89
  const mode = workspacePrefs?.mode ?? "project";
84
90
  const repoMap = new Map<string, RegisteredRepository>();
@@ -65,11 +65,12 @@ export function captureRootDirtySnapshot(rootPath: string): RootDirtySnapshot {
65
65
  for (const line of status.split("\n")) {
66
66
  if (!line.trim()) continue;
67
67
  const code = line.slice(0, 2);
68
+ if (code !== "??") continue;
68
69
  const path = line.slice(3).replace(/^"|"$/g, "");
69
70
  if (!path || isRootRuntimePath(path)) continue;
70
71
  snapshot.set(path, {
71
72
  path,
72
- status: code.trim() || code,
73
+ status: code,
73
74
  fingerprint: fileFingerprint(rootPath, path),
74
75
  });
75
76
  }
@@ -87,7 +88,7 @@ export function detectRootWriteLeak(input: {
87
88
  const leaked: RootDirtyEntry[] = [];
88
89
  for (const entry of after.values()) {
89
90
  const prior = input.before?.get(entry.path);
90
- if (!prior || prior.status !== entry.status || prior.fingerprint !== entry.fingerprint) {
91
+ if (!prior || prior.fingerprint !== entry.fingerprint) {
91
92
  leaked.push(entry);
92
93
  }
93
94
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, cpSync, type CopySyncOptions } from "node:fs"
1
+ import { existsSync, mkdirSync, cpSync, statSync, type CopySyncOptions } from "node:fs"
2
2
  import { dirname } from "node:path"
3
3
  import { logWarning } from "./workflow-logger.js"
4
4
 
@@ -39,7 +39,27 @@ export function safeCopyRecursive(src: string, dst: string, opts?: Omit<CopySync
39
39
  if (!existsSync(src)) return false
40
40
  try {
41
41
  mkdirSync(dirname(dst), { recursive: true })
42
- cpSync(src, dst, { ...opts, recursive: true })
42
+ const userFilter = opts?.filter
43
+ cpSync(src, dst, {
44
+ ...opts,
45
+ recursive: true,
46
+ filter: (srcPath, dstPath) => {
47
+ if (existsSync(dstPath)) {
48
+ try {
49
+ const srcStat = statSync(srcPath)
50
+ const dstStat = statSync(dstPath)
51
+ if (srcStat.isDirectory() !== dstStat.isDirectory()) {
52
+ logWarning("fs", `recursive copy skipped type collision: ${srcPath} → ${dstPath}`)
53
+ return false
54
+ }
55
+ } catch (err) {
56
+ logWarning("fs", `recursive copy skipped unreadable path: ${srcPath} → ${dstPath}: ${(err as Error).message}`)
57
+ return false
58
+ }
59
+ }
60
+ return userFilter ? userFilter(srcPath, dstPath) : true
61
+ },
62
+ })
43
63
  return true
44
64
  } catch (err) {
45
65
  logWarning("fs", `recursive copy failed: ${src} → ${dst}: ${(err as Error).message}`)
@@ -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