gsd-pi 2.76.0-dev.fe143342a → 2.77.0-dev.1d17f366c

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 (597) hide show
  1. package/README.md +17 -35
  2. package/dist/claude-cli-check.js +32 -3
  3. package/dist/cli-web-branch.d.ts +1 -0
  4. package/dist/cli-web-branch.js +3 -0
  5. package/dist/cli.js +38 -2
  6. package/dist/extension-discovery.d.ts +6 -0
  7. package/dist/extension-discovery.js +37 -0
  8. package/dist/extension-registry.d.ts +3 -0
  9. package/dist/extension-sort.d.ts +18 -0
  10. package/dist/extension-sort.js +114 -0
  11. package/dist/extension-validator.d.ts +47 -0
  12. package/dist/extension-validator.js +127 -0
  13. package/dist/loader.js +35 -7
  14. package/dist/onboarding.js +45 -0
  15. package/dist/provider-migrations.d.ts +18 -0
  16. package/dist/provider-migrations.js +14 -0
  17. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  18. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +78 -59
  19. package/dist/resources/extensions/cmux/index.js +20 -0
  20. package/dist/resources/extensions/github-sync/templates.js +103 -0
  21. package/dist/resources/extensions/google-search/extension-manifest.json +5 -4
  22. package/dist/resources/extensions/google-search/index.js +3 -375
  23. package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
  24. package/dist/resources/extensions/gsd/auto/loop.js +90 -2
  25. package/dist/resources/extensions/gsd/auto/phases.js +95 -21
  26. package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
  27. package/dist/resources/extensions/gsd/auto/run-unit.js +48 -4
  28. package/dist/resources/extensions/gsd/auto/session.js +18 -1
  29. package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
  30. package/dist/resources/extensions/gsd/auto-dispatch.js +115 -17
  31. package/dist/resources/extensions/gsd/auto-loop.js +1 -1
  32. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  33. package/dist/resources/extensions/gsd/auto-post-unit.js +90 -2
  34. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  35. package/dist/resources/extensions/gsd/auto-recovery.js +46 -1
  36. package/dist/resources/extensions/gsd/auto-start.js +45 -39
  37. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
  38. package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
  39. package/dist/resources/extensions/gsd/auto-worktree.js +109 -61
  40. package/dist/resources/extensions/gsd/auto.js +97 -31
  41. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +27 -1
  42. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +4 -2
  43. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +11 -0
  44. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -6
  45. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -6
  46. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  47. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  48. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
  49. package/dist/resources/extensions/gsd/commands-cmux.js +9 -6
  50. package/dist/resources/extensions/gsd/commands-extensions.js +634 -43
  51. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  52. package/dist/resources/extensions/gsd/dispatch-guard.js +29 -3
  53. package/dist/resources/extensions/gsd/file-lock.js +49 -9
  54. package/dist/resources/extensions/gsd/git-service.js +1 -0
  55. package/dist/resources/extensions/gsd/gitignore.js +2 -0
  56. package/dist/resources/extensions/gsd/gsd-db.js +90 -30
  57. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
  58. package/dist/resources/extensions/gsd/guided-flow.js +212 -9
  59. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  60. package/dist/resources/extensions/gsd/journal.js +17 -2
  61. package/dist/resources/extensions/gsd/key-manager.js +22 -0
  62. package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
  63. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  64. package/dist/resources/extensions/gsd/model-router.js +36 -3
  65. package/dist/resources/extensions/gsd/notifications.js +30 -16
  66. package/dist/resources/extensions/gsd/pre-execution-checks.js +31 -6
  67. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +29 -2
  68. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  69. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  70. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  71. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  72. package/dist/resources/extensions/gsd/reports.js +5 -4
  73. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  74. package/dist/resources/extensions/gsd/safety/file-change-validator.js +12 -4
  75. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  76. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  77. package/dist/resources/extensions/gsd/state.js +25 -25
  78. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  79. package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
  80. package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
  81. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
  82. package/dist/resources/extensions/gsd/uok/audit.js +18 -2
  83. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  84. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  85. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  86. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  87. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  88. package/dist/resources/extensions/gsd/uok/plan-v2.js +30 -7
  89. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  90. package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
  91. package/dist/resources/extensions/gsd/worktree-manager.js +1 -0
  92. package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
  93. package/dist/resources/extensions/mcp-client/auth.js +10 -1
  94. package/dist/resources/extensions/mcp-client/index.js +118 -9
  95. package/dist/resources/extensions/shared/cmux-events.js +12 -0
  96. package/dist/resources/extensions/shared/rtk-session-stats.js +1 -2
  97. package/dist/resources/skills/create-skill/SKILL.md +2 -2
  98. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  99. package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  100. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  101. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  102. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  103. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  104. package/dist/web/standalone/.next/BUILD_ID +1 -1
  105. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  106. package/dist/web/standalone/.next/build-manifest.json +3 -3
  107. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  108. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  173. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  175. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  177. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  179. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  185. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  187. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  189. package/dist/web/standalone/.next/server/app/index.html +1 -1
  190. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  191. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  192. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  193. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  194. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  195. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  196. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  197. package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
  198. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  199. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  200. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  201. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  203. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  204. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  205. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
  206. package/dist/web/standalone/.next/static/chunks/{webpack-5fc74f13a25fa1bb.js → webpack-2e68521d7c82f7c2.js} +1 -1
  207. package/dist/welcome-screen.js +6 -1
  208. package/dist/wizard.js +2 -0
  209. package/package.json +16 -14
  210. package/packages/daemon/package.json +2 -2
  211. package/packages/mcp-server/README.md +3 -3
  212. package/packages/mcp-server/dist/env-writer.d.ts +1 -0
  213. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
  214. package/packages/mcp-server/dist/env-writer.js +74 -6
  215. package/packages/mcp-server/dist/env-writer.js.map +1 -1
  216. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  217. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  218. package/packages/mcp-server/dist/remote-questions.js +732 -0
  219. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  220. package/packages/mcp-server/dist/server.d.ts +7 -0
  221. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  222. package/packages/mcp-server/dist/server.js +95 -10
  223. package/packages/mcp-server/dist/server.js.map +1 -1
  224. package/packages/mcp-server/dist/session-manager.d.ts +14 -0
  225. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  226. package/packages/mcp-server/dist/session-manager.js +49 -1
  227. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  228. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  229. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  230. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  231. package/packages/mcp-server/package.json +9 -3
  232. package/packages/mcp-server/src/env-writer.test.ts +79 -1
  233. package/packages/mcp-server/src/env-writer.ts +76 -6
  234. package/packages/mcp-server/src/mcp-server.test.ts +67 -0
  235. package/packages/mcp-server/src/readers/readers.test.ts +5 -1
  236. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  237. package/packages/mcp-server/src/remote-questions.ts +916 -0
  238. package/packages/mcp-server/src/server.ts +118 -16
  239. package/packages/mcp-server/src/session-manager.ts +43 -1
  240. package/packages/mcp-server/src/workflow-tools.test.ts +44 -0
  241. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  242. package/packages/mcp-server/tsconfig.test.json +19 -0
  243. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  244. package/packages/native/package.json +6 -1
  245. package/packages/native/src/__tests__/clipboard.test.mjs +69 -23
  246. package/packages/native/tsconfig.tsbuildinfo +1 -1
  247. package/packages/pi-agent-core/package.json +6 -1
  248. package/packages/pi-agent-core/src/agent-loop.test.ts +220 -15
  249. package/packages/pi-ai/dist/models/custom.d.ts +38 -0
  250. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/models/custom.js +41 -0
  252. package/packages/pi-ai/dist/models/custom.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  254. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  255. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  256. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  257. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  258. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  259. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  260. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  261. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  262. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  263. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  264. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  265. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  266. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  267. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  268. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  269. package/packages/pi-ai/package.json +6 -1
  270. package/packages/pi-ai/src/models/custom.ts +42 -0
  271. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  272. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  273. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  274. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  275. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  276. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  277. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
  278. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
  280. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/agent-session.js +7 -0
  282. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +25 -0
  284. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +105 -6
  286. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js +230 -28
  288. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +30 -2
  290. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  291. package/packages/pi-coding-agent/dist/core/compaction/utils.js +113 -12
  292. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
  294. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  295. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +29 -18
  296. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
  298. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
  299. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
  300. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
  301. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +56 -1
  302. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -1
  303. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +8 -15
  304. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  305. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts +25 -0
  306. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts.map +1 -0
  307. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js +109 -0
  308. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js.map +1 -0
  309. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts +67 -0
  310. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts.map +1 -0
  311. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js +167 -0
  312. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js.map +1 -0
  313. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +3 -2
  314. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  315. package/packages/pi-coding-agent/dist/core/extensions/loader.js +24 -8
  316. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  317. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  318. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  319. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  320. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +14 -0
  321. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  322. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  323. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +11 -0
  324. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  325. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +2 -2
  326. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  327. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  328. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  329. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  330. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  331. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  332. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  333. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  334. package/packages/pi-coding-agent/dist/core/resource-loader.js +1 -1
  335. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
  337. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  338. package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
  339. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  340. package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
  341. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  342. package/packages/pi-coding-agent/dist/core/session-manager.js +1 -1
  343. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  344. package/packages/pi-coding-agent/dist/core/session-manager.test.js +21 -1
  345. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  346. package/packages/pi-coding-agent/dist/core/system-prompt.js +3 -3
  347. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +2 -1
  349. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
  351. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
  352. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  353. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
  354. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  355. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -1
  356. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  357. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +31 -9
  358. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  359. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -0
  360. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  361. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  362. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  363. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  364. package/packages/pi-coding-agent/package.json +6 -1
  365. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
  366. package/packages/pi-coding-agent/src/core/agent-session.ts +11 -0
  367. package/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +368 -28
  368. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +122 -6
  369. package/packages/pi-coding-agent/src/core/compaction/utils.ts +111 -13
  370. package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
  371. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +32 -18
  372. package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +68 -1
  373. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +9 -18
  374. package/packages/pi-coding-agent/src/core/extensions/extension-discovery.ts +119 -0
  375. package/packages/pi-coding-agent/src/core/extensions/extension-registry.ts +222 -0
  376. package/packages/pi-coding-agent/src/core/extensions/loader.ts +24 -11
  377. package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -0
  378. package/packages/pi-coding-agent/src/core/extensions/types.ts +15 -0
  379. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +13 -0
  380. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +2 -2
  381. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  382. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  383. package/packages/pi-coding-agent/src/core/resource-loader.ts +1 -1
  384. package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
  385. package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
  386. package/packages/pi-coding-agent/src/core/session-manager.test.ts +30 -1
  387. package/packages/pi-coding-agent/src/core/session-manager.ts +1 -1
  388. package/packages/pi-coding-agent/src/core/system-prompt.ts +3 -3
  389. package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +2 -1
  390. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
  391. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
  392. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
  393. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +14 -0
  394. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  395. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  396. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +12 -5
  397. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  398. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +21 -0
  399. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  400. package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
  401. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  402. package/packages/pi-tui/dist/stdin-buffer.js +20 -0
  403. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  404. package/packages/pi-tui/package.json +6 -1
  405. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -5
  406. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +27 -0
  407. package/packages/pi-tui/src/stdin-buffer.ts +26 -0
  408. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  409. package/packages/rpc-client/package.json +6 -1
  410. package/pkg/package.json +1 -1
  411. package/scripts/install.js +512 -0
  412. package/scripts/lib/workspace-manifest.cjs +86 -0
  413. package/scripts/link-workspace-packages.cjs +5 -17
  414. package/scripts/postinstall.js +9 -178
  415. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  416. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +91 -63
  417. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +114 -12
  418. package/src/resources/extensions/cmux/index.ts +35 -10
  419. package/src/resources/extensions/github-sync/templates.ts +151 -0
  420. package/src/resources/extensions/github-sync/tests/templates.test.ts +59 -0
  421. package/src/resources/extensions/google-search/extension-manifest.json +5 -4
  422. package/src/resources/extensions/google-search/index.ts +9 -470
  423. package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
  424. package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -1
  425. package/src/resources/extensions/gsd/auto/loop.ts +104 -2
  426. package/src/resources/extensions/gsd/auto/phases.ts +123 -21
  427. package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
  428. package/src/resources/extensions/gsd/auto/run-unit.ts +56 -4
  429. package/src/resources/extensions/gsd/auto/session.ts +28 -1
  430. package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
  431. package/src/resources/extensions/gsd/auto/types.ts +1 -1
  432. package/src/resources/extensions/gsd/auto-dispatch.ts +117 -16
  433. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  434. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  435. package/src/resources/extensions/gsd/auto-post-unit.ts +92 -3
  436. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  437. package/src/resources/extensions/gsd/auto-recovery.ts +40 -1
  438. package/src/resources/extensions/gsd/auto-start.ts +48 -52
  439. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
  440. package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
  441. package/src/resources/extensions/gsd/auto-worktree.ts +122 -68
  442. package/src/resources/extensions/gsd/auto.ts +105 -35
  443. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
  444. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -2
  445. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +11 -0
  446. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
  447. package/src/resources/extensions/gsd/bootstrap/system-context.ts +13 -9
  448. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  449. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  450. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
  451. package/src/resources/extensions/gsd/commands-cmux.ts +10 -6
  452. package/src/resources/extensions/gsd/commands-extensions.ts +747 -41
  453. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  454. package/src/resources/extensions/gsd/dispatch-guard.ts +26 -2
  455. package/src/resources/extensions/gsd/file-lock.ts +84 -11
  456. package/src/resources/extensions/gsd/git-service.ts +1 -0
  457. package/src/resources/extensions/gsd/gitignore.ts +2 -1
  458. package/src/resources/extensions/gsd/gsd-db.ts +92 -32
  459. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
  460. package/src/resources/extensions/gsd/guided-flow.ts +259 -10
  461. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  462. package/src/resources/extensions/gsd/journal.ts +29 -3
  463. package/src/resources/extensions/gsd/key-manager.ts +22 -0
  464. package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
  465. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  466. package/src/resources/extensions/gsd/model-router.ts +42 -1
  467. package/src/resources/extensions/gsd/notifications.ts +27 -15
  468. package/src/resources/extensions/gsd/pre-execution-checks.ts +33 -7
  469. package/src/resources/extensions/gsd/preferences-types.ts +8 -0
  470. package/src/resources/extensions/gsd/prompts/discuss-headless.md +29 -2
  471. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  472. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  473. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  474. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  475. package/src/resources/extensions/gsd/reports.ts +5 -4
  476. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  477. package/src/resources/extensions/gsd/safety/file-change-validator.ts +16 -3
  478. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  479. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  480. package/src/resources/extensions/gsd/state.ts +35 -30
  481. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +238 -4
  482. package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
  483. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  484. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +122 -0
  485. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
  486. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +63 -0
  487. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
  488. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  489. package/src/resources/extensions/gsd/tests/cmux.test.ts +5 -9
  490. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +15 -0
  491. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
  492. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  493. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  494. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +161 -0
  495. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  496. package/src/resources/extensions/gsd/tests/derive-state.test.ts +1 -2
  497. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +14 -9
  498. package/src/resources/extensions/gsd/tests/dispatch-guard-summary-db-mismatch.test.ts +77 -0
  499. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  500. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  501. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  502. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  503. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  504. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  505. package/src/resources/extensions/gsd/tests/exec-history.test.ts +113 -0
  506. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  507. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +38 -0
  508. package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
  509. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +131 -0
  510. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +296 -1
  511. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  512. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
  513. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +4 -2
  514. package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
  515. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
  516. package/src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts +11 -0
  517. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  518. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +66 -0
  519. package/src/resources/extensions/gsd/tests/key-manager.test.ts +2 -0
  520. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +76 -0
  521. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  522. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
  523. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  524. package/src/resources/extensions/gsd/tests/milestone-status-authoritative.test.ts +3 -3
  525. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  526. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +4 -2
  527. package/src/resources/extensions/gsd/tests/parallel-commit-scope.test.ts +5 -0
  528. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  529. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +150 -0
  530. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
  531. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  532. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +337 -0
  533. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  534. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +39 -25
  535. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +181 -0
  536. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
  537. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  538. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
  539. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  540. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
  541. package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
  542. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  543. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
  544. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  545. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  546. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  547. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +24 -0
  548. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  549. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
  550. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  551. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  552. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
  553. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
  554. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  555. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  556. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  557. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  558. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +42 -2
  559. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  560. package/src/resources/extensions/gsd/tests/validate-extension-package.test.ts +168 -0
  561. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +138 -5
  562. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +25 -2
  563. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  564. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
  565. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
  566. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
  567. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  568. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  569. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  570. package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
  571. package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
  572. package/src/resources/extensions/gsd/uok/audit.ts +20 -2
  573. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  574. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  575. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  576. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  577. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  578. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  579. package/src/resources/extensions/gsd/uok/plan-v2.ts +39 -8
  580. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  581. package/src/resources/extensions/gsd/workflow-logger.ts +23 -3
  582. package/src/resources/extensions/gsd/worktree-manager.ts +1 -0
  583. package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
  584. package/src/resources/extensions/mcp-client/auth.ts +12 -1
  585. package/src/resources/extensions/mcp-client/index.ts +129 -10
  586. package/src/resources/extensions/shared/cmux-events.ts +59 -0
  587. package/src/resources/extensions/shared/rtk-session-stats.ts +1 -2
  588. package/src/resources/skills/create-skill/SKILL.md +2 -2
  589. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  590. package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  591. package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  592. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  593. package/src/resources/skills/write-docs/SKILL.md +2 -1
  594. package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
  595. package/dist/web/standalone/.next/static/chunks/2826.e59e8578e2e28639.js +0 -9
  596. /package/dist/web/standalone/.next/static/{n21VtX2hZlkpdEUO_nU4z → vidAVJkURvTJ0_V2-64ro}/_buildManifest.js +0 -0
  597. /package/dist/web/standalone/.next/static/{n21VtX2hZlkpdEUO_nU4z → vidAVJkURvTJ0_V2-64ro}/_ssgManifest.js +0 -0
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@gsd-build/mcp-server",
3
- "version": "2.76.0",
3
+ "version": "2.77.0",
4
4
  "description": "MCP server exposing GSD orchestration tools for Claude Code, Cursor, and other MCP clients",
5
5
  "license": "MIT",
6
+ "gsd": {
7
+ "linkable": true,
8
+ "scope": "@gsd-build",
9
+ "name": "mcp-server"
10
+ },
6
11
  "repository": {
7
12
  "type": "git",
8
13
  "url": "https://github.com/gsd-build/gsd-2.git",
@@ -25,11 +30,12 @@
25
30
  },
26
31
  "scripts": {
27
32
  "build": "tsc",
28
- "test": "node --test dist/mcp-server.test.js"
33
+ "build:test": "tsc -p tsconfig.test.json",
34
+ "test": "npm run build:test && node --test dist/mcp-server.test.js dist/remote-questions.test.js"
29
35
  },
30
36
  "dependencies": {
31
37
  "@modelcontextprotocol/sdk": "^1.27.1",
32
- "@gsd-build/rpc-client": "^2.76.0",
38
+ "@gsd-build/rpc-client": "^2.77.0",
33
39
  "zod": "^4.0.0"
34
40
  },
35
41
  "devDependencies": {
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { describe, it, afterEach } from 'node:test';
5
5
  import assert from 'node:assert/strict';
6
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
6
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync, realpathSync, symlinkSync } from 'node:fs';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join } from 'node:path';
9
9
 
@@ -14,6 +14,7 @@ import {
14
14
  applySecrets,
15
15
  isSafeEnvVarKey,
16
16
  isSupportedDeploymentEnvironment,
17
+ resolveProjectEnvFilePath,
17
18
  shellEscapeSingle,
18
19
  } from './env-writer.js';
19
20
 
@@ -181,6 +182,83 @@ describe('writeEnvKey', () => {
181
182
  rmSync(tmp, { recursive: true, force: true });
182
183
  }
183
184
  });
185
+
186
+ it('does not follow symlinked env files when writing', async () => {
187
+ const tmp = makeTempDir('write');
188
+ const outside = makeTempDir('write-outside');
189
+ try {
190
+ const outsideEnv = join(outside, '.env');
191
+ writeFileSync(outsideEnv, 'SECRET=outside\n');
192
+ symlinkSync(outsideEnv, join(tmp, '.env'));
193
+
194
+ await assert.rejects(
195
+ () => writeEnvKey(join(tmp, '.env'), 'SECRET', 'inside'),
196
+ /ELOOP|symbolic link|symlink/i,
197
+ );
198
+ assert.equal(readFileSync(outsideEnv, 'utf8'), 'SECRET=outside\n');
199
+ } finally {
200
+ rmSync(tmp, { recursive: true, force: true });
201
+ rmSync(outside, { recursive: true, force: true });
202
+ }
203
+ });
204
+ });
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // resolveProjectEnvFilePath
208
+ // ---------------------------------------------------------------------------
209
+
210
+ describe('resolveProjectEnvFilePath', () => {
211
+ it('allows .env under the project root', () => {
212
+ const tmp = makeTempDir('env-path');
213
+ try {
214
+ assert.equal(resolveProjectEnvFilePath(tmp, '.env'), join(realpathSync.native(tmp), '.env'));
215
+ } finally {
216
+ rmSync(tmp, { recursive: true, force: true });
217
+ }
218
+ });
219
+
220
+ it('rejects envFilePath outside the project root', () => {
221
+ const tmp = makeTempDir('env-path');
222
+ try {
223
+ assert.throws(
224
+ () => resolveProjectEnvFilePath(tmp, '../outside.env'),
225
+ /inside the project directory/,
226
+ );
227
+ } finally {
228
+ rmSync(tmp, { recursive: true, force: true });
229
+ }
230
+ });
231
+
232
+ it('rejects symlinked parent directories that escape the project root', () => {
233
+ const tmp = makeTempDir('env-path');
234
+ const outside = makeTempDir('env-path-outside');
235
+ try {
236
+ symlinkSync(outside, join(tmp, 'linked-outside'), 'dir');
237
+ assert.throws(
238
+ () => resolveProjectEnvFilePath(tmp, 'linked-outside/.env'),
239
+ /inside the project directory/,
240
+ );
241
+ } finally {
242
+ rmSync(tmp, { recursive: true, force: true });
243
+ rmSync(outside, { recursive: true, force: true });
244
+ }
245
+ });
246
+
247
+ it('rejects existing env files that are symlinks outside the project root', () => {
248
+ const tmp = makeTempDir('env-path');
249
+ const outside = makeTempDir('env-path-outside');
250
+ try {
251
+ writeFileSync(join(outside, '.env'), 'SECRET=outside\n');
252
+ symlinkSync(join(outside, '.env'), join(tmp, '.env'));
253
+ assert.throws(
254
+ () => resolveProjectEnvFilePath(tmp, '.env'),
255
+ /inside the project directory/,
256
+ );
257
+ } finally {
258
+ rmSync(tmp, { recursive: true, force: true });
259
+ rmSync(outside, { recursive: true, force: true });
260
+ }
261
+ });
184
262
  });
185
263
 
186
264
  // ---------------------------------------------------------------------------
@@ -5,9 +5,9 @@
5
5
  // destinations, and checking existing keys. Used by secure_env_collect
6
6
  // MCP tool. No TUI dependencies — pure filesystem + process.env operations.
7
7
 
8
- import { readFile, writeFile } from "node:fs/promises";
9
- import { existsSync, statSync } from "node:fs";
10
- import { resolve } from "node:path";
8
+ import { open, readFile, rename, rm } from "node:fs/promises";
9
+ import { constants, existsSync, lstatSync, realpathSync, statSync } from "node:fs";
10
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
13
  // checkExistingEnvKeys
@@ -71,10 +71,19 @@ export async function writeEnvKey(filePath: string, key: string, value: string):
71
71
  if (typeof value !== "string") {
72
72
  throw new TypeError(`writeEnvKey expects a string value for key "${key}", got ${typeof value}`);
73
73
  }
74
+ assertWritableEnvFileTarget(filePath);
74
75
  let content = "";
75
76
  try {
76
- content = await readFile(filePath, "utf8");
77
- } catch {
77
+ const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
78
+ try {
79
+ content = await handle.readFile("utf8");
80
+ } finally {
81
+ await handle.close();
82
+ }
83
+ } catch (err) {
84
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
85
+ throw err;
86
+ }
78
87
  content = "";
79
88
  }
80
89
  const escaped = value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "");
@@ -86,7 +95,42 @@ export async function writeEnvKey(filePath: string, key: string, value: string):
86
95
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
87
96
  content += `${line}\n`;
88
97
  }
89
- await writeFile(filePath, content, "utf8");
98
+ const tempPath = join(
99
+ dirname(filePath),
100
+ `.${basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,
101
+ );
102
+ let handle: Awaited<ReturnType<typeof open>> | undefined;
103
+ try {
104
+ handle = await open(tempPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
105
+ await handle.writeFile(content, "utf8");
106
+ await handle.close();
107
+ handle = undefined;
108
+ assertWritableEnvFileTarget(filePath);
109
+ await rename(tempPath, filePath);
110
+ } catch (err) {
111
+ if (handle) {
112
+ try {
113
+ await handle.close();
114
+ } catch {
115
+ // Best-effort cleanup.
116
+ }
117
+ }
118
+ await rm(tempPath, { force: true }).catch(() => undefined);
119
+ throw err;
120
+ }
121
+ }
122
+
123
+ function assertWritableEnvFileTarget(filePath: string): void {
124
+ try {
125
+ if (lstatSync(filePath).isSymbolicLink()) {
126
+ throw new Error("Refusing to write symlinked env file");
127
+ }
128
+ } catch (err) {
129
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
130
+ return;
131
+ }
132
+ throw err;
133
+ }
90
134
  }
91
135
 
92
136
  // ---------------------------------------------------------------------------
@@ -101,6 +145,32 @@ export function isSupportedDeploymentEnvironment(env: string): boolean {
101
145
  return env === "development" || env === "preview" || env === "production";
102
146
  }
103
147
 
148
+ function isWithinProjectRoot(projectRoot: string, candidatePath: string): boolean {
149
+ const rel = relative(projectRoot, candidatePath);
150
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
151
+ }
152
+
153
+ export function resolveProjectEnvFilePath(projectDir: string, envFilePath = ".env"): string {
154
+ const projectRoot = realpathSync.native(resolve(projectDir));
155
+ const candidate = resolve(projectRoot, envFilePath);
156
+ if (!isWithinProjectRoot(projectRoot, candidate)) {
157
+ throw new Error("envFilePath must resolve inside the project directory");
158
+ }
159
+ if (existsSync(candidate)) {
160
+ const targetRealPath = realpathSync.native(candidate);
161
+ if (isWithinProjectRoot(projectRoot, targetRealPath)) {
162
+ return candidate;
163
+ }
164
+ throw new Error("envFilePath must resolve inside the project directory");
165
+ }
166
+ const candidateParent = dirname(candidate);
167
+ const parentRealPath = realpathSync.native(candidateParent);
168
+ if (isWithinProjectRoot(projectRoot, parentRealPath)) {
169
+ return candidate;
170
+ }
171
+ throw new Error("envFilePath must resolve inside the project directory");
172
+ }
173
+
104
174
  // ---------------------------------------------------------------------------
105
175
  // Shell helpers (for vercel/convex CLI)
106
176
  // ---------------------------------------------------------------------------
@@ -20,6 +20,7 @@ import {
20
20
  buildAskUserQuestionsElicitRequest,
21
21
  createMcpServer,
22
22
  formatAskUserQuestionsElicitResult,
23
+ withElicitTimeout,
23
24
  } from './server.js';
24
25
  import { MAX_EVENTS } from './types.js';
25
26
  import type { ManagedSession, CostAccumulator, PendingBlocker } from './types.js';
@@ -668,6 +669,43 @@ describe('createMcpServer tool registration', () => {
668
669
  assert.equal(session.status, 'cancelled');
669
670
  });
670
671
 
672
+ it('gsd_cancel can cancel an interactive session (no sessionId) via projectDir fallback', async () => {
673
+ // Simulate an interactive session: registered by projectDir but with an empty sessionId
674
+ // (e.g. started via `/gsd auto` in terminal or from a restarted MCP server that lost its session registry)
675
+ const projectDir = resolve('/tmp/interactive-session');
676
+ const mockClient = new MockRpcClient({ cwd: projectDir, args: [] });
677
+ const interactiveSession: ManagedSession = {
678
+ sessionId: '', // no sessionId — interactive/restarted scenario
679
+ projectDir,
680
+ status: 'running',
681
+ client: mockClient as any,
682
+ events: [],
683
+ pendingBlocker: null,
684
+ cost: { totalCost: 0, tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } },
685
+ startTime: Date.now(),
686
+ };
687
+ sm._putSession(projectDir, interactiveSession);
688
+
689
+ // cancelSession('') should fail — no session found by empty sessionId
690
+ // cancelSessionByDir should succeed — finds session by projectDir
691
+ await sm.cancelSessionByDir(projectDir);
692
+
693
+ const session = sm.getSessionByDir(projectDir)!;
694
+ assert.equal(session.status, 'cancelled');
695
+ assert.ok(mockClient.aborted, 'client.abort() should have been called');
696
+ });
697
+
698
+ it('gsd_cancel via projectDir works even when sessionId lookup returns undefined', async () => {
699
+ // Start a normal session to get its projectDir
700
+ const sessionId = await sm.startSession('/tmp/cancel-by-dir', { cliPath: '/usr/bin/gsd' });
701
+ const session = sm.getSession(sessionId)!;
702
+ const { projectDir } = session;
703
+
704
+ // cancelSessionByDir should find it by dir and cancel it
705
+ await sm.cancelSessionByDir(projectDir);
706
+ assert.equal(session.status, 'cancelled');
707
+ });
708
+
671
709
  it('buildAskUserQuestionsElicitRequest adds None of the above note field for single-select questions', () => {
672
710
  const request = buildAskUserQuestionsElicitRequest([
673
711
  {
@@ -746,3 +784,32 @@ describe('createMcpServer tool registration', () => {
746
784
  );
747
785
  });
748
786
  });
787
+
788
+ // ---------------------------------------------------------------------------
789
+ // withElicitTimeout
790
+ // ---------------------------------------------------------------------------
791
+
792
+ describe('withElicitTimeout', () => {
793
+ it('resolves with the promise value when it settles before the timeout', async () => {
794
+ const result = await withElicitTimeout(Promise.resolve(42), 'test', 5000);
795
+ assert.equal(result, 42);
796
+ });
797
+
798
+ it('rejects with a timeout error when the promise does not settle in time', async () => {
799
+ const never = new Promise<never>(() => {});
800
+ await assert.rejects(
801
+ () => withElicitTimeout(never, 'ask_user_questions', 1),
802
+ (err: Error) => {
803
+ assert.ok(err.message.includes('ask_user_questions'));
804
+ assert.ok(err.message.includes('timed out'));
805
+ return true;
806
+ },
807
+ );
808
+ });
809
+
810
+ it('clears the timer when the promise resolves (no dangling timer)', async () => {
811
+ const start = Date.now();
812
+ await withElicitTimeout(Promise.resolve('done'), 'test', 50);
813
+ assert.ok(Date.now() - start < 40, 'should not wait for the timeout');
814
+ });
815
+ });
@@ -503,7 +503,11 @@ describe('runDoctorLite', () => {
503
503
  const empty = tmpProject();
504
504
  const result = runDoctorLite(empty);
505
505
  assert.equal(result.ok, true);
506
- assert.equal(result.issues[0].code, 'no_gsd_directory');
506
+ assert.ok(
507
+ result.issues.some(
508
+ (issue) => issue.code === 'no_gsd_directory' || issue.code === 'missing_project_md',
509
+ ),
510
+ );
507
511
  rmSync(empty, { recursive: true, force: true });
508
512
  });
509
513
  });
@@ -0,0 +1,294 @@
1
+ /**
2
+ * packages/mcp-server — remote-questions regression tests (#4364)
3
+ *
4
+ * Verifies that the ask_user_questions MCP handler delegates to
5
+ * tryRemoteQuestions when a remote channel is configured, instead of going
6
+ * straight to elicitInput (which is unreachable in Claude Code-under-gsd
7
+ * sessions).
8
+ *
9
+ * Strategy: we cannot mock ES modules without --experimental-test-module-mocks,
10
+ * so we test the exported helpers from remote-questions.ts directly and verify
11
+ * the handler routing by observing mock side-effects injected via environment
12
+ * variables and a fake PREFERENCES.md file.
13
+ */
14
+
15
+ import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
16
+ import assert from 'node:assert/strict';
17
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
18
+ import { tmpdir } from 'node:os';
19
+ import { join } from 'node:path';
20
+
21
+ // We test the module's exported helpers directly.
22
+ // The real createMcpServer routing is tested separately via the
23
+ // ask_user_questions handler integration test further below.
24
+ import {
25
+ isRemoteConfigured,
26
+ tryRemoteQuestions,
27
+ type RemoteQuestion,
28
+ } from './remote-questions.js';
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Helpers
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const SAMPLE_QUESTIONS: RemoteQuestion[] = [
35
+ {
36
+ id: 'approach',
37
+ header: 'Approach',
38
+ question: 'Which implementation approach should I use?',
39
+ options: [
40
+ { label: 'Option A (Recommended)', description: 'Faster, simpler path.' },
41
+ { label: 'Option B', description: 'More flexible but complex.' },
42
+ ],
43
+ },
44
+ ];
45
+
46
+ function makePrefsFile(dir: string, content: string): void {
47
+ writeFileSync(join(dir, 'PREFERENCES.md'), content, 'utf-8');
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // isRemoteConfigured — unit tests
52
+ // ---------------------------------------------------------------------------
53
+
54
+ describe('isRemoteConfigured', () => {
55
+ let tmpDir: string;
56
+ const origGsdHome = process.env['GSD_HOME'];
57
+ const origToken = process.env['DISCORD_BOT_TOKEN'];
58
+
59
+ before(() => {
60
+ tmpDir = mkdtempSync(join(tmpdir(), 'gsd-rq-test-'));
61
+ });
62
+
63
+ after(() => {
64
+ rmSync(tmpDir, { recursive: true, force: true });
65
+ if (origGsdHome !== undefined) {
66
+ process.env['GSD_HOME'] = origGsdHome;
67
+ } else {
68
+ delete process.env['GSD_HOME'];
69
+ }
70
+ if (origToken !== undefined) {
71
+ process.env['DISCORD_BOT_TOKEN'] = origToken;
72
+ } else {
73
+ delete process.env['DISCORD_BOT_TOKEN'];
74
+ }
75
+ });
76
+
77
+ beforeEach(() => {
78
+ process.env['GSD_HOME'] = tmpDir;
79
+ delete process.env['DISCORD_BOT_TOKEN'];
80
+ delete process.env['SLACK_BOT_TOKEN'];
81
+ delete process.env['TELEGRAM_BOT_TOKEN'];
82
+ });
83
+
84
+ it('returns false when PREFERENCES.md is absent', () => {
85
+ assert.equal(isRemoteConfigured(), false);
86
+ });
87
+
88
+ it('returns false when PREFERENCES.md has no remote_questions block', () => {
89
+ makePrefsFile(tmpDir, '---\nsome_other_key: value\n---\n');
90
+ assert.equal(isRemoteConfigured(), false);
91
+ });
92
+
93
+ it('returns false when remote_questions lacks channel or channel_id', () => {
94
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n---\n');
95
+ assert.equal(isRemoteConfigured(), false);
96
+ });
97
+
98
+ it('returns false when discord channel_id format is invalid', () => {
99
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n channel_id: "not-a-snowflake"\n---\n');
100
+ process.env['DISCORD_BOT_TOKEN'] = 'Bot fake-token';
101
+ assert.equal(isRemoteConfigured(), false);
102
+ });
103
+
104
+ it('returns false when env token is absent even if prefs are valid', () => {
105
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n channel_id: "123456789012345678"\n---\n');
106
+ // DISCORD_BOT_TOKEN not set
107
+ assert.equal(isRemoteConfigured(), false);
108
+ });
109
+
110
+ it('returns true when discord config and token are both present', () => {
111
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n channel_id: "123456789012345678"\n---\n');
112
+ process.env['DISCORD_BOT_TOKEN'] = 'Bot fake-token';
113
+ assert.equal(isRemoteConfigured(), true);
114
+ });
115
+
116
+ it('returns true for slack config with valid channel ID and token', () => {
117
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: slack\n channel_id: "C0123456789"\n---\n');
118
+ process.env['SLACK_BOT_TOKEN'] = 'xoxb-fake';
119
+ assert.equal(isRemoteConfigured(), true);
120
+ delete process.env['SLACK_BOT_TOKEN'];
121
+ });
122
+
123
+ it('returns true for telegram config with valid chat ID and token', () => {
124
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: telegram\n channel_id: "-1001234567890"\n---\n');
125
+ process.env['TELEGRAM_BOT_TOKEN'] = 'fake-telegram-token';
126
+ assert.equal(isRemoteConfigured(), true);
127
+ delete process.env['TELEGRAM_BOT_TOKEN'];
128
+ });
129
+
130
+ it('returns false for unknown channel type', () => {
131
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: whatsapp\n channel_id: "123456"\n---\n');
132
+ assert.equal(isRemoteConfigured(), false);
133
+ });
134
+ });
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // tryRemoteQuestions — routing: returns null when not configured
138
+ // ---------------------------------------------------------------------------
139
+
140
+ describe('tryRemoteQuestions returns null when remote is not configured', () => {
141
+ let tmpDir: string;
142
+ const origGsdHome = process.env['GSD_HOME'];
143
+
144
+ before(() => {
145
+ tmpDir = mkdtempSync(join(tmpdir(), 'gsd-rq-null-test-'));
146
+ process.env['GSD_HOME'] = tmpDir;
147
+ // No PREFERENCES.md, no tokens
148
+ delete process.env['DISCORD_BOT_TOKEN'];
149
+ delete process.env['SLACK_BOT_TOKEN'];
150
+ delete process.env['TELEGRAM_BOT_TOKEN'];
151
+ });
152
+
153
+ after(() => {
154
+ rmSync(tmpDir, { recursive: true, force: true });
155
+ if (origGsdHome !== undefined) {
156
+ process.env['GSD_HOME'] = origGsdHome;
157
+ } else {
158
+ delete process.env['GSD_HOME'];
159
+ }
160
+ });
161
+
162
+ it('returns null when no remote channel is configured', async () => {
163
+ const result = await tryRemoteQuestions(SAMPLE_QUESTIONS);
164
+ assert.equal(result, null);
165
+ });
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // tryRemoteQuestions — auth failure path
170
+ // ---------------------------------------------------------------------------
171
+
172
+ describe('tryRemoteQuestions returns error result on auth failure', () => {
173
+ let tmpDir: string;
174
+ const origGsdHome = process.env['GSD_HOME'];
175
+ const origToken = process.env['DISCORD_BOT_TOKEN'];
176
+
177
+ before(() => {
178
+ tmpDir = mkdtempSync(join(tmpdir(), 'gsd-rq-auth-test-'));
179
+ // Set up a valid discord config pointing at an unreachable/fake endpoint
180
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n channel_id: "123456789012345678"\n---\n');
181
+ process.env['GSD_HOME'] = tmpDir;
182
+ // Use an obviously invalid token — the Discord API will reject it
183
+ // but we don't actually call the live Discord API in unit tests.
184
+ // Instead we rely on the fact that fetch() to Discord API will fail
185
+ // in a test environment (no network or invalid token → error result).
186
+ process.env['DISCORD_BOT_TOKEN'] = 'invalid-fake-token-for-test';
187
+ });
188
+
189
+ after(() => {
190
+ rmSync(tmpDir, { recursive: true, force: true });
191
+ if (origGsdHome !== undefined) {
192
+ process.env['GSD_HOME'] = origGsdHome;
193
+ } else {
194
+ delete process.env['GSD_HOME'];
195
+ }
196
+ if (origToken !== undefined) {
197
+ process.env['DISCORD_BOT_TOKEN'] = origToken;
198
+ } else {
199
+ delete process.env['DISCORD_BOT_TOKEN'];
200
+ }
201
+ });
202
+
203
+ it('returns an error result (not null) when auth fails', async () => {
204
+ const controller = new AbortController();
205
+ // Abort immediately so we don't actually poll
206
+ controller.abort();
207
+
208
+ const result = await tryRemoteQuestions(SAMPLE_QUESTIONS, controller.signal);
209
+
210
+ // When configured but auth/dispatch fails, we get a non-null error result
211
+ // rather than null (null means "not configured").
212
+ // The exact failure mode (network error vs HTTP 401) depends on the test
213
+ // environment, but the contract is: non-null result with error details or
214
+ // timed_out details.
215
+ assert.ok(result !== null, 'should return a result, not null, when configured');
216
+ assert.ok(Array.isArray(result.content), 'result.content should be an array');
217
+ assert.ok(result.content.length > 0, 'result.content should be non-empty');
218
+ assert.equal(result.content[0].type, 'text');
219
+ });
220
+ });
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Frontmatter parser — unit tests
224
+ // ---------------------------------------------------------------------------
225
+
226
+ describe('remote-questions YAML frontmatter parsing (via isRemoteConfigured)', () => {
227
+ let tmpDir: string;
228
+ const origGsdHome = process.env['GSD_HOME'];
229
+
230
+ before(() => {
231
+ tmpDir = mkdtempSync(join(tmpdir(), 'gsd-rq-yaml-test-'));
232
+ });
233
+
234
+ after(() => {
235
+ rmSync(tmpDir, { recursive: true, force: true });
236
+ if (origGsdHome !== undefined) {
237
+ process.env['GSD_HOME'] = origGsdHome;
238
+ } else {
239
+ delete process.env['GSD_HOME'];
240
+ }
241
+ delete process.env['DISCORD_BOT_TOKEN'];
242
+ });
243
+
244
+ beforeEach(() => {
245
+ process.env['GSD_HOME'] = tmpDir;
246
+ process.env['DISCORD_BOT_TOKEN'] = 'Bot fake-token';
247
+ });
248
+
249
+ afterEach(() => {
250
+ delete process.env['DISCORD_BOT_TOKEN'];
251
+ });
252
+
253
+ it('parses channel_id as a quoted string', () => {
254
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n channel_id: "987654321098765432"\n---\n');
255
+ assert.equal(isRemoteConfigured(), true);
256
+ });
257
+
258
+ it('parses channel_id as a bare number', () => {
259
+ makePrefsFile(tmpDir, '---\nremote_questions:\n channel: discord\n channel_id: 987654321098765432\n---\n');
260
+ assert.equal(isRemoteConfigured(), true);
261
+ });
262
+
263
+ it('respects timeout_minutes and poll_interval_seconds presence without crashing', () => {
264
+ makePrefsFile(tmpDir, [
265
+ '---',
266
+ 'remote_questions:',
267
+ ' channel: discord',
268
+ ' channel_id: "123456789012345678"',
269
+ ' timeout_minutes: 10',
270
+ ' poll_interval_seconds: 3',
271
+ '---',
272
+ ].join('\n'));
273
+ assert.equal(isRemoteConfigured(), true);
274
+ });
275
+
276
+ it('ignores content after the closing --- fence', () => {
277
+ makePrefsFile(tmpDir, [
278
+ '---',
279
+ 'remote_questions:',
280
+ ' channel: discord',
281
+ ' channel_id: "123456789012345678"',
282
+ '---',
283
+ '# Markdown content after the fence is ignored',
284
+ 'Some prose text.',
285
+ ].join('\n'));
286
+ assert.equal(isRemoteConfigured(), true);
287
+ });
288
+
289
+ it('returns false when PREFERENCES.md has content but no frontmatter delimiters', () => {
290
+ makePrefsFile(tmpDir, 'remote_questions:\n channel: discord\n channel_id: "123456789012345678"\n');
291
+ // No --- fences — not recognized as frontmatter
292
+ assert.equal(isRemoteConfigured(), false);
293
+ });
294
+ });