gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.20aba06

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 (346) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/remote-questions-config.js +2 -2
  8. package/dist/resource-loader.js +100 -3
  9. package/dist/resources/extensions/async-jobs/index.js +10 -0
  10. package/dist/resources/extensions/browser-tools/index.js +3 -1
  11. package/dist/resources/extensions/browser-tools/package.json +3 -1
  12. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  13. package/dist/resources/extensions/cmux/index.js +55 -1
  14. package/dist/resources/extensions/context7/package.json +1 -1
  15. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  16. package/dist/resources/extensions/github-sync/cli.js +284 -0
  17. package/dist/resources/extensions/github-sync/index.js +73 -0
  18. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  19. package/dist/resources/extensions/github-sync/sync.js +424 -0
  20. package/dist/resources/extensions/github-sync/templates.js +118 -0
  21. package/dist/resources/extensions/github-sync/types.js +7 -0
  22. package/dist/resources/extensions/google-search/package.json +3 -1
  23. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  24. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +923 -787
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +107 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +205 -51
  29. package/dist/resources/extensions/gsd/auto-start.js +19 -3
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  31. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  32. package/dist/resources/extensions/gsd/auto.js +149 -100
  33. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  35. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  37. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  38. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  39. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  41. package/dist/resources/extensions/gsd/captures.js +9 -1
  42. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  44. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  45. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  46. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  47. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  48. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  49. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  50. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  51. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  52. package/dist/resources/extensions/gsd/commands-handlers.js +17 -4
  53. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  54. package/dist/resources/extensions/gsd/commands.js +8 -1169
  55. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  56. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  57. package/dist/resources/extensions/gsd/detection.js +1 -2
  58. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  59. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  60. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  61. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  62. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  63. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  64. package/dist/resources/extensions/gsd/doctor.js +234 -12
  65. package/dist/resources/extensions/gsd/env-utils.js +29 -0
  66. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  67. package/dist/resources/extensions/gsd/export-html.js +46 -0
  68. package/dist/resources/extensions/gsd/export.js +1 -1
  69. package/dist/resources/extensions/gsd/files.js +48 -9
  70. package/dist/resources/extensions/gsd/forensics.js +1 -1
  71. package/dist/resources/extensions/gsd/git-service.js +30 -12
  72. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  73. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  74. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  75. package/dist/resources/extensions/gsd/health-widget.js +4 -87
  76. package/dist/resources/extensions/gsd/index.js +4 -1111
  77. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  78. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  79. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  80. package/dist/resources/extensions/gsd/package.json +1 -1
  81. package/dist/resources/extensions/gsd/paths.js +3 -0
  82. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  83. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  84. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  85. package/dist/resources/extensions/gsd/preferences.js +22 -11
  86. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  87. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  88. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  89. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  90. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  91. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  92. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  93. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  94. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  95. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  96. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  97. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  98. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  99. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  100. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  101. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  102. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  103. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  104. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  105. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  106. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  107. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  108. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  109. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  110. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  111. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  112. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  113. package/dist/resources/extensions/gsd/state.js +42 -23
  114. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  115. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  116. package/dist/resources/extensions/gsd/visualizer-data.js +27 -2
  117. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  118. package/dist/resources/extensions/gsd/worktree.js +35 -16
  119. package/dist/resources/extensions/mcp-client/index.js +14 -1
  120. package/dist/resources/extensions/remote-questions/status.js +4 -1
  121. package/dist/resources/extensions/remote-questions/store.js +4 -1
  122. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  123. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  124. package/dist/resources/extensions/subagent/index.js +12 -3
  125. package/dist/resources/extensions/subagent/isolation.js +2 -1
  126. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  127. package/dist/resources/extensions/universal-config/package.json +1 -1
  128. package/dist/welcome-screen.d.ts +13 -0
  129. package/dist/welcome-screen.js +97 -0
  130. package/package.json +1 -1
  131. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  132. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  133. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  134. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  135. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  137. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  140. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  143. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  146. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  147. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  148. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  149. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/skills.js +8 -2
  151. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  153. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  154. package/packages/pi-coding-agent/dist/index.js +1 -1
  155. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  164. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  179. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  180. package/packages/pi-coding-agent/package.json +1 -1
  181. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  182. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  183. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  184. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  185. package/packages/pi-coding-agent/src/core/skills.ts +11 -2
  186. package/packages/pi-coding-agent/src/index.ts +1 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  189. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  190. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  192. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/async-jobs/index.ts +11 -0
  195. package/src/resources/extensions/browser-tools/index.ts +3 -0
  196. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  197. package/src/resources/extensions/cmux/index.ts +57 -1
  198. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  199. package/src/resources/extensions/github-sync/cli.ts +364 -0
  200. package/src/resources/extensions/github-sync/index.ts +93 -0
  201. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  202. package/src/resources/extensions/github-sync/sync.ts +556 -0
  203. package/src/resources/extensions/github-sync/templates.ts +183 -0
  204. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  205. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  206. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  207. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  208. package/src/resources/extensions/github-sync/types.ts +47 -0
  209. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  210. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  211. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  212. package/src/resources/extensions/gsd/auto-loop.ts +1285 -1138
  213. package/src/resources/extensions/gsd/auto-post-unit.ts +90 -46
  214. package/src/resources/extensions/gsd/auto-prompts.ts +250 -53
  215. package/src/resources/extensions/gsd/auto-start.ts +24 -3
  216. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  217. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  218. package/src/resources/extensions/gsd/auto.ts +152 -111
  219. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  220. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  221. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  222. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  225. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  226. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  227. package/src/resources/extensions/gsd/captures.ts +10 -1
  228. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  229. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  230. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  231. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  232. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  233. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  234. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  235. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  236. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  237. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  238. package/src/resources/extensions/gsd/commands-handlers.ts +18 -3
  239. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  240. package/src/resources/extensions/gsd/commands.ts +10 -1307
  241. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  242. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  243. package/src/resources/extensions/gsd/detection.ts +2 -2
  244. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  245. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  246. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  247. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  248. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  249. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  250. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  251. package/src/resources/extensions/gsd/doctor.ts +243 -14
  252. package/src/resources/extensions/gsd/env-utils.ts +31 -0
  253. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  254. package/src/resources/extensions/gsd/export-html.ts +51 -0
  255. package/src/resources/extensions/gsd/export.ts +1 -1
  256. package/src/resources/extensions/gsd/files.ts +51 -11
  257. package/src/resources/extensions/gsd/forensics.ts +1 -1
  258. package/src/resources/extensions/gsd/git-service.ts +44 -10
  259. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  260. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  261. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  262. package/src/resources/extensions/gsd/health-widget.ts +4 -89
  263. package/src/resources/extensions/gsd/index.ts +12 -1307
  264. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  265. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  266. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  267. package/src/resources/extensions/gsd/paths.ts +4 -0
  268. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  269. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  270. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  271. package/src/resources/extensions/gsd/preferences.ts +25 -11
  272. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  273. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  274. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  276. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  277. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  278. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  279. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  280. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  281. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  282. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  283. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  284. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  285. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  286. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  287. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  288. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  289. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  290. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  291. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  292. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  293. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  294. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  295. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  296. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  297. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  298. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  299. package/src/resources/extensions/gsd/state.ts +39 -21
  300. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  301. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  302. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  303. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -77
  304. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  305. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  306. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  307. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  308. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  309. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  310. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  311. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  312. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  313. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  314. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  315. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  316. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  317. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  318. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  319. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  320. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  321. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  322. package/src/resources/extensions/gsd/types.ts +18 -1
  323. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  324. package/src/resources/extensions/gsd/visualizer-data.ts +52 -2
  325. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  326. package/src/resources/extensions/gsd/worktree.ts +35 -15
  327. package/src/resources/extensions/mcp-client/index.ts +17 -1
  328. package/src/resources/extensions/remote-questions/status.ts +5 -1
  329. package/src/resources/extensions/remote-questions/store.ts +5 -1
  330. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  331. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  332. package/src/resources/extensions/subagent/index.ts +12 -3
  333. package/src/resources/extensions/subagent/isolation.ts +3 -1
  334. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  335. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  336. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  337. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  338. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  339. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  340. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  341. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  342. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  343. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  344. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  345. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  346. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -0,0 +1,140 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { loadSkills } from "@gsd/pi-coding-agent";
7
+ import { buildSkillActivationBlock } from "../auto-prompts.js";
8
+ import type { GSDPreferences } from "../preferences.js";
9
+
10
+ function makeTempBase(): string {
11
+ return mkdtempSync(join(tmpdir(), "gsd-skill-activation-"));
12
+ }
13
+
14
+ function cleanup(base: string): void {
15
+ rmSync(base, { recursive: true, force: true });
16
+ }
17
+
18
+ function writeSkill(base: string, name: string, description: string): void {
19
+ const dir = join(base, "skills", name);
20
+ mkdirSync(dir, { recursive: true });
21
+ writeFileSync(join(dir, "SKILL.md"), `---\nname: ${name}\ndescription: ${description}\n---\n\n# ${name}\n`);
22
+ }
23
+
24
+ function loadOnlyTestSkills(base: string): void {
25
+ loadSkills({ cwd: base, includeDefaults: false, skillPaths: [join(base, "skills")] });
26
+ }
27
+
28
+ function buildBlock(
29
+ base: string,
30
+ params: Partial<Parameters<typeof buildSkillActivationBlock>[0]> = {},
31
+ preferences: GSDPreferences = {},
32
+ ): string {
33
+ return buildSkillActivationBlock({
34
+ base,
35
+ milestoneId: "M001",
36
+ sliceId: "S01",
37
+ ...params,
38
+ preferences,
39
+ });
40
+ }
41
+
42
+ test("buildSkillActivationBlock matches installed skills from task context", () => {
43
+ const base = makeTempBase();
44
+ try {
45
+ writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work.");
46
+ writeSkill(base, "swiftui", "Use for SwiftUI views, iOS layout, and Apple platform UI work.");
47
+ loadOnlyTestSkills(base);
48
+
49
+ const result = buildBlock(base, {
50
+ sliceTitle: "Build React dashboard",
51
+ taskId: "T01",
52
+ taskTitle: "Implement React settings panel",
53
+ });
54
+
55
+ assert.match(result, /<skill_activation>/);
56
+ assert.match(result, /Call Skill\('react'\)/);
57
+ assert.doesNotMatch(result, /swiftui/);
58
+ } finally {
59
+ cleanup(base);
60
+ }
61
+ });
62
+
63
+ test("buildSkillActivationBlock includes always_use_skills from preferences using exact Skill tool format", () => {
64
+ const base = makeTempBase();
65
+ try {
66
+ writeSkill(base, "swift-testing", "Use for Swift Testing assertions and verification patterns.");
67
+ loadOnlyTestSkills(base);
68
+
69
+ const result = buildBlock(base, { taskTitle: "Unrelated task title" }, {
70
+ always_use_skills: ["swift-testing"],
71
+ });
72
+
73
+ assert.equal(result, "<skill_activation>Call Skill('swift-testing').</skill_activation>");
74
+ } finally {
75
+ cleanup(base);
76
+ }
77
+ });
78
+
79
+ test("buildSkillActivationBlock includes skill_rules matches and task-plan skills_used", () => {
80
+ const base = makeTempBase();
81
+ try {
82
+ writeSkill(base, "prisma", "Use for Prisma schema, migrations, and ORM queries.");
83
+ writeSkill(base, "accessibility", "Use for accessibility, aria attributes, and keyboard support.");
84
+ loadOnlyTestSkills(base);
85
+
86
+ const taskPlan = [
87
+ "---",
88
+ "skills_used:",
89
+ " - accessibility",
90
+ "---",
91
+ "# T01: Example",
92
+ ].join("\n");
93
+
94
+ const result = buildBlock(base, {
95
+ taskTitle: "Update prisma schema",
96
+ taskPlanContent: taskPlan,
97
+ }, {
98
+ skill_rules: [{ when: "prisma database schema", use: ["prisma"] }],
99
+ });
100
+
101
+ assert.match(result, /Call Skill\('accessibility'\)/);
102
+ assert.match(result, /Call Skill\('prisma'\)/);
103
+ } finally {
104
+ cleanup(base);
105
+ }
106
+ });
107
+
108
+ test("buildSkillActivationBlock honors avoid_skills", () => {
109
+ const base = makeTempBase();
110
+ try {
111
+ writeSkill(base, "react", "Use for React components and frontend UI work.");
112
+ loadOnlyTestSkills(base);
113
+
114
+ const result = buildBlock(base, {
115
+ taskTitle: "Implement React settings panel",
116
+ }, {
117
+ avoid_skills: ["react"],
118
+ });
119
+
120
+ assert.equal(result, "");
121
+ } finally {
122
+ cleanup(base);
123
+ }
124
+ });
125
+
126
+ test("buildSkillActivationBlock falls back cleanly when nothing matches", () => {
127
+ const base = makeTempBase();
128
+ try {
129
+ writeSkill(base, "swiftui", "Use for SwiftUI apps.");
130
+ loadOnlyTestSkills(base);
131
+
132
+ const result = buildBlock(base, {
133
+ taskTitle: "Plain text docs task",
134
+ });
135
+
136
+ assert.equal(result, "");
137
+ } finally {
138
+ cleanup(base);
139
+ }
140
+ });
@@ -422,25 +422,25 @@ assertTrue(
422
422
  "overlay has 10 tab labels",
423
423
  );
424
424
 
425
- // Verify commands.ts integration
426
- const commandsPath = join(__dirname, "..", "commands.ts");
427
- const commandsSrc = readFileSync(commandsPath, "utf-8");
425
+ // Verify commands/handlers/core.ts integration
426
+ const coreHandlerPath = join(__dirname, "..", "commands", "handlers", "core.ts");
427
+ const coreHandlerSrc = readFileSync(coreHandlerPath, "utf-8");
428
428
 
429
- console.log("\n=== commands.ts integration ===");
429
+ console.log("\n=== commands/handlers/core.ts integration ===");
430
430
 
431
431
  assertTrue(
432
- commandsSrc.includes('"visualize"'),
433
- "commands.ts has visualize in subcommands array",
432
+ coreHandlerSrc.includes('"visualize"'),
433
+ "core.ts has visualize in subcommands array",
434
434
  );
435
435
 
436
436
  assertTrue(
437
- commandsSrc.includes("GSDVisualizerOverlay"),
438
- "commands.ts imports GSDVisualizerOverlay",
437
+ coreHandlerSrc.includes("GSDVisualizerOverlay"),
438
+ "core.ts imports GSDVisualizerOverlay",
439
439
  );
440
440
 
441
441
  assertTrue(
442
- commandsSrc.includes("handleVisualize"),
443
- "commands.ts has handleVisualize handler",
442
+ coreHandlerSrc.includes("handleVisualize"),
443
+ "core.ts has handleVisualize handler",
444
444
  );
445
445
 
446
446
  report();
@@ -11,6 +11,7 @@ import {
11
11
  getMainBranch,
12
12
  getSliceBranchName,
13
13
  parseSliceBranch,
14
+ resolveProjectRoot,
14
15
  setActiveMilestoneId,
15
16
  SLICE_BRANCH_RE,
16
17
  } from "../worktree.ts";
@@ -165,6 +166,52 @@ async function main(): Promise<void> {
165
166
  rmSync(repo, { recursive: true, force: true });
166
167
  }
167
168
 
169
+ // ── detectWorktreeName: symlink-resolved paths ───────────────────────────
170
+ console.log("\n=== detectWorktreeName (symlink-resolved paths) ===");
171
+ assertEq(
172
+ detectWorktreeName("/Users/fran/.gsd/projects/89e1c9ad49bf/worktrees/M001"),
173
+ "M001",
174
+ "detects milestone in symlink-resolved path",
175
+ );
176
+ assertEq(
177
+ detectWorktreeName("/Users/fran/.gsd/projects/abc123/worktrees/M002/subdir"),
178
+ "M002",
179
+ "detects milestone with trailing subdir in symlink-resolved path",
180
+ );
181
+ assertEq(
182
+ detectWorktreeName("/Users/fran/.gsd/projects/abc123"),
183
+ null,
184
+ "returns null for project root without worktrees segment",
185
+ );
186
+ assertEq(
187
+ detectWorktreeName("/foo/.gsd/worktrees/M001"),
188
+ "M001",
189
+ "still detects direct layout path",
190
+ );
191
+
192
+ // ── resolveProjectRoot: symlink-resolved paths ──────────────────────────
193
+ console.log("\n=== resolveProjectRoot (symlink-resolved paths) ===");
194
+ assertEq(
195
+ resolveProjectRoot("/Users/fran/.gsd/projects/89e1c9ad49bf/worktrees/M001"),
196
+ "/Users/fran",
197
+ "resolves to user home for symlink-resolved path",
198
+ );
199
+ assertEq(
200
+ resolveProjectRoot("/foo/.gsd/worktrees/M001"),
201
+ "/foo",
202
+ "still resolves direct layout path",
203
+ );
204
+ assertEq(
205
+ resolveProjectRoot("/some/repo"),
206
+ "/some/repo",
207
+ "returns unchanged for non-worktree path",
208
+ );
209
+ assertEq(
210
+ resolveProjectRoot("/data/.gsd/projects/deadbeef/worktrees/M003/nested"),
211
+ "/data",
212
+ "resolves correctly with nested subdirs after worktree name",
213
+ );
214
+
168
215
  rmSync(base, { recursive: true, force: true });
169
216
  report();
170
217
  }
@@ -61,6 +61,16 @@ export interface TaskPlanEntry {
61
61
  verify?: string; // e.g. "run tests" — extracted from "- Verify:" subline
62
62
  }
63
63
 
64
+ export interface TaskPlanFrontmatter {
65
+ estimated_steps?: number; // optional scope estimate for plan quality validator
66
+ estimated_files?: number; // optional file-count estimate for scope warning heuristics
67
+ skills_used: string[]; // installed skill slugs/names to hand off to execute-task prompts
68
+ }
69
+
70
+ export interface TaskPlanFile {
71
+ frontmatter: TaskPlanFrontmatter;
72
+ }
73
+
64
74
  // ─── Verification Gate ─────────────────────────────────────────────────────
65
75
 
66
76
  /** Result of a single verification command execution */
@@ -423,7 +433,6 @@ export interface Requirement {
423
433
 
424
434
  // ─── Parallel Orchestration Types ────────────────────────────────────────
425
435
 
426
- export type CompressionStrategy = "truncate" | "compress";
427
436
  export type ContextSelectionMode = "full" | "smart";
428
437
 
429
438
  export type MergeStrategy = "per-slice" | "per-milestone";
@@ -479,3 +488,11 @@ export interface ReactiveExecutionState {
479
488
  };
480
489
  updatedAt: string;
481
490
  }
491
+
492
+ export interface BrowserFlowResult {
493
+ url: string;
494
+ passed: boolean;
495
+ checksTotal: number;
496
+ checksPassed: number;
497
+ duration: number;
498
+ }
@@ -37,6 +37,21 @@ export interface AuditWarningJSON {
37
37
  fixAvailable: boolean;
38
38
  }
39
39
 
40
+ export interface BrowserEvidenceCheckJSON {
41
+ description: string;
42
+ passed: boolean;
43
+ actual?: string;
44
+ evidence?: string;
45
+ error?: string;
46
+ }
47
+
48
+ export interface BrowserEvidenceJSON {
49
+ url: string;
50
+ passed: boolean;
51
+ checks: BrowserEvidenceCheckJSON[];
52
+ duration: number;
53
+ }
54
+
40
55
  export interface EvidenceJSON {
41
56
  schemaVersion: 1;
42
57
  taskId: string;
@@ -49,6 +64,7 @@ export interface EvidenceJSON {
49
64
  maxRetries?: number;
50
65
  runtimeErrors?: RuntimeErrorJSON[];
51
66
  auditWarnings?: AuditWarningJSON[];
67
+ browser?: BrowserEvidenceJSON;
52
68
  }
53
69
 
54
70
  /**
@@ -1,10 +1,11 @@
1
1
  // Data loader for workflow visualizer overlay — aggregates state + metrics.
2
2
 
3
3
  import { existsSync, readFileSync, statSync } from 'node:fs';
4
+ import { join } from 'node:path';
4
5
  import { deriveState } from './state.js';
5
6
  import { parseRoadmap, parsePlan, parseSummary, loadFile } from './files.js';
6
- import { findMilestoneIds } from './guided-flow.js';
7
- import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile } from './paths.js';
7
+ import { findMilestoneIds } from './milestone-ids.js';
8
+ import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile, gsdRoot } from './paths.js';
8
9
  import {
9
10
  getLedger,
10
11
  getProjectTotals,
@@ -21,6 +22,8 @@ import { loadEffectiveGSDPreferences } from './preferences.js';
21
22
  import { runProviderChecks, type ProviderCheckResult } from './doctor-providers.js';
22
23
  import { generateSkillHealthReport } from './skill-health.js';
23
24
  import { runEnvironmentChecks, type EnvironmentCheckResult } from './doctor-environment.js';
25
+ import { computeProgressScore } from './progress-score.js';
26
+ import { getHealthHistory } from './doctor-proactive.js';
24
27
 
25
28
  import type { Phase } from './types.js';
26
29
  import type { CaptureEntry } from './captures.js';
@@ -161,6 +164,27 @@ export interface SkillSummaryInfo {
161
164
  topIssue: string | null;
162
165
  }
163
166
 
167
+ /** A single doctor history entry for visualizer display. */
168
+ export interface VisualizerDoctorEntry {
169
+ ts: string;
170
+ ok: boolean;
171
+ errors: number;
172
+ warnings: number;
173
+ fixes: number;
174
+ codes: string[];
175
+ issues?: Array<{ severity: string; code: string; message: string; unitId: string }>;
176
+ fixDescriptions?: string[];
177
+ scope?: string;
178
+ summary?: string;
179
+ }
180
+
181
+ /** Current progress score snapshot for health display. */
182
+ export interface VisualizerProgressScore {
183
+ level: "green" | "yellow" | "red";
184
+ summary: string;
185
+ signals: Array<{ kind: "positive" | "negative" | "neutral"; label: string }>;
186
+ }
187
+
164
188
  export interface HealthInfo {
165
189
  budgetCeiling: number | undefined;
166
190
  tokenProfile: string;
@@ -174,6 +198,10 @@ export interface HealthInfo {
174
198
  providers: ProviderStatusSummary[];
175
199
  skillSummary: SkillSummaryInfo;
176
200
  environmentIssues: import("./doctor-environment.js").EnvironmentCheckResult[];
201
+ /** Persisted doctor run history (most recent first, up to 20 entries). */
202
+ doctorHistory?: VisualizerDoctorEntry[];
203
+ /** Current in-memory progress score (null if auto-mode not active). */
204
+ progressScore?: VisualizerProgressScore | null;
177
205
  }
178
206
 
179
207
  export interface VisualizerData {
@@ -608,6 +636,26 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null, basePath
608
636
  environmentIssues = runEnvironmentChecks(basePath).filter(r => r.status !== "ok");
609
637
  } catch { /* non-fatal */ }
610
638
 
639
+ // Doctor run history — persisted across sessions (sync read to keep loadHealth sync)
640
+ let doctorHistory: VisualizerDoctorEntry[] = [];
641
+ try {
642
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
643
+ if (existsSync(historyPath)) {
644
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
645
+ doctorHistory = lines.slice(-20).reverse().map(l => JSON.parse(l) as VisualizerDoctorEntry);
646
+ }
647
+ } catch { /* non-fatal */ }
648
+
649
+ // Current progress score — only meaningful when auto-mode has health data
650
+ let progressScore: VisualizerProgressScore | null = null;
651
+ try {
652
+ const history = getHealthHistory();
653
+ if (history.length > 0) {
654
+ const score = computeProgressScore();
655
+ progressScore = { level: score.level, summary: score.summary, signals: score.signals };
656
+ }
657
+ } catch { /* non-fatal */ }
658
+
611
659
  return {
612
660
  budgetCeiling,
613
661
  tokenProfile,
@@ -621,6 +669,8 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null, basePath
621
669
  providers,
622
670
  skillSummary,
623
671
  environmentIssues,
672
+ doctorHistory,
673
+ progressScore,
624
674
  };
625
675
  }
626
676
 
@@ -1150,6 +1150,64 @@ export function renderHealthView(
1150
1150
  }
1151
1151
  }
1152
1152
 
1153
+ // Progress score section — current traffic light status
1154
+ if (health.progressScore) {
1155
+ lines.push("");
1156
+ lines.push(th.fg("accent", th.bold("Progress Score")));
1157
+ lines.push("");
1158
+ const ps = health.progressScore;
1159
+ const scoreColor = ps.level === "green" ? "success" : ps.level === "yellow" ? "warning" : "error";
1160
+ const scoreIcon = ps.level === "green" ? "●" : ps.level === "yellow" ? "◐" : "○";
1161
+ lines.push(` ${th.fg(scoreColor, scoreIcon)} ${th.fg(scoreColor, ps.summary)}`);
1162
+ for (const signal of ps.signals) {
1163
+ const prefix = signal.kind === "positive" ? th.fg("success", " ✓")
1164
+ : signal.kind === "negative" ? th.fg("error", " ✗")
1165
+ : th.fg("dim", " ·");
1166
+ lines.push(` ${prefix} ${th.fg("dim", signal.label)}`);
1167
+ }
1168
+ }
1169
+
1170
+ // Doctor history section — persisted across sessions
1171
+ const doctorHistory = health.doctorHistory ?? [];
1172
+ if (doctorHistory.length > 0) {
1173
+ lines.push("");
1174
+ lines.push(th.fg("accent", th.bold("Doctor History")));
1175
+ lines.push("");
1176
+
1177
+ for (const entry of doctorHistory.slice(0, 10)) {
1178
+ const icon = entry.ok ? th.fg("success", "✓") : th.fg("error", "✗");
1179
+ const ts = entry.ts.replace("T", " ").slice(0, 19);
1180
+ const scopeTag = entry.scope ? th.fg("accent", ` [${entry.scope}]`) : "";
1181
+ // Prefer human-readable summary, fall back to counts
1182
+ const detail = entry.summary
1183
+ ? th.fg("text", entry.summary)
1184
+ : th.fg("text", `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`);
1185
+ lines.push(` ${icon} ${th.fg("dim", ts)}${scopeTag} ${detail}`);
1186
+
1187
+ // Show issue details if available
1188
+ if (entry.issues && entry.issues.length > 0) {
1189
+ for (const issue of entry.issues.slice(0, 3)) {
1190
+ const issuePfx = issue.severity === "error" ? th.fg("error", " ✗") : th.fg("warning", " ⚠");
1191
+ lines.push(` ${issuePfx} ${th.fg("dim", truncateToWidth(issue.message, width - 12))}`);
1192
+ }
1193
+ if (entry.issues.length > 3) {
1194
+ lines.push(` ${th.fg("dim", `+${entry.issues.length - 3} more`)}`);
1195
+ }
1196
+ }
1197
+
1198
+ // Show fixes if available
1199
+ if (entry.fixDescriptions && entry.fixDescriptions.length > 0) {
1200
+ for (const fix of entry.fixDescriptions.slice(0, 2)) {
1201
+ lines.push(` ${th.fg("success", "↳")} ${th.fg("dim", truncateToWidth(fix, width - 12))}`);
1202
+ }
1203
+ }
1204
+ }
1205
+
1206
+ if (doctorHistory.length > 10) {
1207
+ lines.push(` ${th.fg("dim", `...${doctorHistory.length - 10} older entries`)}`);
1208
+ }
1209
+ }
1210
+
1153
1211
  // Skills section
1154
1212
  if (health.skillSummary?.total > 0) {
1155
1213
  lines.push("");
@@ -67,40 +67,60 @@ export function captureIntegrationBranch(basePath: string, milestoneId: string,
67
67
 
68
68
  // ─── Pure Utility Functions (unchanged) ────────────────────────────────────
69
69
 
70
+ /**
71
+ * Find the worktrees segment in a path, supporting both direct
72
+ * (`/.gsd/worktrees/`) and symlink-resolved (`/.gsd/projects/<hash>/worktrees/`)
73
+ * layouts. When `.gsd` is a symlink to `~/.gsd/projects/<hash>`, resolved
74
+ * paths contain the intermediate `projects/<hash>/` segment that the old
75
+ * single-marker check missed.
76
+ */
77
+ function findWorktreeSegment(normalizedPath: string): { gsdIdx: number; afterWorktrees: number } | null {
78
+ // Direct layout: /.gsd/worktrees/<name>
79
+ const directMarker = "/.gsd/worktrees/";
80
+ const idx = normalizedPath.indexOf(directMarker);
81
+ if (idx !== -1) {
82
+ return { gsdIdx: idx, afterWorktrees: idx + directMarker.length };
83
+ }
84
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/<name>
85
+ const symlinkRe = /\/\.gsd\/projects\/[a-f0-9]+\/worktrees\//;
86
+ const match = normalizedPath.match(symlinkRe);
87
+ if (match && match.index !== undefined) {
88
+ return { gsdIdx: match.index, afterWorktrees: match.index + match[0].length };
89
+ }
90
+ return null;
91
+ }
92
+
70
93
  /**
71
94
  * Detect the active worktree name from the current working directory.
72
95
  * Returns null if not inside a GSD worktree (.gsd/worktrees/<name>/).
73
96
  */
74
97
  export function detectWorktreeName(basePath: string): string | null {
75
98
  const normalizedPath = basePath.replaceAll("\\", "/");
76
- const marker = "/.gsd/worktrees/";
77
- const idx = normalizedPath.indexOf(marker);
78
- if (idx === -1) return null;
79
- const afterMarker = normalizedPath.slice(idx + marker.length);
99
+ const seg = findWorktreeSegment(normalizedPath);
100
+ if (!seg) return null;
101
+ const afterMarker = normalizedPath.slice(seg.afterWorktrees);
80
102
  const name = afterMarker.split("/")[0];
81
103
  return name || null;
82
104
  }
83
105
 
84
106
  /**
85
107
  * Resolve the project root from a path that may be inside a worktree.
86
- * If the path contains `/.gsd/worktrees/<name>/`, returns the portion
87
- * before `/.gsd/`. Otherwise returns the input unchanged.
108
+ * If the path contains a worktrees segment, returns the portion before
109
+ * `/.gsd/`. Otherwise returns the input unchanged.
88
110
  *
89
111
  * Use this in commands that call `process.cwd()` to ensure they always
90
112
  * operate against the real project root, not a worktree subdirectory.
91
113
  */
92
114
  export function resolveProjectRoot(basePath: string): string {
93
115
  const normalizedPath = basePath.replaceAll("\\", "/");
94
- const marker = "/.gsd/worktrees/";
95
- const idx = normalizedPath.indexOf(marker);
96
- if (idx === -1) return basePath;
97
- // Return the original path up to the .gsd/ marker (un-normalized)
98
- // Account for potential OS-specific separators
116
+ const seg = findWorktreeSegment(normalizedPath);
117
+ if (!seg) return basePath;
118
+ // Return the original path up to the /.gsd/ boundary
99
119
  const sep = basePath.includes("\\") ? "\\" : "/";
100
- const markerOs = `${sep}.gsd${sep}worktrees${sep}`;
101
- const idxOs = basePath.indexOf(markerOs);
102
- if (idxOs !== -1) return basePath.slice(0, idxOs);
103
- return basePath.slice(0, idx);
120
+ const gsdMarker = `${sep}.gsd${sep}`;
121
+ const gsdIdx = basePath.indexOf(gsdMarker);
122
+ if (gsdIdx !== -1) return basePath.slice(0, gsdIdx);
123
+ return basePath.slice(0, seg.gsdIdx);
104
124
  }
105
125
 
106
126
  /**
@@ -114,6 +114,22 @@ function getServerConfig(name: string): McpServerConfig | undefined {
114
114
  return readConfigs().find((s) => s.name === name);
115
115
  }
116
116
 
117
+ /** Resolve ${VAR} references in env values against process.env. */
118
+ function resolveEnv(env: Record<string, string>): Record<string, string> {
119
+ const resolved: Record<string, string> = {};
120
+ for (const [key, value] of Object.entries(env)) {
121
+ if (typeof value === "string") {
122
+ resolved[key] = value.replace(
123
+ /\$\{([^}]+)\}/g,
124
+ (_match, varName) => process.env[varName] ?? "",
125
+ );
126
+ } else {
127
+ resolved[key] = value;
128
+ }
129
+ }
130
+ return resolved;
131
+ }
132
+
117
133
  async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client> {
118
134
  const existing = connections.get(name);
119
135
  if (existing) return existing.client;
@@ -128,7 +144,7 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client>
128
144
  transport = new StdioClientTransport({
129
145
  command: config.command,
130
146
  args: config.args,
131
- env: config.env ? { ...process.env, ...config.env } as Record<string, string> : undefined,
147
+ env: config.env ? { ...process.env, ...resolveEnv(config.env) } as Record<string, string> : undefined,
132
148
  cwd: config.cwd,
133
149
  stderr: "pipe",
134
150
  });
@@ -7,6 +7,10 @@ import { join } from "node:path";
7
7
  import { homedir } from "node:os";
8
8
  import { readPromptRecord } from "./store.js";
9
9
 
10
+ function getGsdHome(): string {
11
+ return process.env.GSD_HOME || join(homedir(), ".gsd");
12
+ }
13
+
10
14
  export interface LatestPromptSummary {
11
15
  id: string;
12
16
  status: string;
@@ -14,7 +18,7 @@ export interface LatestPromptSummary {
14
18
  }
15
19
 
16
20
  export function getLatestPromptSummary(): LatestPromptSummary | null {
17
- const runtimeDir = join(homedir(), ".gsd", "runtime", "remote-questions");
21
+ const runtimeDir = join(getGsdHome(), "runtime", "remote-questions");
18
22
  if (!existsSync(runtimeDir)) return null;
19
23
  const files = readdirSync(runtimeDir).filter((f) => f.endsWith(".json"));
20
24
  if (files.length === 0) return null;
@@ -7,8 +7,12 @@ import { join } from "node:path";
7
7
  import { homedir } from "node:os";
8
8
  import type { RemotePrompt, RemotePromptRecord, RemotePromptRef, RemoteAnswer, RemotePromptStatus } from "./types.js";
9
9
 
10
+ function getGsdHome(): string {
11
+ return process.env.GSD_HOME || join(homedir(), ".gsd");
12
+ }
13
+
10
14
  function runtimeDir(): string {
11
- return join(homedir(), ".gsd", "runtime", "remote-questions");
15
+ return join(getGsdHome(), "runtime", "remote-questions");
12
16
  }
13
17
 
14
18
  function recordPath(id: string): string {
@@ -17,7 +17,8 @@ import { resolveSearchProviderFromPreferences } from '../gsd/preferences.js'
17
17
  // Compute authFilePath locally instead of importing from app-paths.ts,
18
18
  // because extensions are copied to ~/.gsd/agent/extensions/ at runtime
19
19
  // where the relative import '../../../app-paths.ts' doesn't resolve.
20
- const authFilePath = join(homedir(), '.gsd', 'agent', 'auth.json')
20
+ const gsdHome = process.env.GSD_HOME || join(homedir(), '.gsd')
21
+ const authFilePath = join(gsdHome, 'agent', 'auth.json')
21
22
 
22
23
  export type SearchProvider = 'tavily' | 'brave' | 'ollama'
23
24
  export type SearchProviderPreference = SearchProvider | 'auto'
@@ -50,7 +50,7 @@ export function parseFrontmatterMap(lines: string[]): Record<string, unknown> {
50
50
  }
51
51
 
52
52
  // Array item (2-space indent)
53
- const arrayMatch = line.match(/^ - (.*)$/);
53
+ const arrayMatch = line.match(/^ - ?(.*)$/);
54
54
  if (arrayMatch && currentKey) {
55
55
  // If there's a pending nested object, push it
56
56
  if (currentObj && Object.keys(currentObj).length > 0) {
@@ -452,7 +452,7 @@ async function runSingleAgent(
452
452
 
453
453
  async function runSingleAgentInCmuxSplit(
454
454
  cmuxClient: CmuxClient,
455
- direction: "right" | "down",
455
+ directionOrSurfaceId: "right" | "down" | string,
456
456
  defaultCwd: string,
457
457
  agents: AgentConfig[],
458
458
  agentName: string,
@@ -503,7 +503,12 @@ async function runSingleAgentInCmuxSplit(
503
503
  const stdoutPath = path.join(tmpOutputDir, "stdout.jsonl");
504
504
  const stderrPath = path.join(tmpOutputDir, "stderr.log");
505
505
  const exitPath = path.join(tmpOutputDir, "exit.code");
506
- const cmuxSurfaceId = await cmuxClient.createSplit(direction);
506
+ // Accept either a pre-created surface ID or a direction to create a new split
507
+ const isDirection = directionOrSurfaceId === "right" || directionOrSurfaceId === "down"
508
+ || directionOrSurfaceId === "left" || directionOrSurfaceId === "up";
509
+ const cmuxSurfaceId = isDirection
510
+ ? await cmuxClient.createSplit(directionOrSurfaceId as "right" | "down" | "left" | "up")
511
+ : directionOrSurfaceId;
507
512
  if (!cmuxSurfaceId) {
508
513
  return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
509
514
  }
@@ -806,12 +811,16 @@ export default function (pi: ExtensionAPI) {
806
811
  const MAX_RETRIES = 1; // Retry failed tasks once
807
812
  const batchId = crypto.randomUUID();
808
813
  const batchSize = params.tasks.length;
814
+ // Pre-create a grid layout for cmux splits so agents get a clean tiled arrangement
815
+ const gridSurfaces = cmuxSplitsEnabled
816
+ ? await cmuxClient.createGridLayout(Math.min(batchSize, MAX_CONCURRENCY))
817
+ : [];
809
818
  const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
810
819
  const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
811
820
  const runTask = () => cmuxSplitsEnabled
812
821
  ? runSingleAgentInCmuxSplit(
813
822
  cmuxClient,
814
- index % 2 === 0 ? "right" : "down",
823
+ gridSurfaces[index] ?? (index % 2 === 0 ? "right" : "down"),
815
824
  ctx.cwd,
816
825
  agents,
817
826
  t.agent,