@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
@@ -1014,6 +1014,143 @@ test("runProviderChecks detects claude.exe in PATH on Windows (#4548)", { skip:
1014
1014
  });
1015
1015
  });
1016
1016
 
1017
+
1018
+ test("runProviderChecks reports error for required cursor-agent when binary is missing", () => {
1019
+ const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-repo-")));
1020
+ mkdirSync(join(repo, ".gsd"), { recursive: true });
1021
+ writeFileSync(
1022
+ join(repo, ".gsd", "PREFERENCES.md"),
1023
+ [
1024
+ "---",
1025
+ "models:",
1026
+ " execution:",
1027
+ " model: composer-2.5",
1028
+ " provider: cursor-agent",
1029
+ "---",
1030
+ "",
1031
+ ].join("\n"),
1032
+ );
1033
+
1034
+ const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-home-")));
1035
+
1036
+ withEnv({
1037
+ HOME: tmpHome,
1038
+ PATH: tmpHome,
1039
+ CURSOR_API_KEY: undefined,
1040
+ }, () => {
1041
+ withCwd(repo, () => {
1042
+ const results = runProviderChecks();
1043
+ const cursor = results.find(r => r.name === "cursor-agent");
1044
+ assert.ok(cursor, "cursor-agent result should exist");
1045
+ assert.equal(cursor!.status, "error", "cursor-agent should error when the CLI binary is missing");
1046
+ assert.ok(cursor!.detail?.includes("Cursor Agent"), "should explain Cursor Agent must be installed");
1047
+ });
1048
+ });
1049
+
1050
+ rmSync(repo, { recursive: true, force: true });
1051
+ rmSync(tmpHome, { recursive: true, force: true });
1052
+ });
1053
+
1054
+ test("runProviderChecks does not route OpenAI via unauthenticated cursor-agent binary in PATH", () => {
1055
+ const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-route-repo-")));
1056
+ mkdirSync(join(repo, ".gsd"), { recursive: true });
1057
+ writeFileSync(
1058
+ join(repo, ".gsd", "PREFERENCES.md"),
1059
+ [
1060
+ "---",
1061
+ "models:",
1062
+ " execution: gpt-5.5",
1063
+ "---",
1064
+ "",
1065
+ ].join("\n"),
1066
+ );
1067
+
1068
+ const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-route-home-")));
1069
+ const binDir = join(tmpHome, "bin");
1070
+ mkdirSync(binDir, { recursive: true });
1071
+ const fakeCursor = join(binDir, "cursor-agent");
1072
+ writeFileSync(fakeCursor, [
1073
+ "#!/bin/sh",
1074
+ "if [ \"$1\" = \"status\" ]; then echo \"Not logged in\"; exit 0; fi",
1075
+ "echo mock",
1076
+ "",
1077
+ ].join("\n"));
1078
+ chmodSync(fakeCursor, 0o755);
1079
+
1080
+ withEnv({
1081
+ HOME: tmpHome,
1082
+ OPENAI_API_KEY: undefined,
1083
+ COPILOT_GITHUB_TOKEN: undefined,
1084
+ GH_TOKEN: undefined,
1085
+ GITHUB_TOKEN: undefined,
1086
+ PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
1087
+ }, () => {
1088
+ try {
1089
+ withCwd(repo, () => {
1090
+ const results = runProviderChecks();
1091
+ const openai = results.find(r => r.name === "openai");
1092
+ assert.ok(openai, "openai result should exist");
1093
+ assert.equal(openai!.status, "error", "binary-only cursor-agent must not satisfy OpenAI routing");
1094
+ assert.ok(openai!.detail?.includes("OPENAI_API_KEY"), "should still tell the user how to configure OpenAI");
1095
+ });
1096
+ } finally {
1097
+ rmSync(repo, { recursive: true, force: true });
1098
+ rmSync(tmpHome, { recursive: true, force: true });
1099
+ }
1100
+ });
1101
+ });
1102
+
1103
+ test("runProviderChecks routes OpenAI via authenticated cursor-agent CLI", () => {
1104
+ const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-auth-route-repo-")));
1105
+ mkdirSync(join(repo, ".gsd"), { recursive: true });
1106
+ writeFileSync(
1107
+ join(repo, ".gsd", "PREFERENCES.md"),
1108
+ [
1109
+ "---",
1110
+ "models:",
1111
+ " execution: gpt-5.5",
1112
+ "---",
1113
+ "",
1114
+ ].join("\n"),
1115
+ );
1116
+
1117
+ const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-cursor-auth-route-home-")));
1118
+ const binDir = join(tmpHome, "bin");
1119
+ mkdirSync(binDir, { recursive: true });
1120
+ const fakeCursor = join(binDir, "cursor-agent");
1121
+ writeFileSync(fakeCursor, [
1122
+ "#!/bin/sh",
1123
+ "if [ \"$1\" = \"--version\" ]; then echo cursor-agent 1.0; exit 0; fi",
1124
+ "if [ \"$1\" = \"status\" ]; then echo \"Authenticated as user@example.com\"; exit 0; fi",
1125
+ "echo mock",
1126
+ "",
1127
+ ].join("\n"));
1128
+ chmodSync(fakeCursor, 0o755);
1129
+
1130
+ withEnv({
1131
+ HOME: tmpHome,
1132
+ OPENAI_API_KEY: undefined,
1133
+ COPILOT_GITHUB_TOKEN: undefined,
1134
+ GH_TOKEN: undefined,
1135
+ GITHUB_TOKEN: undefined,
1136
+ CURSOR_API_KEY: undefined,
1137
+ PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
1138
+ }, () => {
1139
+ try {
1140
+ withCwd(repo, () => {
1141
+ const results = runProviderChecks();
1142
+ const openai = results.find(r => r.name === "openai");
1143
+ assert.ok(openai, "openai result should exist");
1144
+ assert.equal(openai!.status, "ok", "authenticated cursor-agent should satisfy OpenAI routing");
1145
+ assert.ok(openai!.message.toLowerCase().includes("cursor"), "should mention cursor-agent as source");
1146
+ });
1147
+ } finally {
1148
+ rmSync(repo, { recursive: true, force: true });
1149
+ rmSync(tmpHome, { recursive: true, force: true });
1150
+ }
1151
+ });
1152
+ });
1153
+
1017
1154
  test("PROVIDER_ROUTES includes google-antigravity and google-gemini-cli as routes for google (#2922)", async () => {
1018
1155
  const { readFileSync: readFS } = await import("node:fs");
1019
1156
  const { dirname: dirn, join: joinPath } = await import("node:path");
@@ -1022,7 +1159,7 @@ test("PROVIDER_ROUTES includes google-antigravity and google-gemini-cli as route
1022
1159
  const src = readFS(joinPath(__dir, "..", "doctor-providers.ts"), "utf-8");
1023
1160
 
1024
1161
  assert.ok(
1025
- src.includes('google: ["google-antigravity", "google-gemini-cli"]'),
1162
+ src.includes('google: ["google-antigravity", "google-gemini-cli", "cursor-agent"]'),
1026
1163
  'PROVIDER_ROUTES must prefer "google-antigravity" over "google-gemini-cli" for google (#2922)',
1027
1164
  );
1028
1165
  });
@@ -7,6 +7,7 @@ import {
7
7
  insertMilestone,
8
8
  insertSlice,
9
9
  insertTask,
10
+ getTask,
10
11
  openDatabase,
11
12
  } from "../gsd-db.ts";
12
13
  import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -314,7 +315,7 @@ test("checkEngineHealth reports artifact rows whose files are missing on disk",
314
315
  });
315
316
  insertArtifact({
316
317
  path: "milestones/M001/M001-MISSING.md",
317
- artifact_type: "CONTEXT",
318
+ artifact_type: "PLAN",
318
319
  milestone_id: "M001",
319
320
  slice_id: null,
320
321
  task_id: null,
@@ -335,6 +336,398 @@ test("checkEngineHealth reports artifact rows whose files are missing on disk",
335
336
  assert.equal(missing.fixable, false);
336
337
  });
337
338
 
339
+ test("checkEngineHealth resolves escaped .gsd artifact rows against the project .gsd directory", async (t) => {
340
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-artifact-"));
341
+ t.after(() => rmSync(base, { recursive: true, force: true }));
342
+
343
+ const gsdDir = join(base, ".gsd");
344
+ const artifactPath = "phases/01-m001/01-01-ASSESSMENT.md";
345
+ mkdirSync(join(gsdDir, "phases", "01-m001"), { recursive: true });
346
+ writeFileSync(join(gsdDir, artifactPath), "# Assessment\n", "utf-8");
347
+
348
+ openDatabase(join(gsdDir, "gsd.db"));
349
+ insertArtifact({
350
+ path: `../../../Documents/Projects/project/.gsd/${artifactPath}`,
351
+ artifact_type: "ASSESSMENT",
352
+ milestone_id: "M001",
353
+ slice_id: "S01",
354
+ task_id: null,
355
+ full_content: "# Assessment\n",
356
+ });
357
+
358
+ const issues: any[] = [];
359
+ await checkEngineHealth(base, issues, []);
360
+
361
+ assert.equal(
362
+ issues.some((issue) => issue.code === "artifact_file_missing"),
363
+ false,
364
+ "escaped .gsd paths should resolve to the project .gsd artifact",
365
+ );
366
+ });
367
+
368
+ test("checkEngineHealth reports escaped missing artifact rows with .gsd-relative paths", async (t) => {
369
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-missing-artifact-"));
370
+ t.after(() => rmSync(base, { recursive: true, force: true }));
371
+
372
+ const gsdDir = join(base, ".gsd");
373
+ mkdirSync(gsdDir, { recursive: true });
374
+
375
+ openDatabase(join(gsdDir, "gsd.db"));
376
+ insertArtifact({
377
+ path: "../../../Documents/Projects/project/.gsd/phases/01-m001/01-01-PLAN.md",
378
+ artifact_type: "PLAN",
379
+ milestone_id: "M001",
380
+ slice_id: "S01",
381
+ task_id: null,
382
+ full_content: "# Plan\n",
383
+ });
384
+
385
+ const issues: any[] = [];
386
+ await checkEngineHealth(base, issues, []);
387
+
388
+ const issue = issues.find((candidate) => candidate.code === "artifact_file_missing");
389
+ assert.ok(issue, "missing escaped artifact row should still be reported");
390
+ assert.equal(issue.file, "phases/01-m001/01-01-PLAN.md");
391
+ assert.doesNotMatch(issue.message, /\.\.\//);
392
+ });
393
+
394
+ test("checkEngineHealth repair prunes stale phases artifact rows with present milestones files", async (t) => {
395
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-stale-phase-artifact-"));
396
+ t.after(() => rmSync(base, { recursive: true, force: true }));
397
+
398
+ const gsdDir = join(base, ".gsd");
399
+ const stalePath = "phases/01-m001/01-01-PLAN.md";
400
+ const replacementPath = "milestones/M001/slices/S01/S01-PLAN.md";
401
+ mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01"), { recursive: true });
402
+ writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
403
+
404
+ openDatabase(join(gsdDir, "gsd.db"));
405
+ insertArtifact({
406
+ path: stalePath,
407
+ artifact_type: "PLAN",
408
+ milestone_id: "M001",
409
+ slice_id: "S01",
410
+ task_id: null,
411
+ full_content: "# stale plan\n",
412
+ });
413
+
414
+ const issues: any[] = [];
415
+ const fixes: string[] = [];
416
+ await checkEngineHealth(base, issues, fixes, { repair: true });
417
+
418
+ assert.equal(
419
+ issues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
420
+ false,
421
+ "repair should not report stale rows it pruned",
422
+ );
423
+ assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
424
+
425
+ const rows = _getAdapter()!
426
+ .prepare("SELECT path FROM artifacts ORDER BY path")
427
+ .all() as Array<{ path: string }>;
428
+ assert.deepEqual(rows.map((row) => row.path), []);
429
+ });
430
+
431
+ test("checkEngineHealth repair prunes stale phases task rows against tasks/<T>-<TYPE>.md replacements", async (t) => {
432
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-stale-task-phase-artifact-"));
433
+ t.after(() => rmSync(base, { recursive: true, force: true }));
434
+
435
+ const gsdDir = join(base, ".gsd");
436
+ const stalePath = "phases/01-m001/01-01-T01-SUMMARY.md";
437
+ // Canonical legacy task layout has no per-task subdirectory: the SUMMARY lives
438
+ // at tasks/<T>-<TYPE>.md, not tasks/<T>/<T>-<TYPE>.md. If the expected-path
439
+ // builder adds an extra tasks/<T>/ segment, task-scoped stale rows never match
440
+ // their on-disk replacement and leak unpruned.
441
+ const replacementPath = "milestones/M001/slices/S01/tasks/T01-SUMMARY.md";
442
+ mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
443
+ writeFileSync(join(gsdDir, replacementPath), "# Summary\n", "utf-8");
444
+
445
+ openDatabase(join(gsdDir, "gsd.db"));
446
+ insertArtifact({
447
+ path: stalePath,
448
+ artifact_type: "SUMMARY",
449
+ milestone_id: "M001",
450
+ slice_id: "S01",
451
+ task_id: "T01",
452
+ full_content: "# stale summary\n",
453
+ });
454
+
455
+ const issues: any[] = [];
456
+ const fixes: string[] = [];
457
+ await checkEngineHealth(base, issues, fixes, { repair: true });
458
+
459
+ assert.equal(
460
+ issues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
461
+ false,
462
+ "task-scoped stale rows should prune against the tasks/<T>-<TYPE>.md replacement",
463
+ );
464
+ assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
465
+
466
+ const rows = _getAdapter()!
467
+ .prepare("SELECT path FROM artifacts ORDER BY path")
468
+ .all() as Array<{ path: string }>;
469
+ assert.deepEqual(rows.map((row) => row.path), []);
470
+ });
471
+
472
+ test("checkEngineHealth repair prunes stale phases rows stored as escaped ../ paths", async (t) => {
473
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-escaped-phase-artifact-"));
474
+ t.after(() => rmSync(base, { recursive: true, force: true }));
475
+
476
+ const gsdDir = join(base, ".gsd");
477
+ // Leaked row: the DB stores an escaped, absolute-ish path rather than a clean
478
+ // `phases/…` value. The prune must gate on the .gsd-relative path so the row
479
+ // this repair targets is not skipped just because the raw value starts with `../`.
480
+ const stalePath = "../../../Documents/Projects/project/.gsd/phases/01-m001/01-01-PLAN.md";
481
+ const replacementPath = "milestones/M001/slices/S01/S01-PLAN.md";
482
+ mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01"), { recursive: true });
483
+ writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
484
+
485
+ openDatabase(join(gsdDir, "gsd.db"));
486
+ insertArtifact({
487
+ path: stalePath,
488
+ artifact_type: "PLAN",
489
+ milestone_id: "M001",
490
+ slice_id: "S01",
491
+ task_id: null,
492
+ full_content: "# stale plan\n",
493
+ });
494
+
495
+ const issues: any[] = [];
496
+ const fixes: string[] = [];
497
+ await checkEngineHealth(base, issues, fixes, { repair: true });
498
+
499
+ assert.equal(
500
+ issues.some((issue) => issue.code === "artifact_file_missing"),
501
+ false,
502
+ "escaped phases rows should prune against the milestones replacement, not be reported missing",
503
+ );
504
+ assert.ok(
505
+ fixes.some((fix) => fix.startsWith("pruned stale flat-phase artifact row") && fix.includes("phases/01-m001/01-01-PLAN.md")),
506
+ "repair should record a prune for the escaped stale phases row",
507
+ );
508
+
509
+ const rows = _getAdapter()!
510
+ .prepare("SELECT path FROM artifacts ORDER BY path")
511
+ .all() as Array<{ path: string }>;
512
+ assert.deepEqual(rows.map((row) => row.path), [], "escaped stale phases row should be deleted from the DB");
513
+ });
514
+
515
+ test("checkEngineHealth marks escaped phases rows fixable when a milestones replacement exists", async (t) => {
516
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-escaped-phase-fixable-"));
517
+ t.after(() => rmSync(base, { recursive: true, force: true }));
518
+
519
+ const gsdDir = join(base, ".gsd");
520
+ const replacementPath = "milestones/M001/slices/S01/S01-PLAN.md";
521
+ mkdirSync(join(gsdDir, "milestones", "M001", "slices", "S01"), { recursive: true });
522
+ writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
523
+
524
+ openDatabase(join(gsdDir, "gsd.db"));
525
+ insertArtifact({
526
+ path: "../../../Documents/Projects/project/.gsd/phases/01-m001/01-01-PLAN.md",
527
+ artifact_type: "PLAN",
528
+ milestone_id: "M001",
529
+ slice_id: "S01",
530
+ task_id: null,
531
+ full_content: "# stale plan\n",
532
+ });
533
+
534
+ const issues: any[] = [];
535
+ await checkEngineHealth(base, issues, []);
536
+
537
+ const issue = issues.find((candidate) => candidate.code === "artifact_file_missing");
538
+ assert.ok(issue, "escaped stale row should still be reported when repair is off");
539
+ assert.equal(issue.file, "phases/01-m001/01-01-PLAN.md");
540
+ assert.equal(issue.fixable, true, "escaped phases rows with a milestones replacement must be marked fixable");
541
+ });
542
+
543
+ function taskSummary(id: string, verificationResult = "passed"): string {
544
+ return [
545
+ "---",
546
+ `id: ${id}`,
547
+ "parent: S01",
548
+ "milestone: M001",
549
+ "key_files:",
550
+ " - src/example.ts",
551
+ "key_decisions:",
552
+ " - Keep recovery conservative",
553
+ "duration: 5m",
554
+ `verification_result: ${verificationResult}`,
555
+ "completed_at: 2026-01-01T00:00:00.000Z",
556
+ "blocker_discovered: false",
557
+ "---",
558
+ "",
559
+ `# ${id}: Done`,
560
+ "",
561
+ "**Recovered task completion.**",
562
+ "",
563
+ "## What Happened",
564
+ "",
565
+ "The task finished and wrote its summary.",
566
+ "",
567
+ "## Deviations",
568
+ "",
569
+ "None.",
570
+ "",
571
+ "## Known Issues",
572
+ "",
573
+ "None.",
574
+ "",
575
+ ].join("\n");
576
+ }
577
+
578
+ test("checkEngineHealth marks valid task artifact DB divergence fixable and repairs it", async (t) => {
579
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-artifact-db-repair-"));
580
+ t.after(() => rmSync(base, { recursive: true, force: true }));
581
+
582
+ const gsdDir = join(base, ".gsd");
583
+ const tasksDir = join(gsdDir, "milestones", "M001", "slices", "S01", "tasks");
584
+ mkdirSync(tasksDir, { recursive: true });
585
+
586
+ openDatabase(join(gsdDir, "gsd.db"));
587
+ insertMilestone({ id: "M001", title: "Foundation", status: "active" });
588
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "low", depends: [], sequence: 1 });
589
+ insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "Task", status: "pending", sequence: 1 });
590
+
591
+ const relSummaryPath = "milestones/M001/slices/S01/tasks/T01-SUMMARY.md";
592
+ const summary = taskSummary("T01");
593
+ writeFileSync(join(gsdDir, relSummaryPath), summary, "utf-8");
594
+ insertArtifact({
595
+ path: relSummaryPath,
596
+ artifact_type: "SUMMARY",
597
+ milestone_id: "M001",
598
+ slice_id: "S01",
599
+ task_id: "T01",
600
+ full_content: summary,
601
+ });
602
+
603
+ const detectIssues: any[] = [];
604
+ await checkEngineHealth(base, detectIssues, []);
605
+
606
+ const divergence = detectIssues.find((issue) => issue.code === "artifact_db_status_divergence" && issue.unitId === "M001/S01/T01");
607
+ assert.ok(divergence, "doctor should report the artifact/DB divergence");
608
+ assert.equal(divergence.fixable, true);
609
+
610
+ const repairIssues: any[] = [];
611
+ const fixes: string[] = [];
612
+ await checkEngineHealth(base, repairIssues, fixes, { repair: true });
613
+
614
+ assert.ok(
615
+ fixes.includes("repaired task completion from SUMMARY artifact for M001/S01/T01"),
616
+ "repair mode should report the task completion repair",
617
+ );
618
+ assert.equal(
619
+ repairIssues.some((issue) => issue.code === "artifact_db_status_divergence" && issue.unitId === "M001/S01/T01"),
620
+ false,
621
+ "repaired divergence should not be reported in the same doctor run",
622
+ );
623
+
624
+ const task = getTask("M001", "S01", "T01");
625
+ assert.equal(task?.status, "complete");
626
+ assert.equal(task?.completed_at, "2026-01-01T00:00:00.000Z");
627
+ assert.equal(task?.verification_result, "passed");
628
+ assert.match(task?.full_summary_md ?? "", /# T01: Done/);
629
+ });
630
+
631
+ test("checkEngineHealth keeps failed and negated-pass summaries non-fixable", async (t) => {
632
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-artifact-db-failure-"));
633
+ t.after(() => rmSync(base, { recursive: true, force: true }));
634
+
635
+ const gsdDir = join(base, ".gsd");
636
+ const tasksDir = join(gsdDir, "milestones", "M001", "slices", "S01", "tasks");
637
+ mkdirSync(tasksDir, { recursive: true });
638
+
639
+ openDatabase(join(gsdDir, "gsd.db"));
640
+ insertMilestone({ id: "M001", title: "Foundation", status: "active" });
641
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active", risk: "low", depends: [], sequence: 1 });
642
+ const nonPassingResults = [
643
+ ["T02", "failed"],
644
+ ["T03", "not passed"],
645
+ ["T04", "not passing"],
646
+ ] as const;
647
+ for (const [taskId, verificationResult] of nonPassingResults) {
648
+ insertTask({ id: taskId, milestoneId: "M001", sliceId: "S01", title: "Task", status: "pending", sequence: 1 });
649
+
650
+ const relSummaryPath = `milestones/M001/slices/S01/tasks/${taskId}-SUMMARY.md`;
651
+ const summary = taskSummary(taskId, verificationResult);
652
+ writeFileSync(join(gsdDir, relSummaryPath), summary, "utf-8");
653
+ insertArtifact({
654
+ path: relSummaryPath,
655
+ artifact_type: "SUMMARY",
656
+ milestone_id: "M001",
657
+ slice_id: "S01",
658
+ task_id: taskId,
659
+ full_content: summary,
660
+ });
661
+ }
662
+
663
+ const issues: any[] = [];
664
+ await checkEngineHealth(base, issues, []);
665
+
666
+ for (const [taskId] of nonPassingResults) {
667
+ const divergence = issues.find((issue) => issue.code === "artifact_db_status_divergence" && issue.unitId === `M001/S01/${taskId}`);
668
+ assert.ok(divergence, "doctor should still report non-passing summary drift");
669
+ assert.equal(divergence.fixable, false);
670
+ }
671
+ });
672
+
673
+ test("checkEngineHealth reports missing CONTEXT and RESEARCH artifacts as user-content warnings", async (t) => {
674
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-user-content-artifact-"));
675
+ t.after(() => rmSync(base, { recursive: true, force: true }));
676
+
677
+ const gsdDir = join(base, ".gsd");
678
+ mkdirSync(gsdDir, { recursive: true });
679
+
680
+ openDatabase(join(gsdDir, "gsd.db"));
681
+ insertArtifact({
682
+ path: "milestones/M002/M002-CONTEXT.md",
683
+ artifact_type: "CONTEXT",
684
+ milestone_id: "M002",
685
+ slice_id: null,
686
+ task_id: null,
687
+ full_content: "# Context\n",
688
+ });
689
+ insertArtifact({
690
+ path: "milestones/M002/M002-RESEARCH.md",
691
+ artifact_type: "RESEARCH",
692
+ milestone_id: "M002",
693
+ slice_id: null,
694
+ task_id: null,
695
+ full_content: "# Research\n",
696
+ });
697
+
698
+ const issues: any[] = [];
699
+ const fixes: string[] = [];
700
+ await checkEngineHealth(base, issues, fixes, { repair: true });
701
+
702
+ assert.equal(
703
+ issues.some((issue) => issue.code === "artifact_file_missing"),
704
+ false,
705
+ "missing user-authored artifacts should not be reported as blocking projection errors",
706
+ );
707
+
708
+ const contextIssue = issues.find((issue) => issue.code === "artifact_user_content_missing" && issue.file === "milestones/M002/M002-CONTEXT.md");
709
+ assert.ok(contextIssue, "missing CONTEXT should use the user-content code");
710
+ assert.equal(contextIssue.severity, "warning");
711
+ assert.equal(contextIssue.unitId, "M002");
712
+ assert.equal(
713
+ contextIssue.message,
714
+ "Artifact `milestones/M002/M002-CONTEXT.md` is a user-authored CONTEXT file recorded in the database but missing from disk. Re-run `/gsd discuss` in this milestone to regenerate it.",
715
+ );
716
+
717
+ const researchIssue = issues.find((issue) => issue.code === "artifact_user_content_missing" && issue.file === "milestones/M002/M002-RESEARCH.md");
718
+ assert.ok(researchIssue, "missing RESEARCH should use the user-content code");
719
+ assert.equal(researchIssue.severity, "warning");
720
+
721
+ assert.ok(
722
+ fixes.some((fix) => fix === "skipped user-authored CONTEXT artifact milestones/M002/M002-CONTEXT.md (content cannot be regenerated from the database)"),
723
+ "repair output should explain skipped CONTEXT content",
724
+ );
725
+ assert.ok(
726
+ fixes.some((fix) => fix === "skipped user-authored RESEARCH artifact milestones/M002/M002-RESEARCH.md (content cannot be regenerated from the database)"),
727
+ "repair output should explain skipped RESEARCH content",
728
+ );
729
+ });
730
+
338
731
  test("checkEngineHealth clears artifact_file_missing after projection re-render recreates the file", async (t) => {
339
732
  const base = mkdtempSync(join(tmpdir(), "gsd-doctor-stale-missing-artifact-"));
340
733
  t.after(() => rmSync(base, { recursive: true, force: true }));
@@ -381,8 +774,7 @@ test("checkEngineHealth clears artifact_file_missing after projection re-render
381
774
  false,
382
775
  "doctor should not report a missing artifact that projection repair recreated in the same run",
383
776
  );
384
- assert.ok(
385
- issues.some((issue) => issue.code === "artifact_file_missing" && issue.file === "phases/01-foundation/01-CONTEXT.md"),
386
- "doctor should still report a missing artifact that projection repair did not recreate",
387
- );
777
+ const contextIssue = issues.find((issue) => issue.code === "artifact_user_content_missing" && issue.file === "phases/01-foundation/01-CONTEXT.md");
778
+ assert.ok(contextIssue, "doctor should still report missing user content that projection repair did not recreate");
779
+ assert.equal(contextIssue.severity, "warning");
388
780
  });