gsd-pi 2.41.0 → 2.42.0-dev.1df898f

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 (494) hide show
  1. package/README.md +92 -29
  2. package/dist/cli-web-branch.d.ts +6 -0
  3. package/dist/cli-web-branch.js +17 -0
  4. package/dist/cli.js +18 -3
  5. package/dist/loader.js +3 -1
  6. package/dist/onboarding.js +2 -1
  7. package/dist/resource-loader.js +39 -6
  8. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  9. package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
  10. package/dist/resources/extensions/async-jobs/index.js +2 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +89 -1
  12. package/dist/resources/extensions/gsd/auto/phases.js +29 -13
  13. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  14. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -2
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
  16. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
  17. package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
  18. package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
  19. package/dist/resources/extensions/gsd/auto-start.js +16 -14
  20. package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
  21. package/dist/resources/extensions/gsd/auto.js +64 -2
  22. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
  23. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +25 -3
  26. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  27. package/dist/resources/extensions/gsd/commands/catalog.js +40 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  29. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  30. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  31. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  32. package/dist/resources/extensions/gsd/context-store.js +4 -3
  33. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  34. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  35. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  36. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  37. package/dist/resources/extensions/gsd/db-writer.js +5 -2
  38. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  39. package/dist/resources/extensions/gsd/detection.js +20 -1
  40. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  41. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  42. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  43. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  44. package/dist/resources/extensions/gsd/doctor.js +11 -1
  45. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  46. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  47. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  48. package/dist/resources/extensions/gsd/exit-command.js +12 -2
  49. package/dist/resources/extensions/gsd/export.js +9 -13
  50. package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
  51. package/dist/resources/extensions/gsd/files.js +28 -11
  52. package/dist/resources/extensions/gsd/forensics.js +94 -3
  53. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  54. package/dist/resources/extensions/gsd/git-service.js +6 -2
  55. package/dist/resources/extensions/gsd/graph.js +225 -0
  56. package/dist/resources/extensions/gsd/gsd-db.js +25 -8
  57. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  58. package/dist/resources/extensions/gsd/guided-flow.js +7 -3
  59. package/dist/resources/extensions/gsd/journal.js +85 -0
  60. package/dist/resources/extensions/gsd/md-importer.js +5 -0
  61. package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
  62. package/dist/resources/extensions/gsd/native-git-bridge.js +3 -2
  63. package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
  64. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  65. package/dist/resources/extensions/gsd/preferences.js +60 -8
  66. package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
  67. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  68. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  69. package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  71. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  72. package/dist/resources/extensions/gsd/repo-identity.js +92 -7
  73. package/dist/resources/extensions/gsd/rule-registry.js +489 -0
  74. package/dist/resources/extensions/gsd/rule-types.js +6 -0
  75. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  76. package/dist/resources/extensions/gsd/service-tier.js +147 -0
  77. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  78. package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
  79. package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
  80. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  81. package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
  82. package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
  83. package/dist/resources/extensions/gsd/worktree-resolver.js +21 -4
  84. package/dist/resources/extensions/gsd/worktree.js +2 -2
  85. package/dist/resources/extensions/mcp-client/index.js +2 -1
  86. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  87. package/dist/resources/extensions/subagent/index.js +7 -3
  88. package/dist/resources/extensions/voice/index.js +4 -4
  89. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  90. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  91. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  92. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  93. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  94. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  95. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  96. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  97. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  98. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  99. package/dist/web/standalone/.next/BUILD_ID +1 -1
  100. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  101. package/dist/web/standalone/.next/build-manifest.json +4 -4
  102. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  103. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  104. package/dist/web/standalone/.next/required-server-files.json +3 -3
  105. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  106. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  108. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  116. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +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 +3 -3
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  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 +3 -3
  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_client-reference-manifest.js +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_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  158. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  164. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  170. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  176. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  180. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  190. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  192. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  194. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  200. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  202. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  203. package/dist/web/standalone/.next/server/app/index.html +1 -1
  204. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  205. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  206. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  207. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  208. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  209. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  210. package/dist/web/standalone/.next/server/app/page.js +2 -2
  211. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  212. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  213. package/dist/web/standalone/.next/server/chunks/229.js +3 -3
  214. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  215. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  217. package/dist/web/standalone/.next/server/middleware.js +2 -2
  218. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  219. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  220. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  221. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  222. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  223. package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
  224. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
  225. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
  226. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
  227. package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
  228. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  229. package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
  230. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  231. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  232. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  233. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  234. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  235. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  236. package/dist/web/standalone/server.js +1 -1
  237. package/dist/web-mode.d.ts +4 -0
  238. package/dist/web-mode.js +69 -11
  239. package/package.json +1 -1
  240. package/packages/native/src/__tests__/text.test.mjs +33 -0
  241. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  242. package/packages/pi-agent-core/dist/agent.js +2 -0
  243. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  244. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  245. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  246. package/packages/pi-agent-core/dist/types.js.map +1 -1
  247. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  248. package/packages/pi-agent-core/src/agent.ts +3 -0
  249. package/packages/pi-agent-core/src/types.ts +6 -0
  250. package/packages/pi-agent-core/tsconfig.json +1 -1
  251. package/packages/pi-ai/dist/models.d.ts +5 -3
  252. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  253. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  254. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  255. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  256. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  257. package/packages/pi-ai/dist/models.js.map +1 -1
  258. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  259. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  260. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  261. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  262. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  263. package/packages/pi-ai/src/models.ts +7 -4
  264. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  265. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  267. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  269. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  270. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  271. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  272. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  273. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  274. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
  275. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  276. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  277. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  278. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  280. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  281. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  282. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  284. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  286. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  288. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  290. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  291. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  292. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  294. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  295. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  297. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  298. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  299. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  300. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  301. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  303. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
  304. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  305. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  306. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +34 -10
  307. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  308. package/packages/pi-coding-agent/package.json +1 -1
  309. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  310. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  311. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  312. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
  313. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  314. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  315. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  316. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  317. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  318. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  319. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  320. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  321. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
  322. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +36 -11
  323. package/pkg/package.json +1 -1
  324. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  325. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  326. package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
  327. package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
  328. package/src/resources/extensions/async-jobs/index.ts +1 -0
  329. package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
  330. package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -2
  331. package/src/resources/extensions/gsd/auto/loop.ts +101 -1
  332. package/src/resources/extensions/gsd/auto/phases.ts +31 -13
  333. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  334. package/src/resources/extensions/gsd/auto/types.ts +4 -0
  335. package/src/resources/extensions/gsd/auto-dashboard.ts +9 -2
  336. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
  337. package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
  338. package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
  339. package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
  340. package/src/resources/extensions/gsd/auto-start.ts +15 -13
  341. package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
  342. package/src/resources/extensions/gsd/auto.ts +71 -2
  343. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
  344. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
  345. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  346. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +25 -4
  347. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  348. package/src/resources/extensions/gsd/commands/catalog.ts +40 -1
  349. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  350. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  351. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  352. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  353. package/src/resources/extensions/gsd/context-store.ts +4 -3
  354. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  355. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  356. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  357. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  358. package/src/resources/extensions/gsd/db-writer.ts +6 -2
  359. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  360. package/src/resources/extensions/gsd/detection.ts +20 -1
  361. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  362. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  363. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  364. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  365. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  366. package/src/resources/extensions/gsd/doctor.ts +12 -1
  367. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  368. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  369. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  370. package/src/resources/extensions/gsd/exit-command.ts +14 -2
  371. package/src/resources/extensions/gsd/export.ts +8 -15
  372. package/src/resources/extensions/gsd/extension-manifest.json +2 -2
  373. package/src/resources/extensions/gsd/files.ts +29 -12
  374. package/src/resources/extensions/gsd/forensics.ts +101 -3
  375. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  376. package/src/resources/extensions/gsd/git-service.ts +5 -5
  377. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  378. package/src/resources/extensions/gsd/graph.ts +312 -0
  379. package/src/resources/extensions/gsd/gsd-db.ts +37 -8
  380. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  381. package/src/resources/extensions/gsd/guided-flow.ts +7 -3
  382. package/src/resources/extensions/gsd/journal.ts +134 -0
  383. package/src/resources/extensions/gsd/md-importer.ts +6 -0
  384. package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
  385. package/src/resources/extensions/gsd/native-git-bridge.ts +3 -2
  386. package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
  387. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  388. package/src/resources/extensions/gsd/preferences.ts +63 -6
  389. package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
  390. package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  391. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  392. package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
  393. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  394. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  395. package/src/resources/extensions/gsd/repo-identity.ts +95 -7
  396. package/src/resources/extensions/gsd/rule-registry.ts +599 -0
  397. package/src/resources/extensions/gsd/rule-types.ts +68 -0
  398. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  399. package/src/resources/extensions/gsd/service-tier.ts +184 -0
  400. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  401. package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
  402. package/src/resources/extensions/gsd/templates/decisions.md +2 -2
  403. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  404. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
  405. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
  406. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
  407. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
  408. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  409. package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
  410. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  411. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  412. package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
  413. package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
  414. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  415. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  416. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  417. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  418. package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
  419. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  420. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  421. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
  422. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
  423. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
  424. package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
  425. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  426. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  427. package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
  428. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  429. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  430. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  431. package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
  432. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  433. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
  434. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
  435. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  436. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
  437. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
  438. package/src/resources/extensions/gsd/tests/journal.test.ts +341 -0
  439. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  440. package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
  441. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  442. package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
  443. package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
  444. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
  445. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
  446. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
  447. package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
  448. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
  449. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  450. package/src/resources/extensions/gsd/tests/service-tier.test.ts +127 -0
  451. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
  452. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
  453. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
  454. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
  455. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  456. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  457. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
  458. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
  459. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  460. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
  461. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
  462. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
  463. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +135 -0
  464. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +203 -106
  465. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +79 -5
  466. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
  467. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
  468. package/src/resources/extensions/gsd/types.ts +3 -0
  469. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  470. package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
  471. package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
  472. package/src/resources/extensions/gsd/worktree-resolver.ts +32 -12
  473. package/src/resources/extensions/gsd/worktree.ts +2 -2
  474. package/src/resources/extensions/mcp-client/index.ts +5 -1
  475. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  476. package/src/resources/extensions/subagent/index.ts +7 -3
  477. package/src/resources/extensions/voice/index.ts +4 -4
  478. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  479. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  480. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  481. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  482. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  483. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  484. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  485. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  486. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  487. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  488. package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
  489. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
  490. package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
  491. package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
  492. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
  493. /package/dist/web/standalone/.next/static/{Ute3pMouVczQyT15qrBBO → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
  494. /package/dist/web/standalone/.next/static/{Ute3pMouVczQyT15qrBBO → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
@@ -52,7 +52,7 @@ function makeDeps(
52
52
  fn: "mergeMilestoneToMain",
53
53
  args: [basePath, milestoneId, roadmapContent],
54
54
  });
55
- return { pushed: false };
55
+ return { pushed: false, codeFilesChanged: true };
56
56
  },
57
57
  syncWorktreeStateBack: (
58
58
  mainBasePath: string,
@@ -139,11 +139,10 @@ function makeDeps(
139
139
  captureIntegrationBranch: (
140
140
  basePath: string,
141
141
  mid: string | undefined,
142
- opts?: { commitDocs?: boolean },
143
142
  ) => {
144
143
  calls.push({
145
144
  fn: "captureIntegrationBranch",
146
- args: [basePath, mid, opts],
145
+ args: [basePath, mid],
147
146
  });
148
147
  },
149
148
  ...overrides,
@@ -424,7 +423,7 @@ test("mergeAndExit in worktree mode shows pushed status", () => {
424
423
  const deps = makeDeps({
425
424
  isInAutoWorktree: () => true,
426
425
  getIsolationMode: () => "worktree",
427
- mergeMilestoneToMain: () => ({ pushed: true }),
426
+ mergeMilestoneToMain: () => ({ pushed: true, codeFilesChanged: true }),
428
427
  });
429
428
  const ctx = makeNotifyCtx();
430
429
  const resolver = new WorktreeResolver(s, deps);
@@ -659,6 +658,81 @@ test("mergeAndExit in none mode is a no-op", () => {
659
658
  assert.equal(ctx.messages.length, 0);
660
659
  });
661
660
 
661
+ // ─── #1906 — metadata-only merge warning ────────────────────────────────────
662
+
663
+ test("mergeAndExit warns when merge contains no code changes (#1906)", () => {
664
+ const s = makeSession({
665
+ basePath: "/project/.gsd/worktrees/M001",
666
+ originalBasePath: "/project",
667
+ });
668
+ const deps = makeDeps({
669
+ isInAutoWorktree: () => true,
670
+ getIsolationMode: () => "worktree",
671
+ mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
672
+ });
673
+ const ctx = makeNotifyCtx();
674
+ const resolver = new WorktreeResolver(s, deps);
675
+
676
+ resolver.mergeAndExit("M001", ctx);
677
+
678
+ assert.ok(
679
+ ctx.messages.some((m) => m.msg.includes("NO code changes") && m.level === "warning"),
680
+ "must emit warning when only .gsd/ metadata was merged",
681
+ );
682
+ assert.ok(
683
+ !ctx.messages.some((m) => m.msg.includes("merged to main") && m.level === "info"),
684
+ "must NOT emit success-style info notification for metadata-only merge",
685
+ );
686
+ });
687
+
688
+ test("mergeAndExit emits info when merge contains code changes (#1906)", () => {
689
+ const s = makeSession({
690
+ basePath: "/project/.gsd/worktrees/M001",
691
+ originalBasePath: "/project",
692
+ });
693
+ const deps = makeDeps({
694
+ isInAutoWorktree: () => true,
695
+ getIsolationMode: () => "worktree",
696
+ mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
697
+ });
698
+ const ctx = makeNotifyCtx();
699
+ const resolver = new WorktreeResolver(s, deps);
700
+
701
+ resolver.mergeAndExit("M001", ctx);
702
+
703
+ assert.ok(
704
+ ctx.messages.some((m) => m.msg.includes("merged to main") && m.level === "info"),
705
+ "must emit info notification when code files were merged",
706
+ );
707
+ assert.ok(
708
+ !ctx.messages.some((m) => m.msg.includes("NO code changes")),
709
+ "must NOT emit metadata-only warning when code files were merged",
710
+ );
711
+ });
712
+
713
+ test("mergeAndExit branch mode warns when merge contains no code changes (#1906)", () => {
714
+ const s = makeSession({
715
+ basePath: "/project",
716
+ originalBasePath: "/project",
717
+ });
718
+ const deps = makeDeps({
719
+ isInAutoWorktree: () => false,
720
+ getIsolationMode: () => "branch",
721
+ getCurrentBranch: () => "milestone/M001",
722
+ autoWorktreeBranch: () => "milestone/M001",
723
+ mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
724
+ });
725
+ const ctx = makeNotifyCtx();
726
+ const resolver = new WorktreeResolver(s, deps);
727
+
728
+ resolver.mergeAndExit("M001", ctx);
729
+
730
+ assert.ok(
731
+ ctx.messages.some((m) => m.msg.includes("NO code changes") && m.level === "warning"),
732
+ "branch mode must emit warning when only .gsd/ metadata was merged",
733
+ );
734
+ });
735
+
662
736
  // ─── mergeAndEnterNext Tests ─────────────────────────────────────────────────
663
737
 
664
738
  test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
@@ -677,7 +751,7 @@ test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
677
751
  _roadmap: string,
678
752
  ) => {
679
753
  callOrder.push(`merge:${milestoneId}`);
680
- return { pushed: false };
754
+ return { pushed: false, codeFilesChanged: true };
681
755
  },
682
756
  getAutoWorktreePath: () => null,
683
757
  createAutoWorktree: (basePath: string, milestoneId: string) => {
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Regression test for #1852: removeWorktree targets wrong path when .gsd/ is a symlink.
3
+ *
4
+ * When .gsd/ is a symlink to an external state directory, git registers
5
+ * the worktree at the resolved (real) path. But removeWorktree recomputes
6
+ * the path via worktreePath() which uses the unresolved symlink, causing
7
+ * a mismatch — the removal silently fails.
8
+ *
9
+ * Fix: removeWorktree should query `git worktree list` to find the actual
10
+ * registered path when the computed path doesn't match.
11
+ */
12
+ import { mkdtempSync, mkdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync, existsSync, realpathSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+ import { execSync } from "node:child_process";
16
+
17
+ import {
18
+ createWorktree,
19
+ removeWorktree,
20
+ listWorktrees,
21
+ worktreePath,
22
+ } from "../worktree-manager.ts";
23
+ import { createTestContext } from './test-helpers.ts';
24
+
25
+ const { assertEq, assertTrue, report } = createTestContext();
26
+
27
+ function run(command: string, cwd: string): string {
28
+ return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
29
+ }
30
+
31
+ // Set up a test repo with .gsd/ as a symlink to an external directory,
32
+ // mimicking the external state directory layout (~/.gsd/projects/<hash>/).
33
+ // Resolve tmpdir to handle macOS /tmp -> /private/var/... symlink.
34
+ const realTmp = realpathSync(tmpdir());
35
+ const base = mkdtempSync(join(realTmp, "gsd-wt-symlink-test-"));
36
+ const externalState = mkdtempSync(join(realTmp, "gsd-wt-symlink-ext-"));
37
+
38
+ run("git init -b main", base);
39
+ run('git config user.name "Test"', base);
40
+ run('git config user.email "test@example.com"', base);
41
+
42
+ // Create external state directory structure
43
+ mkdirSync(join(externalState, "worktrees"), { recursive: true });
44
+
45
+ // Create .gsd as a symlink to the external state directory
46
+ symlinkSync(externalState, join(base, ".gsd"));
47
+
48
+ // Verify the symlink is in place
49
+ assertTrue(existsSync(join(base, ".gsd")), ".gsd symlink exists");
50
+ assertTrue(
51
+ realpathSync(join(base, ".gsd")) === externalState,
52
+ ".gsd resolves to external state dir",
53
+ );
54
+
55
+ // Create initial commit so we have a valid repo
56
+ writeFileSync(join(base, "README.md"), "# Test\n", "utf-8");
57
+ run("git add .", base);
58
+ run('git commit -m "init"', base);
59
+
60
+ async function main(): Promise<void> {
61
+ console.log("\n=== #1852: removeWorktree with symlinked .gsd/ ===");
62
+
63
+ // Create a worktree — git will resolve the symlink and register
64
+ // the worktree at the external path
65
+ const info = createWorktree(base, "M002", { branch: "milestone/M002" });
66
+ assertTrue(info.exists, "worktree created");
67
+
68
+ // Verify worktree was created at the resolved (external) path
69
+ const realWtPath = realpathSync(info.path);
70
+ assertTrue(
71
+ realWtPath.startsWith(externalState),
72
+ `worktree real path (${realWtPath}) is under external state dir`,
73
+ );
74
+
75
+ // Verify git registered the worktree
76
+ const gitList = run("git worktree list", base);
77
+ assertTrue(gitList.includes("M002"), "git worktree list shows M002");
78
+
79
+ // The computed path via worktreePath uses the symlink path
80
+ const computedPath = worktreePath(base, "M002");
81
+ assertTrue(existsSync(computedPath), "computed path exists (via symlink)");
82
+
83
+ // Simulate what syncStateToProjectRoot does: replace the .gsd symlink with
84
+ // a real directory containing stale worktree data. This causes worktreePath()
85
+ // to compute a LOCAL path that differs from git's REGISTERED path (the
86
+ // resolved external path). The stale local dir passes existsSync but is not
87
+ // a real git worktree, so nativeWorktreeRemove fails silently.
88
+ unlinkSync(join(base, ".gsd")); // remove the symlink
89
+ mkdirSync(join(base, ".gsd", "worktrees", "M002"), { recursive: true });
90
+ // Write a dummy file so the stale directory is non-empty
91
+ writeFileSync(join(base, ".gsd", "worktrees", "M002", "stale.txt"), "stale sync artifact", "utf-8");
92
+
93
+ // Now worktreePath(base, "M002") points to the LOCAL stale dir, not the
94
+ // external path where git actually registered the worktree.
95
+ const stalePath = worktreePath(base, "M002");
96
+ assertTrue(existsSync(stalePath), "stale local worktree dir exists");
97
+ assertTrue(
98
+ stalePath !== realWtPath,
99
+ `computed path (${stalePath}) differs from git-registered path (${realWtPath})`,
100
+ );
101
+
102
+ // THE ACTUAL TEST: removeWorktree must find the git-registered path and
103
+ // remove the real worktree, not just operate on the stale local directory.
104
+ removeWorktree(base, "M002", { branch: "milestone/M002", deleteBranch: true });
105
+
106
+ // After removal, the worktree should be gone from git's list
107
+ const gitListAfter = run("git worktree list", base);
108
+ assertTrue(
109
+ !gitListAfter.includes("M002"),
110
+ "worktree removed from git worktree list after removeWorktree",
111
+ );
112
+
113
+ // The branch should be deleted
114
+ const branches = run("git branch", base);
115
+ assertTrue(
116
+ !branches.includes("milestone/M002"),
117
+ "milestone/M002 branch deleted after removeWorktree",
118
+ );
119
+
120
+ // The worktree directory should be gone
121
+ assertTrue(
122
+ !existsSync(realWtPath),
123
+ "worktree directory removed from disk",
124
+ );
125
+
126
+ // List should be empty
127
+ const listed = listWorktrees(base);
128
+ assertEq(listed.length, 0, "no worktrees listed after removal");
129
+
130
+ // Cleanup
131
+ rmSync(base, { recursive: true, force: true });
132
+ rmSync(externalState, { recursive: true, force: true });
133
+
134
+ report();
135
+ }
136
+
137
+ main().catch((error) => {
138
+ console.error(error);
139
+ process.exit(1);
140
+ });
@@ -19,6 +19,8 @@
19
19
  * - syncWorktreeStateBack syncs root-level .gsd/ files (REQUIREMENTS, PROJECT, etc.)
20
20
  * - syncWorktreeStateBack syncs ALL milestone directories, not just the current one
21
21
  * - syncWorktreeStateBack handles next-milestone artifacts created during completion
22
+ * - syncGsdStateToWorktree syncs non-standard milestone dir names (#1547)
23
+ * - syncWorktreeStateBack syncs non-standard milestone dir names (#1547)
22
24
  */
23
25
 
24
26
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
@@ -517,6 +519,78 @@ async function main(): Promise<void> {
517
519
  }
518
520
  }
519
521
 
522
+ // ─── 14. syncGsdStateToWorktree syncs non-standard milestone dir names (#1547) ──
523
+ console.log('\n=== 14. syncGsdStateToWorktree syncs non-standard milestone dir names (#1547) ===');
524
+ {
525
+ const mainBase = createBase('main');
526
+ const wtBase = createBase('wt');
527
+
528
+ try {
529
+ // Main has milestone dirs with non-standard names
530
+ const customDir = join(mainBase, '.gsd', 'milestones', 'sprint-alpha');
531
+ mkdirSync(customDir, { recursive: true });
532
+ writeFileSync(join(customDir, 'CONTEXT.md'), '# Sprint Alpha Context');
533
+
534
+ const suffixDir = join(mainBase, '.gsd', 'milestones', 'M001-abc123');
535
+ mkdirSync(suffixDir, { recursive: true });
536
+ writeFileSync(join(suffixDir, 'M001-abc123-CONTEXT.md'), '# M001 Context');
537
+
538
+ assertTrue(!existsSync(join(wtBase, '.gsd', 'milestones', 'sprint-alpha')), 'sprint-alpha missing before sync');
539
+ assertTrue(!existsSync(join(wtBase, '.gsd', 'milestones', 'M001-abc123')), 'M001-abc123 missing before sync');
540
+
541
+ const result = syncGsdStateToWorktree(mainBase, wtBase);
542
+
543
+ assertTrue(
544
+ existsSync(join(wtBase, '.gsd', 'milestones', 'sprint-alpha', 'CONTEXT.md')),
545
+ '#1547: non-standard milestone dir "sprint-alpha" synced to worktree',
546
+ );
547
+ assertTrue(
548
+ existsSync(join(wtBase, '.gsd', 'milestones', 'M001-abc123', 'M001-abc123-CONTEXT.md')),
549
+ '#1547: suffixed milestone dir "M001-abc123" synced to worktree',
550
+ );
551
+ assertTrue(result.synced.length > 0, 'sync reported files');
552
+ } finally {
553
+ cleanup(mainBase);
554
+ cleanup(wtBase);
555
+ }
556
+ }
557
+
558
+ // ─── 15. syncWorktreeStateBack syncs non-standard milestone dir names (#1547) ──
559
+ console.log('\n=== 15. syncWorktreeStateBack syncs non-standard milestone dir names (#1547) ===');
560
+ {
561
+ const mainBase = mkdtempSync(join(tmpdir(), 'gsd-wt-back-custom-main-'));
562
+ const wtBase = mkdtempSync(join(tmpdir(), 'gsd-wt-back-custom-wt-'));
563
+
564
+ try {
565
+ mkdirSync(join(mainBase, '.gsd', 'milestones'), { recursive: true });
566
+ mkdirSync(join(wtBase, '.gsd', 'milestones'), { recursive: true });
567
+
568
+ // Worktree has a non-standard milestone dir
569
+ const wtCustomDir = join(wtBase, '.gsd', 'milestones', 'sprint-beta');
570
+ mkdirSync(wtCustomDir, { recursive: true });
571
+ writeFileSync(join(wtCustomDir, 'SUMMARY.md'), '# Sprint Beta Summary');
572
+
573
+ assertTrue(
574
+ !existsSync(join(mainBase, '.gsd', 'milestones', 'sprint-beta')),
575
+ 'sprint-beta missing in main before sync',
576
+ );
577
+
578
+ const { synced } = syncWorktreeStateBack(mainBase, wtBase, 'M001');
579
+
580
+ assertTrue(
581
+ existsSync(join(mainBase, '.gsd', 'milestones', 'sprint-beta', 'SUMMARY.md')),
582
+ '#1547: non-standard milestone dir "sprint-beta" synced back to main',
583
+ );
584
+ assertTrue(
585
+ synced.some((p) => p.includes('sprint-beta')),
586
+ '#1547: sprint-beta appears in synced list',
587
+ );
588
+ } finally {
589
+ rmSync(mainBase, { recursive: true, force: true });
590
+ rmSync(wtBase, { recursive: true, force: true });
591
+ }
592
+ }
593
+
520
594
  report();
521
595
  }
522
596
 
@@ -404,6 +404,8 @@ export interface HookStatusEntry {
404
404
 
405
405
  // ─── Database Types (Decisions & Requirements) ────────────────────────────
406
406
 
407
+ export type DecisionMadeBy = "human" | "agent" | "collaborative";
408
+
407
409
  export interface Decision {
408
410
  seq: number; // auto-increment primary key
409
411
  id: string; // e.g. "D001"
@@ -413,6 +415,7 @@ export interface Decision {
413
415
  choice: string; // the specific choice made
414
416
  rationale: string; // why this choice
415
417
  revisable: string; // whether/when revisable
418
+ made_by: DecisionMadeBy; // who made the decision: human, agent, or collaborative
416
419
  superseded_by: string | null; // ID of superseding decision, or null
417
420
  }
418
421
 
@@ -0,0 +1,38 @@
1
+ /**
2
+ * workflow-engine.ts — WorkflowEngine interface.
3
+ *
4
+ * Defines the contract every engine implementation must satisfy.
5
+ * Imports only from the leaf-node engine-types.
6
+ */
7
+
8
+ import type {
9
+ EngineState,
10
+ EngineDispatchAction,
11
+ CompletedStep,
12
+ ReconcileResult,
13
+ DisplayMetadata,
14
+ } from "./engine-types.js";
15
+
16
+ /** A pluggable workflow engine that drives the auto-loop. */
17
+ export interface WorkflowEngine {
18
+ /** Unique identifier for this engine (e.g. "dev", "custom"). */
19
+ readonly engineId: string;
20
+
21
+ /** Derive the current engine state from the project on disk. */
22
+ deriveState(basePath: string): Promise<EngineState>;
23
+
24
+ /** Decide what the loop should do next given current state. */
25
+ resolveDispatch(
26
+ state: EngineState,
27
+ context: { basePath: string },
28
+ ): Promise<EngineDispatchAction>;
29
+
30
+ /** Reconcile state after a step has been executed. */
31
+ reconcile(
32
+ state: EngineState,
33
+ completedStep: CompletedStep,
34
+ ): Promise<ReconcileResult>;
35
+
36
+ /** Return UI-facing metadata for progress display. */
37
+ getDisplayMetadata(state: EngineState): DisplayMetadata;
38
+ }
@@ -8,10 +8,21 @@
8
8
  import { readFileSync, existsSync } from "node:fs";
9
9
  import { join, dirname } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
+ import { homedir } from "node:os";
11
12
 
12
- const __extensionDir = dirname(fileURLToPath(import.meta.url));
13
+ const __extensionDir = resolveGsdExtensionDir();
13
14
  const registryPath = join(__extensionDir, "workflow-templates", "registry.json");
14
15
 
16
+ /** Resolve the GSD extension dir with fallback to ~/.gsd/agent/extensions/gsd/. */
17
+ function resolveGsdExtensionDir(): string {
18
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
19
+ if (existsSync(join(moduleDir, "workflow-templates"))) return moduleDir;
20
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
21
+ const agentGsdDir = join(gsdHome, "agent", "extensions", "gsd");
22
+ if (existsSync(join(agentGsdDir, "workflow-templates"))) return agentGsdDir;
23
+ return moduleDir;
24
+ }
25
+
15
26
  // ─── Types ───────────────────────────────────────────────────────────────────
16
27
 
17
28
  export interface TemplateEntry {
@@ -286,11 +286,26 @@ export function removeWorktree(
286
286
  name: string,
287
287
  opts: { deleteBranch?: boolean; force?: boolean; branch?: string } = {},
288
288
  ): void {
289
- const wtPath = worktreePath(basePath, name);
290
- const resolvedWtPath = existsSync(wtPath) ? realpathSync(wtPath) : wtPath;
289
+ let wtPath = worktreePath(basePath, name);
291
290
  const branch = opts.branch ?? worktreeBranchName(name);
292
291
  const { deleteBranch = true, force = true } = opts;
293
292
 
293
+ // Resolve the ACTUAL worktree path from git's worktree list.
294
+ // The computed path may differ when .gsd/ is (or was) a symlink to an
295
+ // external state directory — git resolves symlinks at worktree creation
296
+ // time, so its registered path points to the resolved external location.
297
+ // If syncStateToProjectRoot later creates a real .gsd/ directory that
298
+ // shadows the symlink, the computed path diverges from git's record.
299
+ try {
300
+ const entries = nativeWorktreeList(basePath);
301
+ const entry = entries.find(e => e.branch === branch);
302
+ if (entry?.path) {
303
+ wtPath = entry.path;
304
+ }
305
+ } catch { /* fall back to computed path */ }
306
+
307
+ const resolvedWtPath = existsSync(wtPath) ? realpathSync(wtPath) : wtPath;
308
+
294
309
  // If we're inside the worktree, move out first — git can't remove an in-use directory
295
310
  const cwd = process.cwd();
296
311
  const resolvedCwd = existsSync(cwd) ? realpathSync(cwd) : cwd;
@@ -306,12 +321,12 @@ export function removeWorktree(
306
321
  return;
307
322
  }
308
323
 
309
- // Remove worktree (force if requested, to handle dirty worktrees)
310
- try { nativeWorktreeRemove(basePath, wtPath, force); } catch { /* may fail */ }
324
+ // Remove worktree using the resolved path (force if requested, to handle dirty worktrees)
325
+ try { nativeWorktreeRemove(basePath, resolvedWtPath, force); } catch { /* may fail */ }
311
326
 
312
327
  // If the directory is still there (e.g. locked), try harder with force
313
- if (existsSync(wtPath)) {
314
- try { nativeWorktreeRemove(basePath, wtPath, true); } catch { /* may fail */ }
328
+ if (existsSync(resolvedWtPath)) {
329
+ try { nativeWorktreeRemove(basePath, resolvedWtPath, true); } catch { /* may fail */ }
315
330
  }
316
331
 
317
332
  // Prune stale entries so git knows the worktree is gone
@@ -28,7 +28,7 @@ export interface WorktreeResolverDeps {
28
28
  basePath: string,
29
29
  milestoneId: string,
30
30
  roadmapContent: string,
31
- ) => { pushed: boolean };
31
+ ) => { pushed: boolean; codeFilesChanged: boolean };
32
32
  syncWorktreeStateBack: (
33
33
  mainBasePath: string,
34
34
  worktreePath: string,
@@ -63,7 +63,6 @@ export interface WorktreeResolverDeps {
63
63
  captureIntegrationBranch: (
64
64
  basePath: string,
65
65
  mid: string,
66
- opts?: { commitDocs?: boolean },
67
66
  ) => void;
68
67
  }
69
68
 
@@ -371,10 +370,23 @@ export class WorktreeResolver {
371
370
  milestoneId,
372
371
  roadmapContent,
373
372
  );
374
- ctx.notify(
375
- `Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
376
- "info",
377
- );
373
+ if (mergeResult.codeFilesChanged) {
374
+ ctx.notify(
375
+ `Milestone ${milestoneId} merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
376
+ "info",
377
+ );
378
+ } else {
379
+ // (#1906) Milestone produced only .gsd/ metadata — no actual code was
380
+ // merged. This typically means the LLM wrote planning artifacts
381
+ // (summaries, roadmaps) but never implemented the code. Surface this
382
+ // clearly so the user knows the milestone is not truly complete.
383
+ ctx.notify(
384
+ `WARNING: Milestone ${milestoneId} merged to main but contained NO code changes — only .gsd/ metadata files. ` +
385
+ `The milestone summary may describe planned work that was never implemented. ` +
386
+ `Review the milestone output and re-run if code is missing.`,
387
+ "warning",
388
+ );
389
+ }
378
390
  } else {
379
391
  // No roadmap at either location — teardown but PRESERVE the branch so
380
392
  // commits are not orphaned. The user can merge manually later (#1573).
@@ -397,10 +409,10 @@ export class WorktreeResolver {
397
409
  });
398
410
  // Surface a clear, actionable error. The worktree and milestone branch are
399
411
  // intentionally preserved — nothing has been deleted. The user can retry
400
- // /complete-milestone or merge manually once the underlying issue is fixed
412
+ // /gsd dispatch complete-milestone or merge manually once the underlying issue is fixed
401
413
  // (e.g. checkout to wrong branch, unresolved conflicts). (#1668)
402
414
  ctx.notify(
403
- `Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry /complete-milestone or merge manually.`,
415
+ `Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry /gsd dispatch complete-milestone or merge manually.`,
404
416
  "warning",
405
417
  );
406
418
 
@@ -478,10 +490,18 @@ export class WorktreeResolver {
478
490
  // Rebuild GitService after merge (branch HEAD changed)
479
491
  this.rebuildGitService();
480
492
 
481
- ctx.notify(
482
- `Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
483
- "info",
484
- );
493
+ if (mergeResult.codeFilesChanged) {
494
+ ctx.notify(
495
+ `Milestone ${milestoneId} merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
496
+ "info",
497
+ );
498
+ } else {
499
+ ctx.notify(
500
+ `WARNING: Milestone ${milestoneId} merged (branch mode) but contained NO code changes — only .gsd/ metadata. ` +
501
+ `Review the milestone output and re-run if code is missing.`,
502
+ "warning",
503
+ );
504
+ }
485
505
  debugLog("WorktreeResolver", {
486
506
  action: "mergeAndExit",
487
507
  milestoneId,
@@ -57,13 +57,13 @@ export function setActiveMilestoneId(basePath: string, milestoneId: string | nul
57
57
  * record when the user starts from a different branch (#300). Always a no-op
58
58
  * if on a GSD slice branch.
59
59
  */
60
- export function captureIntegrationBranch(basePath: string, milestoneId: string, options?: { commitDocs?: boolean }): void {
60
+ export function captureIntegrationBranch(basePath: string, milestoneId: string): void {
61
61
  // In a worktree, the base branch is implicit (worktree/<name>).
62
62
  // Writing it to META.json would leave stale metadata after merge back to main.
63
63
  if (detectWorktreeName(basePath)) return;
64
64
  const svc = getService(basePath);
65
65
  const current = svc.getCurrentBranch();
66
- writeIntegrationBranch(basePath, milestoneId, current, options);
66
+ writeIntegrationBranch(basePath, milestoneId, current);
67
67
  }
68
68
 
69
69
  // ─── Pure Utility Functions (unchanged) ────────────────────────────────────
@@ -149,7 +149,11 @@ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client>
149
149
  stderr: "pipe",
150
150
  });
151
151
  } else if (config.transport === "http" && config.url) {
152
- transport = new StreamableHTTPClientTransport(new URL(config.url));
152
+ const resolvedUrl = config.url.replace(
153
+ /\$\{([^}]+)\}/g,
154
+ (_, name) => process.env[name] ?? "",
155
+ );
156
+ transport = new StreamableHTTPClientTransport(new URL(resolvedUrl));
153
157
  } else {
154
158
  throw new Error(`Server "${name}" has unsupported transport: ${config.transport}`);
155
159
  }
@@ -398,16 +398,16 @@ export function registerSearchTool(pi: ExtensionAPI) {
398
398
  // with brief interruptions every MAX_CONSECUTIVE_DUPES+1 calls.
399
399
  if (cacheKey === lastSearchKey) {
400
400
  consecutiveDupeCount++;
401
- if (consecutiveDupeCount >= MAX_CONSECUTIVE_DUPES) {
401
+ if (consecutiveDupeCount > MAX_CONSECUTIVE_DUPES) {
402
402
  return {
403
- content: [{ type: "text" as const, text: `⚠️ Search loop detected: the query "${params.query}" has been searched ${consecutiveDupeCount + 1} times consecutively with identical results. The information you need is already in the previous search results above. Stop searching and use those results to proceed with your task.` }],
403
+ content: [{ type: "text" as const, text: `⚠️ Search loop detected: the query "${params.query}" has been searched ${consecutiveDupeCount} times consecutively with identical results. The information you need is already in the previous search results above. Stop searching and use those results to proceed with your task.` }],
404
404
  isError: true,
405
405
  details: { errorKind: "search_loop", error: "Consecutive duplicate search detected" } satisfies Partial<SearchDetails>,
406
406
  };
407
407
  }
408
408
  } else {
409
409
  lastSearchKey = cacheKey;
410
- consecutiveDupeCount = 0;
410
+ consecutiveDupeCount = 1;
411
411
  }
412
412
 
413
413
  const cached = searchCache.get(cacheKey);
@@ -516,12 +516,16 @@ async function runSingleAgentInCmuxSplit(
516
516
  const bundledPaths = (process.env.GSD_BUNDLED_EXTENSION_PATHS ?? "").split(path.delimiter).map((s) => s.trim()).filter(Boolean);
517
517
  const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
518
518
  const processArgs = [process.env.GSD_BIN_PATH!, ...extensionArgs, ...buildSubagentProcessArgs(agent, task, tmpPromptPath)];
519
+ // Normalize all paths to forward slashes before embedding in bash strings.
520
+ // On Windows, backslashes are interpreted as escape characters by bash,
521
+ // mangling paths like C:\Users\user into C:Useruser (#1436).
522
+ const bashPath = (p: string) => shellEscape(p.replaceAll("\\", "/"));
519
523
  const innerScript = [
520
- `cd ${shellEscape(cwd ?? defaultCwd)}`,
524
+ `cd ${bashPath(cwd ?? defaultCwd)}`,
521
525
  "set -o pipefail",
522
- `${shellEscape(process.execPath)} ${processArgs.map(shellEscape).join(" ")} 2> >(tee ${shellEscape(stderrPath)} >&2) | tee ${shellEscape(stdoutPath)}`,
526
+ `${bashPath(process.execPath)} ${processArgs.map(a => bashPath(a)).join(" ")} 2> >(tee ${bashPath(stderrPath)} >&2) | tee ${bashPath(stdoutPath)}`,
523
527
  "status=${PIPESTATUS[0]}",
524
- `printf '%s' "$status" > ${shellEscape(exitPath)}`,
528
+ `printf '%s' "$status" > ${bashPath(exitPath)}`,
525
529
  ].join("; ");
526
530
 
527
531
  const sent = await cmuxClient.sendSurface(cmuxSurfaceId, `bash -lc ${shellEscape(innerScript)}`);
@@ -2,7 +2,7 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
2
2
  import { shortcutDesc } from "../shared/mod.js";
3
3
  import type { AssistantMessage } from "@gsd/pi-ai";
4
4
  import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
5
- import { spawn, execSync, type ChildProcess } from "node:child_process";
5
+ import { spawn, execFileSync, type ChildProcess } from "node:child_process";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
@@ -32,7 +32,7 @@ function linuxPython(): string {
32
32
  function ensureBinary(): boolean {
33
33
  if (fs.existsSync(RECOGNIZER_BIN)) return true;
34
34
  try {
35
- execSync(`swiftc "${SWIFT_SRC}" -o "${RECOGNIZER_BIN}" -framework Speech -framework AVFoundation`, {
35
+ execFileSync("swiftc", [SWIFT_SRC, "-o", RECOGNIZER_BIN, "-framework", "Speech", "-framework", "AVFoundation"], {
36
36
  timeout: 60000,
37
37
  });
38
38
  return true;
@@ -54,7 +54,7 @@ function ensureLinuxReady(ctx: ExtensionContext): boolean {
54
54
 
55
55
  // Check python3 exists
56
56
  try {
57
- execSync("which python3", { stdio: "pipe" });
57
+ execFileSync("which", ["python3"], { stdio: "pipe" });
58
58
  } catch {
59
59
  ctx.ui.notify("Voice: python3 not found — install with: sudo apt install python3", "error");
60
60
  return false;
@@ -63,7 +63,7 @@ function ensureLinuxReady(ctx: ExtensionContext): boolean {
63
63
  // Check that sounddevice is importable
64
64
  const py = linuxPython();
65
65
  try {
66
- execSync(`${py} -c "import sounddevice"`, {
66
+ execFileSync(py, ["-c", "import sounddevice"], {
67
67
  stdio: "pipe",
68
68
  timeout: 10000,
69
69
  });