@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
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { isAbsolute, join, relative } from "node:path";
3
- import { getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, _getAdapter, } from "./gsd-db.js";
2
+ import { isAbsolute, join, relative, sep } from "node:path";
3
+ import { deleteArtifactByPath, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, isMemoriesFtsAvailable, repairTaskCompletionFromSummary, _getAdapter, } from "./gsd-db.js";
4
4
  import { MEMORIES_FTS_REBUILT_KEY } from "./db-memory-fts-schema.js";
5
5
  import { isAfter, latestExplicitReopenAt } from "./milestone-reopen-events.js";
6
6
  import { gsdProjectionRoot, gsdRoot, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "./paths.js";
@@ -11,9 +11,38 @@ import { readEvents } from "./workflow-events.js";
11
11
  import { flushWorkflowProjections } from "./projection-flush.js";
12
12
  import { parseRoadmapSlices } from "./roadmap-slices.js";
13
13
  import { parsePlan } from "./parsers-legacy.js";
14
+ import { parseSummary } from "./files.js";
15
+ const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
16
+ function escapeRegExp(value) {
17
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18
+ }
14
19
  function relativeFile(basePath, filePath) {
15
20
  return relative(basePath, filePath).split("\\").join("/");
16
21
  }
22
+ function normalizedArtifactType(artifactType) {
23
+ return artifactType.trim().toUpperCase();
24
+ }
25
+ function isUserAuthoredArtifactType(artifactType) {
26
+ return USER_AUTHORED_ARTIFACT_TYPES.has(normalizedArtifactType(artifactType));
27
+ }
28
+ function userContentRecoveryCommand(artifactType) {
29
+ return normalizedArtifactType(artifactType) === "CONTEXT" ? "/gsd discuss" : "/gsd auto";
30
+ }
31
+ function userContentMissingMessage(path, artifactType) {
32
+ const type = normalizedArtifactType(artifactType) || "UNKNOWN";
33
+ return `Artifact \`${path}\` is a user-authored ${type} file recorded in the database but missing from disk. Re-run \`${userContentRecoveryCommand(type)}\` in this milestone to regenerate it.`;
34
+ }
35
+ function artifactPathRelativeToGsd(artifactPath) {
36
+ const parts = artifactPath.split(/[\\/]+/);
37
+ const gsdIndex = parts.lastIndexOf(".gsd");
38
+ if (gsdIndex < 0 || gsdIndex === parts.length - 1)
39
+ return artifactPath;
40
+ return parts.slice(gsdIndex + 1).join("/");
41
+ }
42
+ function isPathInside(basePath, candidatePath) {
43
+ const rel = relative(basePath, candidatePath);
44
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
45
+ }
17
46
  function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, unitId, status, checkboxDone) {
18
47
  const dbDone = isClosedStatus(status);
19
48
  if (checkboxDone === dbDone)
@@ -85,12 +114,97 @@ function isClearedByMilestoneShellProjectionFlush(basePath, issue, reRenderedMil
85
114
  return issue.file === relativeFile(basePath, roadmapPath);
86
115
  }
87
116
  function artifactExistsOnDisk(basePath, artifactPath) {
88
- if (isAbsolute(artifactPath))
89
- return existsSync(artifactPath);
90
- return [
91
- join(gsdProjectionRoot(basePath), artifactPath),
92
- join(gsdRoot(basePath), artifactPath),
93
- ].some((candidate) => existsSync(candidate));
117
+ return resolveArtifactDiskPath(basePath, artifactPath) !== null;
118
+ }
119
+ function resolveArtifactDiskPath(basePath, artifactPath) {
120
+ const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
121
+ if (isAbsolute(relativeArtifactPath)) {
122
+ return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
123
+ }
124
+ for (const root of [gsdProjectionRoot(basePath), gsdRoot(basePath)]) {
125
+ const candidate = join(root, relativeArtifactPath);
126
+ if (isPathInside(root, candidate) && existsSync(candidate))
127
+ return candidate;
128
+ }
129
+ return null;
130
+ }
131
+ function taskExistsInPlan(basePath, milestoneId, sliceId, taskId) {
132
+ const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN");
133
+ if (!planPath || !existsSync(planPath))
134
+ return false;
135
+ try {
136
+ return parsePlan(readFileSync(planPath, "utf-8")).tasks.some((task) => task.id === taskId);
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ }
142
+ function isPassingVerificationResult(value) {
143
+ const normalized = value.trim().toLowerCase();
144
+ if (!normalized)
145
+ return false;
146
+ if (/\b(fail(?:ed|ing)?|error|blocked|mixed|untested)\b/.test(normalized))
147
+ return false;
148
+ if (/\b(?:not|never|no|didn't|did\s+not|cannot|can't|won't|couldn't)\s+(?:\w+\s+){0,3}?(?:pass(?:ed|ing)?|success(?:ful)?|succeeded)\b/.test(normalized)) {
149
+ return false;
150
+ }
151
+ return /\b(pass(?:ed|ing)?|success(?:ful)?|succeeded)\b/.test(normalized) || normalized === "all-pass";
152
+ }
153
+ function readTaskCompletionEvidenceFromSummary(basePath, row) {
154
+ if (!row.slice_id || !row.task_id)
155
+ return null;
156
+ if (!row.task_status && Number(row.task_count) > 0 && !taskExistsInPlan(basePath, row.milestone_id, row.slice_id, row.task_id)) {
157
+ return null;
158
+ }
159
+ const diskPath = resolveArtifactDiskPath(basePath, row.path);
160
+ if (!diskPath)
161
+ return null;
162
+ try {
163
+ const fullSummaryMd = readFileSync(diskPath, "utf-8");
164
+ const summary = parseSummary(fullSummaryMd);
165
+ const fm = summary.frontmatter;
166
+ if (fm.id !== row.task_id || fm.parent !== row.slice_id || fm.milestone !== row.milestone_id)
167
+ return null;
168
+ if (fm.blocker_discovered)
169
+ return null;
170
+ if (!isPassingVerificationResult(fm.verification_result))
171
+ return null;
172
+ const completedAt = fm.completed_at.trim();
173
+ if (!completedAt || !Number.isFinite(Date.parse(completedAt)))
174
+ return null;
175
+ return {
176
+ completedAt,
177
+ verificationResult: fm.verification_result.trim(),
178
+ title: summary.title.replace(new RegExp(`^${escapeRegExp(row.task_id)}:\\s*`), "").trim(),
179
+ oneLiner: summary.oneLiner,
180
+ narrative: summary.whatHappened,
181
+ duration: fm.duration,
182
+ blockerDiscovered: fm.blocker_discovered,
183
+ deviations: summary.deviations,
184
+ knownIssues: summary.knownLimitations,
185
+ keyFiles: fm.key_files.filter((file) => file !== "(none)"),
186
+ keyDecisions: fm.key_decisions.filter((decision) => decision !== "(none)"),
187
+ fullSummaryMd,
188
+ };
189
+ }
190
+ catch {
191
+ return null;
192
+ }
193
+ }
194
+ function repairTaskArtifactDbStatusDivergence(basePath, row) {
195
+ const evidence = readTaskCompletionEvidenceFromSummary(basePath, row);
196
+ if (!evidence || !row.slice_id || !row.task_id)
197
+ return false;
198
+ repairTaskCompletionFromSummary({
199
+ milestoneId: row.milestone_id,
200
+ sliceId: row.slice_id,
201
+ taskId: row.task_id,
202
+ ...evidence,
203
+ });
204
+ return true;
205
+ }
206
+ function artifactDbStatusDivergenceFixable(basePath, row) {
207
+ return readTaskCompletionEvidenceFromSummary(basePath, row) !== null;
94
208
  }
95
209
  function artifactUnitId(row) {
96
210
  if (!row.milestone_id)
@@ -110,7 +224,39 @@ function artifactScope(row) {
110
224
  return "milestone";
111
225
  return "project";
112
226
  }
113
- export async function checkEngineHealth(basePath, issues, fixesApplied) {
227
+ function sameArtifactIdentity(left, right) {
228
+ return left.artifact_type === right.artifact_type &&
229
+ left.milestone_id === right.milestone_id &&
230
+ left.slice_id === right.slice_id &&
231
+ left.task_id === right.task_id;
232
+ }
233
+ function isMilestonesArtifactPath(artifactPath) {
234
+ return artifactPathRelativeToGsd(artifactPath).startsWith("milestones/");
235
+ }
236
+ function expectedMilestonesArtifactPath(row) {
237
+ if (!row.milestone_id)
238
+ return null;
239
+ const artifactType = normalizedArtifactType(row.artifact_type);
240
+ if (!artifactType)
241
+ return null;
242
+ if (row.slice_id && row.task_id) {
243
+ return `milestones/${row.milestone_id}/slices/${row.slice_id}/tasks/${row.task_id}-${artifactType}.md`;
244
+ }
245
+ if (row.slice_id) {
246
+ return `milestones/${row.milestone_id}/slices/${row.slice_id}/${row.slice_id}-${artifactType}.md`;
247
+ }
248
+ return `milestones/${row.milestone_id}/${row.milestone_id}-${artifactType}.md`;
249
+ }
250
+ function hasPresentMilestonesReplacement(basePath, row, artifactRows) {
251
+ const expectedPath = expectedMilestonesArtifactPath(row);
252
+ if (expectedPath && artifactExistsOnDisk(basePath, expectedPath))
253
+ return true;
254
+ return artifactRows.some((other) => other.path !== row.path &&
255
+ isMilestonesArtifactPath(other.path) &&
256
+ sameArtifactIdentity(row, other) &&
257
+ artifactExistsOnDisk(basePath, other.path));
258
+ }
259
+ export async function checkEngineHealth(basePath, issues, fixesApplied, options) {
114
260
  const dbPath = resolveGsdPathContract(basePath).projectDb;
115
261
  if (!isDbAvailable() && existsSync(dbPath)) {
116
262
  issues.push({
@@ -301,6 +447,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
301
447
  // Non-fatal — completed-milestone reopen check failed
302
448
  }
303
449
  // f. Artifact rows reference files that no longer exist on disk.
450
+ const missingUserContentArtifacts = [];
304
451
  try {
305
452
  const artifactRows = adapter
306
453
  .prepare(`SELECT path, artifact_type, milestone_id, slice_id, task_id
@@ -312,20 +459,48 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
312
459
  if (artifactExistsOnDisk(basePath, row.path))
313
460
  continue;
314
461
  const unitId = artifactUnitId(row);
462
+ const issuePath = artifactPathRelativeToGsd(row.path);
463
+ if (options?.repair && issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows)) {
464
+ // Route the write through the Single Writer owner (gsd-db.ts) instead
465
+ // of issuing raw DELETE SQL here — doctor is a read-only consumer and
466
+ // the single-writer invariant forbids write SQL outside the allowlist.
467
+ deleteArtifactByPath(row.path);
468
+ fixesApplied.push(`pruned stale flat-phase artifact row ${row.path}`);
469
+ continue;
470
+ }
471
+ if (isUserAuthoredArtifactType(row.artifact_type)) {
472
+ const artifactType = normalizedArtifactType(row.artifact_type);
473
+ missingUserContentArtifacts.push({ path: issuePath, artifactType });
474
+ issues.push({
475
+ severity: "warning",
476
+ code: "artifact_user_content_missing",
477
+ scope: artifactScope(row),
478
+ unitId,
479
+ message: userContentMissingMessage(issuePath, artifactType),
480
+ file: issuePath,
481
+ fixable: false,
482
+ });
483
+ continue;
484
+ }
315
485
  issues.push({
316
486
  severity: "error",
317
487
  code: "artifact_file_missing",
318
488
  scope: artifactScope(row),
319
489
  unitId,
320
- message: `Artifact ${row.path} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
321
- file: row.path,
322
- fixable: false,
490
+ message: `Artifact ${issuePath} is recorded in the database as ${row.artifact_type || "UNKNOWN"} but no matching file exists on disk`,
491
+ file: issuePath,
492
+ fixable: issuePath.startsWith("phases/") && hasPresentMilestonesReplacement(basePath, row, artifactRows),
323
493
  });
324
494
  }
325
495
  }
326
496
  catch {
327
497
  // Non-fatal — artifact file existence check failed
328
498
  }
499
+ if (options?.repair) {
500
+ for (const artifact of missingUserContentArtifacts) {
501
+ fixesApplied.push(`skipped user-authored ${artifact.artifactType} artifact ${artifact.path} (content cannot be regenerated from the database)`);
502
+ }
503
+ }
329
504
  // g. Completion artifacts disagree with open DB hierarchy rows.
330
505
  try {
331
506
  const rows = adapter
@@ -361,13 +536,30 @@ export async function checkEngineHealth(basePath, issues, fixesApplied) {
361
536
  if (seen.has(unitId))
362
537
  continue;
363
538
  seen.add(unitId);
539
+ const fixable = artifactDbStatusDivergenceFixable(basePath, row);
540
+ let repairFailed = false;
541
+ if (options?.repair && fixable) {
542
+ try {
543
+ if (repairTaskArtifactDbStatusDivergence(basePath, row)) {
544
+ fixesApplied.push(`repaired task completion from SUMMARY artifact for ${unitId}`);
545
+ continue;
546
+ }
547
+ }
548
+ catch {
549
+ repairFailed = true;
550
+ }
551
+ }
364
552
  issues.push({
365
553
  severity: "error",
366
554
  code: "artifact_db_status_divergence",
367
555
  scope: row.task_id ? "task" : row.slice_id ? "slice" : "milestone",
368
556
  unitId,
369
- message: `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Runtime will not import it silently; run explicit recovery/repair after review.`,
370
- fixable: false,
557
+ message: repairFailed
558
+ ? `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Doctor found valid SUMMARY completion evidence but could not repair the database state.`
559
+ : fixable
560
+ ? `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Doctor can repair this from the SUMMARY completion evidence.`
561
+ : `Completion artifact ${row.path} exists while DB state for ${unitId} is still open or missing. Runtime will not import it silently; run explicit recovery/repair after review.`,
562
+ fixable,
371
563
  });
372
564
  }
373
565
  }
@@ -12,12 +12,13 @@
12
12
  */
13
13
  import { existsSync, readFileSync } from "node:fs";
14
14
  import { access, readFile } from "node:fs/promises";
15
+ import { homedir } from "node:os";
15
16
  import { delimiter, join } from "node:path";
16
17
  import { AuthStorage } from "@gsd/pi-coding-agent";
17
18
  import { getEnvApiKey } from "@gsd/pi-ai";
19
+ import { isCursorAgentReadyUncached } from "../cursor-cli/readiness.js";
18
20
  import { loadEffectiveGSDPreferences } from "./preferences.js";
19
21
  import { getAuthPath, PROVIDER_REGISTRY, supportsBrowserOAuth } from "./key-manager.js";
20
- import { homedir } from "node:os";
21
22
  // ── Provider routing constants ────────────────────────────────────────────────
22
23
  /**
23
24
  * Providers that use external CLI authentication (not API keys).
@@ -27,6 +28,7 @@ const CLI_AUTH_PROVIDERS = new Set([
27
28
  "claude-code",
28
29
  "google-gemini-cli",
29
30
  "google-antigravity",
31
+ "cursor-agent",
30
32
  ]);
31
33
  /**
32
34
  * Providers that can serve models normally associated with another provider.
@@ -34,9 +36,9 @@ const CLI_AUTH_PROVIDERS = new Set([
34
36
  * e.g. GitHub Copilot subscriptions can access Claude and GPT models.
35
37
  */
36
38
  const PROVIDER_ROUTES = {
37
- anthropic: ["github-copilot", "claude-code"],
38
- openai: ["github-copilot", "openai-codex"],
39
- google: ["google-antigravity", "google-gemini-cli"],
39
+ anthropic: ["github-copilot", "claude-code", "cursor-agent"],
40
+ openai: ["github-copilot", "openai-codex", "cursor-agent"],
41
+ google: ["google-antigravity", "google-gemini-cli", "cursor-agent"],
40
42
  };
41
43
  // ── Model → Provider ID mapping ───────────────────────────────────────────────
42
44
  /**
@@ -61,6 +63,7 @@ function modelToProviderId(model) {
61
63
  anthropic: "anthropic",
62
64
  openai: "openai",
63
65
  "github-copilot": "github-copilot",
66
+ "cursor-agent": "cursor-agent",
64
67
  };
65
68
  if (prefixMap[prefix])
66
69
  return prefixMap[prefix];
@@ -73,6 +76,8 @@ function modelToProviderId(model) {
73
76
  return "openai";
74
77
  if (lower.startsWith("gemini"))
75
78
  return "google";
79
+ if (lower.startsWith("composer"))
80
+ return "cursor-agent";
76
81
  if (lower.startsWith("llama") || lower.startsWith("mixtral"))
77
82
  return "groq";
78
83
  if (lower.startsWith("grok"))
@@ -129,10 +134,12 @@ const CLI_BINARY_MAP = {
129
134
  "claude-code": ["claude", "claude-code"],
130
135
  "google-gemini-cli": ["gemini"],
131
136
  "google-antigravity": ["agy"],
137
+ "cursor-agent": ["cursor-agent"],
132
138
  };
133
139
  const CLI_AUTH_PATH_CHECK_PROVIDERS = new Set([
134
140
  "google-gemini-cli",
135
141
  "google-antigravity",
142
+ "cursor-agent",
136
143
  ]);
137
144
  let asyncCliBinaryPathCache = null;
138
145
  function cliExecutableNames(providerId) {
@@ -173,6 +180,17 @@ function isCliBinaryInPath(providerId) {
173
180
  const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
174
181
  return pathDirs.some(dir => executableNames.some(name => existsSync(join(dir, name))));
175
182
  }
183
+ function isExternalCliProviderReady(providerId) {
184
+ if (providerId === "cursor-agent")
185
+ return isCursorAgentReadyUncached();
186
+ return isCliBinaryInPath(providerId);
187
+ }
188
+ function cursorAgentFailureDetail() {
189
+ if (!isCliBinaryInPath("cursor-agent")) {
190
+ return "Install Cursor Agent and ensure `cursor-agent` is on PATH";
191
+ }
192
+ return "Run `cursor-agent login` or set CURSOR_API_KEY";
193
+ }
176
194
  async function isCliBinaryInPathAsync(providerId) {
177
195
  const executableNames = cliExecutableNames(providerId);
178
196
  if (executableNames.length === 0)
@@ -293,6 +311,11 @@ function resolveKeyFromAuthOrEnv(providerId) {
293
311
  return null;
294
312
  }
295
313
  function resolveKey(providerId) {
314
+ if (providerId === "cursor-agent") {
315
+ return isExternalCliProviderReady(providerId)
316
+ ? { found: true, source: "env", backedOff: false }
317
+ : { found: false, source: "none", backedOff: false };
318
+ }
296
319
  const direct = resolveKeyFromAuthOrEnv(providerId);
297
320
  if (direct)
298
321
  return direct;
@@ -301,7 +324,7 @@ function resolveKey(providerId) {
301
324
  }
302
325
  // Cross-provider routes can use a local CLI when it is installed. Explicit
303
326
  // external CLI provider selections are handled in checkLlmProviders() below.
304
- if (CLI_AUTH_PROVIDERS.has(providerId) && isCliBinaryInPath(providerId)) {
327
+ if (CLI_AUTH_PROVIDERS.has(providerId) && isExternalCliProviderReady(providerId)) {
305
328
  return { found: true, source: "env", backedOff: false };
306
329
  }
307
330
  return { found: false, source: "none", backedOff: false };
@@ -316,17 +339,22 @@ function checkLlmProviders() {
316
339
  if (CLI_AUTH_PROVIDERS.has(providerId)) {
317
340
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
318
341
  const label = info?.label ?? providerId;
319
- if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !isCliBinaryInPath(providerId)) {
342
+ if (CLI_AUTH_PATH_CHECK_PROVIDERS.has(providerId) && !isExternalCliProviderReady(providerId)) {
320
343
  const binaries = CLI_BINARY_MAP[providerId]?.map(binary => `\`${binary}\``).join(" or ");
344
+ const detail = providerId === "cursor-agent"
345
+ ? cursorAgentFailureDetail()
346
+ : binaries
347
+ ? `Install ${label} and ensure ${binaries} is on PATH`
348
+ : `Install ${label} and ensure its CLI is on PATH`;
321
349
  results.push({
322
350
  name: providerId,
323
351
  label,
324
352
  category: "llm",
325
353
  status: "error",
326
- message: `${label} CLI not found`,
327
- detail: binaries
328
- ? `Install ${label} and ensure ${binaries} is on PATH`
329
- : `Install ${label} and ensure its CLI is on PATH`,
354
+ message: providerId === "cursor-agent" && isCliBinaryInPath(providerId)
355
+ ? `${label} — CLI not authenticated`
356
+ : `${label} CLI not found`,
357
+ detail,
330
358
  required: true,
331
359
  });
332
360
  continue;
@@ -1,5 +1,6 @@
1
- import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { execFileSync } from "node:child_process";
3
4
  import { loadFile, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
4
5
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
5
6
  import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
@@ -12,6 +13,8 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
12
13
  import { collectPreferenceDiagnostics, formatPreferenceDiagnosticDetail } from "./preferences-diagnostics.js";
13
14
  import { isClosedStatus } from "./status-guards.js";
14
15
  import { GLOBAL_STATE_CODES } from "./doctor-types.js";
16
+ import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
17
+ import { createRepositoryRegistryFromPreferences } from "./repository-registry.js";
15
18
  import { checkGitHealth, checkRuntimeHealth, checkGlobalHealth, checkEngineHealth } from "./doctor-checks.js";
16
19
  import { checkEnvironmentHealth } from "./doctor-environment.js";
17
20
  import { runProviderChecks } from "./doctor-providers.js";
@@ -281,6 +284,92 @@ export async function readDoctorHistory(basePath, lastN = 50) {
281
284
  return [];
282
285
  }
283
286
  }
287
+ /**
288
+ * Resolve the git working-tree root for a path, or null if it is not a repo.
289
+ * Used by the workspace-repository probe to check that a declared child path is
290
+ * itself a git repository (not merely nested inside the parent's repo).
291
+ */
292
+ function resolveGitToplevel(cwd) {
293
+ try {
294
+ const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
295
+ cwd,
296
+ stdio: ["ignore", "pipe", "pipe"],
297
+ encoding: "utf-8",
298
+ env: GIT_NO_PROMPT_ENV,
299
+ }).trim();
300
+ return out ? resolve(out) : null;
301
+ }
302
+ catch {
303
+ return null;
304
+ }
305
+ }
306
+ /** realpath that falls back to the resolved path if the link cannot be read. */
307
+ function realpathSafe(p) {
308
+ try {
309
+ return realpathSync(p);
310
+ }
311
+ catch {
312
+ return resolve(p);
313
+ }
314
+ }
315
+ /**
316
+ * Parent-workspace probe (#818): validate that every declared child repository
317
+ * exists on disk and is a git repository at its own root. The registry only
318
+ * checks paths stay inside the project root; it never checks existence or
319
+ * git-ness, so a typo'd path (e.g. `frontned`) would otherwise build cleanly
320
+ * and silently produce "no problems found." No-op for single-repo projects.
321
+ */
322
+ function checkWorkspaceRepositoryHealth(basePath, prefs, issues) {
323
+ if (prefs?.workspace?.mode !== "parent")
324
+ return;
325
+ let registry;
326
+ try {
327
+ registry = createRepositoryRegistryFromPreferences(basePath, prefs);
328
+ }
329
+ catch (err) {
330
+ issues.push({
331
+ severity: "error",
332
+ code: "invalid_preferences",
333
+ scope: "project",
334
+ unitId: "workspace",
335
+ message: `workspace registry failed to build: ${err instanceof Error ? err.message : String(err)}`,
336
+ fixable: false,
337
+ });
338
+ return;
339
+ }
340
+ for (const repo of registry.repositories) {
341
+ if (repo.id === "project")
342
+ continue;
343
+ if (!existsSync(repo.root)) {
344
+ issues.push({
345
+ severity: "error",
346
+ code: "workspace_repo_path_missing",
347
+ scope: "project",
348
+ unitId: `workspace.repositories.${repo.id}`,
349
+ message: `declared repository "${repo.id}" path does not exist on disk: ${repo.root}`,
350
+ fixable: false,
351
+ });
352
+ continue;
353
+ }
354
+ // Must be a repo at its OWN root — a plain dir nested in the parent repo
355
+ // would otherwise pass a bare `nativeIsRepo` check via the enclosing
356
+ // parent's .git. Compare on realpath so symlinked temp roots (macOS
357
+ // /var vs /private/var) don't cause a false mismatch.
358
+ const toplevel = resolveGitToplevel(repo.root);
359
+ const declaredReal = realpathSafe(repo.root);
360
+ const toplevelReal = toplevel ? realpathSafe(toplevel) : null;
361
+ if (toplevelReal !== declaredReal) {
362
+ issues.push({
363
+ severity: "warning",
364
+ code: "workspace_repo_not_a_repo",
365
+ scope: "project",
366
+ unitId: `workspace.repositories.${repo.id}`,
367
+ message: `declared repository "${repo.id}" path is not a git repository at its own root: ${repo.root}`,
368
+ fixable: false,
369
+ });
370
+ }
371
+ }
372
+ }
284
373
  export async function runGSDDoctor(basePath, options) {
285
374
  const issues = [];
286
375
  const fixesApplied = [];
@@ -336,6 +425,7 @@ export async function runGSDDoctor(basePath, options) {
336
425
  (prefs?.preferences?.git?.isolation === "worktree" ? "worktree" :
337
426
  prefs?.preferences?.git?.isolation === "branch" ? "branch" : "none");
338
427
  await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode, dryRun);
428
+ checkWorkspaceRepositoryHealth(basePath, prefs?.preferences, issues);
339
429
  const gitMs = Date.now() - t0git;
340
430
  // Runtime health checks — timed
341
431
  const t0runtime = Date.now();
@@ -352,7 +442,7 @@ export async function runGSDDoctor(basePath, options) {
352
442
  });
353
443
  const envMs = Date.now() - t0env;
354
444
  // Engine health checks — DB constraints and projection drift
355
- await checkEngineHealth(basePath, issues, fixesApplied);
445
+ await checkEngineHealth(basePath, issues, fixesApplied, { repair: fix && !dryRun });
356
446
  const milestonesPath = milestonesDir(basePath);
357
447
  const legacyMilestonesPath2 = legacyMilestonesDir(basePath);
358
448
  if (!existsSync(milestonesPath) && !existsSync(legacyMilestonesPath2)) {
@@ -12,6 +12,7 @@ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
12
  import { randomUUID } from "node:crypto";
13
13
  import { resolve } from "node:path";
14
14
  import { getShellConfig, killProcessTree, SIGKILL_GRACE_MS, HARD_DEADLINE_MS } from "@gsd/pi-coding-agent";
15
+ import { redactSecrets } from "./redact-secrets.js";
15
16
  const ALWAYS_FORWARD_ENV = ["PATH", "HOME"];
16
17
  // SIGKILL_GRACE_MS / HARD_DEADLINE_MS are imported from @gsd/pi-coding-agent
17
18
  // (shell.ts) — the single source of truth for the graceful-kill timing ladder —
@@ -232,8 +233,11 @@ export function runExecSandbox(request, opts) {
232
233
  const stderrBuf = Buffer.concat(stderrChunks);
233
234
  const stdoutSuffix = stdoutTruncated ? "\n[truncated: stdout cap reached]\n" : "";
234
235
  const stderrSuffix = stderrTruncated ? "\n[truncated: stderr cap reached]\n" : "";
235
- writeFileSync(stdoutPath, Buffer.concat([stdoutBuf, Buffer.from(stdoutSuffix, "utf-8")]));
236
- writeFileSync(stderrPath, Buffer.concat([stderrBuf, Buffer.from(stderrSuffix, "utf-8")]));
236
+ // Redact secret-shaped substrings before persisting to .gsd/exec/ (skipped by
237
+ // the secret scanner). The returned digest is derived from the raw buffer
238
+ // below and is redacted downstream when the session is written to activity/.
239
+ writeFileSync(stdoutPath, redactSecrets(Buffer.concat([stdoutBuf, Buffer.from(stdoutSuffix, "utf-8")]).toString("utf-8")));
240
+ writeFileSync(stderrPath, redactSecrets(Buffer.concat([stderrBuf, Buffer.from(stderrSuffix, "utf-8")]).toString("utf-8")));
237
241
  const digestBody = tail(stdoutBuf, opts.digest_chars);
238
242
  const digest = digestBody.length > 0
239
243
  ? digestBody