gsd-pi 2.54.0 → 2.55.0-dev.9ec7cdf

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 (262) hide show
  1. package/dist/cli.js +19 -19
  2. package/dist/headless-ui.d.ts +27 -1
  3. package/dist/headless-ui.js +203 -13
  4. package/dist/headless.js +60 -3
  5. package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +2 -2
  6. package/dist/resources/extensions/bg-shell/utilities.js +34 -5
  7. package/dist/resources/extensions/gsd/auto/phases.js +19 -3
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  9. package/dist/resources/extensions/gsd/auto-model-selection.js +17 -1
  10. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  11. package/dist/resources/extensions/gsd/auto-start.js +12 -5
  12. package/dist/resources/extensions/gsd/auto-worktree.js +39 -14
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +5 -1
  14. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +18 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -5
  16. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +20 -0
  17. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  18. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +15 -1
  19. package/dist/resources/extensions/gsd/crash-recovery.js +2 -2
  20. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +413 -0
  21. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -1
  22. package/dist/resources/extensions/gsd/session-lock.js +46 -12
  23. package/dist/resources/extensions/gsd/skill-health.js +2 -2
  24. package/dist/resources/extensions/gsd/visualizer-overlay.js +3 -3
  25. package/dist/resources/extensions/shared/format-utils.js +1 -1
  26. package/dist/resources/extensions/subagent/worker-registry.js +2 -1
  27. package/dist/web/standalone/.next/BUILD_ID +1 -1
  28. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  29. package/dist/web/standalone/.next/build-manifest.json +4 -4
  30. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  31. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  32. package/dist/web/standalone/.next/required-server-files.json +3 -3
  33. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  36. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  44. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  60. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  72. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  100. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  106. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  120. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  122. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  124. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  126. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/index.html +1 -1
  136. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  137. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  138. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  139. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  141. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/page.js +2 -2
  143. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  145. package/dist/web/standalone/.next/server/chunks/2229.js +2 -2
  146. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  147. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/middleware.js +2 -2
  150. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  152. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  153. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  154. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  155. package/dist/web/standalone/.next/static/chunks/6502.2305d0afd2385711.js +9 -0
  156. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  157. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  158. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  159. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  160. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  161. package/dist/web/standalone/.next/static/chunks/{webpack-bca0e732db0dcec3.js → webpack-4332cbd5dd1be584.js} +1 -1
  162. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  163. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  164. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  165. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  166. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  167. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  168. package/dist/web/standalone/server.js +1 -1
  169. package/package.json +6 -4
  170. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -1
  171. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +14 -2
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  177. package/packages/pi-coding-agent/package.json +1 -1
  178. package/packages/pi-coding-agent/src/core/model-registry.ts +1 -1
  179. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -2
  180. package/pkg/package.json +1 -1
  181. package/scripts/ensure-workspace-builds.cjs +45 -41
  182. package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +2 -2
  183. package/src/resources/extensions/bg-shell/utilities.ts +39 -4
  184. package/src/resources/extensions/gsd/auto/phases.ts +25 -4
  185. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  186. package/src/resources/extensions/gsd/auto-model-selection.ts +21 -1
  187. package/src/resources/extensions/gsd/auto-prompts.ts +15 -0
  188. package/src/resources/extensions/gsd/auto-start.ts +13 -5
  189. package/src/resources/extensions/gsd/auto-worktree.ts +46 -13
  190. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +5 -4
  191. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +53 -0
  192. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -6
  193. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +24 -0
  194. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  195. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +19 -1
  196. package/src/resources/extensions/gsd/crash-recovery.ts +2 -3
  197. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +497 -0
  198. package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -1
  199. package/src/resources/extensions/gsd/session-lock.ts +46 -12
  200. package/src/resources/extensions/gsd/skill-health.ts +2 -2
  201. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +139 -0
  202. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +28 -0
  203. package/src/resources/extensions/gsd/tests/{all-milestones-complete-merge.test.ts → integration/all-milestones-complete-merge.test.ts} +3 -3
  204. package/src/resources/extensions/gsd/tests/{atomic-task-closeout.test.ts → integration/atomic-task-closeout.test.ts} +1 -1
  205. package/src/resources/extensions/gsd/tests/{auto-preflight.test.ts → integration/auto-preflight.test.ts} +1 -1
  206. package/src/resources/extensions/gsd/tests/{auto-recovery.test.ts → integration/auto-recovery.test.ts} +7 -7
  207. package/src/resources/extensions/gsd/tests/{auto-secrets-gate.test.ts → integration/auto-secrets-gate.test.ts} +2 -2
  208. package/src/resources/extensions/gsd/tests/{auto-stash-merge.test.ts → integration/auto-stash-merge.test.ts} +3 -3
  209. package/src/resources/extensions/gsd/tests/{auto-worktree-milestone-merge.test.ts → integration/auto-worktree-milestone-merge.test.ts} +4 -4
  210. package/src/resources/extensions/gsd/tests/{auto-worktree.test.ts → integration/auto-worktree.test.ts} +5 -5
  211. package/src/resources/extensions/gsd/tests/{continue-here.test.ts → integration/continue-here.test.ts} +3 -3
  212. package/src/resources/extensions/gsd/tests/{doctor-completion-deferral.test.ts → integration/doctor-completion-deferral.test.ts} +1 -1
  213. package/src/resources/extensions/gsd/tests/{doctor-delimiter-fix.test.ts → integration/doctor-delimiter-fix.test.ts} +1 -1
  214. package/src/resources/extensions/gsd/tests/{doctor-enhancements.test.ts → integration/doctor-enhancements.test.ts} +3 -3
  215. package/src/resources/extensions/gsd/tests/{doctor-environment-worktree.test.ts → integration/doctor-environment-worktree.test.ts} +1 -1
  216. package/src/resources/extensions/gsd/tests/{doctor-environment.test.ts → integration/doctor-environment.test.ts} +1 -1
  217. package/src/resources/extensions/gsd/tests/{doctor-fixlevel.test.ts → integration/doctor-fixlevel.test.ts} +2 -2
  218. package/src/resources/extensions/gsd/tests/{doctor-git.test.ts → integration/doctor-git.test.ts} +1 -1
  219. package/src/resources/extensions/gsd/tests/{doctor-proactive.test.ts → integration/doctor-proactive.test.ts} +1 -1
  220. package/src/resources/extensions/gsd/tests/{doctor-roadmap-summary-atomicity.test.ts → integration/doctor-roadmap-summary-atomicity.test.ts} +1 -1
  221. package/src/resources/extensions/gsd/tests/{doctor-runtime.test.ts → integration/doctor-runtime.test.ts} +1 -1
  222. package/src/resources/extensions/gsd/tests/{doctor.test.ts → integration/doctor.test.ts} +1 -1
  223. package/src/resources/extensions/gsd/tests/{e2e-workflow-pipeline-integration.test.ts → integration/e2e-workflow-pipeline-integration.test.ts} +5 -5
  224. package/src/resources/extensions/gsd/tests/{feature-branch-lifecycle-integration.test.ts → integration/feature-branch-lifecycle-integration.test.ts} +4 -4
  225. package/src/resources/extensions/gsd/tests/{git-locale.test.ts → integration/git-locale.test.ts} +4 -4
  226. package/src/resources/extensions/gsd/tests/{git-self-heal.test.ts → integration/git-self-heal.test.ts} +1 -1
  227. package/src/resources/extensions/gsd/tests/{git-service.test.ts → integration/git-service.test.ts} +4 -4
  228. package/src/resources/extensions/gsd/tests/{gitignore-tracked-gsd.test.ts → integration/gitignore-tracked-gsd.test.ts} +2 -2
  229. package/src/resources/extensions/gsd/tests/{idle-recovery.test.ts → integration/idle-recovery.test.ts} +3 -3
  230. package/src/resources/extensions/gsd/tests/{inherited-repo-home-dir.test.ts → integration/inherited-repo-home-dir.test.ts} +1 -1
  231. package/src/resources/extensions/gsd/tests/{integration-lifecycle.test.ts → integration/integration-lifecycle.test.ts} +4 -4
  232. package/src/resources/extensions/gsd/tests/{integration-mixed-milestones.test.ts → integration/integration-mixed-milestones.test.ts} +6 -6
  233. package/src/resources/extensions/gsd/tests/{integration-proof.test.ts → integration/integration-proof.test.ts} +12 -12
  234. package/src/resources/extensions/gsd/tests/{migrate-command.test.ts → integration/migrate-command.test.ts} +2 -2
  235. package/src/resources/extensions/gsd/tests/{milestone-transition-worktree.test.ts → integration/milestone-transition-worktree.test.ts} +3 -3
  236. package/src/resources/extensions/gsd/tests/{parallel-merge.test.ts → integration/parallel-merge.test.ts} +3 -3
  237. package/src/resources/extensions/gsd/tests/{parallel-workers-multi-milestone-e2e.test.ts → integration/parallel-workers-multi-milestone-e2e.test.ts} +3 -3
  238. package/src/resources/extensions/gsd/tests/{paths.test.ts → integration/paths.test.ts} +1 -1
  239. package/src/resources/extensions/gsd/tests/{plugin-importer-live.test.ts → integration/plugin-importer-live.test.ts} +2 -2
  240. package/src/resources/extensions/gsd/tests/{queue-completed-milestone-perf.test.ts → integration/queue-completed-milestone-perf.test.ts} +3 -3
  241. package/src/resources/extensions/gsd/tests/{queue-reorder-e2e.test.ts → integration/queue-reorder-e2e.test.ts} +5 -5
  242. package/src/resources/extensions/gsd/tests/{quick-branch-lifecycle.test.ts → integration/quick-branch-lifecycle.test.ts} +5 -5
  243. package/src/resources/extensions/gsd/tests/{run-uat.test.ts → integration/run-uat.test.ts} +4 -4
  244. package/src/resources/extensions/gsd/tests/{token-savings.test.ts → integration/token-savings.test.ts} +3 -3
  245. package/src/resources/extensions/gsd/tests/{worktree-e2e.test.ts → integration/worktree-e2e.test.ts} +4 -4
  246. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +55 -0
  247. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +60 -0
  248. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +226 -0
  249. package/src/resources/extensions/gsd/tests/plan-milestone-queue-context.test.ts +48 -0
  250. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +61 -19
  251. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +98 -0
  252. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +59 -0
  253. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +49 -24
  254. package/src/resources/extensions/gsd/visualizer-overlay.ts +3 -3
  255. package/src/resources/extensions/shared/format-utils.ts +1 -1
  256. package/src/resources/extensions/subagent/worker-registry.ts +2 -1
  257. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +0 -9
  258. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
  259. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  260. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  261. /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_buildManifest.js +0 -0
  262. /package/dist/web/standalone/.next/static/{KixbEdSRlU9zzYdZdrJ7A → k92jvAf8IfV4dZE3nnrAr}/_ssgManifest.js +0 -0
@@ -0,0 +1,497 @@
1
+ /**
2
+ * GSD Parallel Monitor Overlay
3
+ *
4
+ * Full-screen TUI overlay showing real-time parallel worker progress.
5
+ * Opened via `/gsd parallel watch` or Ctrl+Alt+P.
6
+ * Reads the same data sources as `scripts/parallel-monitor.mjs` but
7
+ * renders as a native pi-tui overlay with theme integration.
8
+ */
9
+
10
+ import { existsSync, statSync, readFileSync, openSync, readSync, closeSync, readdirSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { spawnSync } from "node:child_process";
13
+
14
+ import type { Theme } from "@gsd/pi-coding-agent";
15
+ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
16
+
17
+ import { formatDuration, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
18
+
19
+ // ─── Types ────────────────────────────────────────────────────────────────
20
+
21
+ interface StatusJson {
22
+ milestoneId: string;
23
+ pid: number;
24
+ state: string;
25
+ cost: number;
26
+ lastHeartbeat: number;
27
+ startedAt: number;
28
+ worktreePath: string;
29
+ }
30
+
31
+ interface AutoLock {
32
+ pid: number;
33
+ startedAt: string;
34
+ unitType: string;
35
+ unitId: string;
36
+ unitStartedAt: string;
37
+ }
38
+
39
+ interface SliceProgress {
40
+ id: string;
41
+ status: string;
42
+ total: number;
43
+ done: number;
44
+ }
45
+
46
+ interface WorkerView {
47
+ mid: string;
48
+ pid: number;
49
+ alive: boolean;
50
+ state: string;
51
+ cost: number;
52
+ heartbeatAge: number;
53
+ currentUnit: string | null;
54
+ unitType: string | null;
55
+ unitElapsed: number;
56
+ elapsed: number;
57
+ totalTasks: number;
58
+ doneTasks: number;
59
+ totalSlices: number;
60
+ doneSlices: number;
61
+ slices: SliceProgress[];
62
+ errors: string[];
63
+ }
64
+
65
+ // ─── Data Helpers ─────────────────────────────────────────────────────────
66
+
67
+ function readJsonSafe<T>(filePath: string): T | null {
68
+ try {
69
+ return JSON.parse(readFileSync(filePath, "utf-8")) as T;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ function isPidAlive(pid: number): boolean {
76
+ try {
77
+ process.kill(pid, 0);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ function tailRead(filePath: string, maxBytes: number): string {
85
+ try {
86
+ const stat = statSync(filePath);
87
+ const readSize = Math.min(stat.size, maxBytes);
88
+ const fd = openSync(filePath, "r");
89
+ const buf = Buffer.alloc(readSize);
90
+ readSync(fd, buf, 0, readSize, Math.max(0, stat.size - readSize));
91
+ closeSync(fd);
92
+ return buf.toString("utf-8");
93
+ } catch {
94
+ return "";
95
+ }
96
+ }
97
+
98
+ function discoverWorkers(basePath: string): string[] {
99
+ const parallelDir = join(basePath, ".gsd", "parallel");
100
+ const worktreeDir = join(basePath, ".gsd", "worktrees");
101
+ const mids = new Set<string>();
102
+
103
+ if (existsSync(parallelDir)) {
104
+ try {
105
+ for (const f of readdirSync(parallelDir)) {
106
+ if (f.endsWith(".status.json")) mids.add(f.replace(".status.json", ""));
107
+ const m = f.match(/^(M\d+)\.(stderr|stdout)\.log$/);
108
+ if (m) mids.add(m[1]);
109
+ }
110
+ } catch { /* skip */ }
111
+ }
112
+
113
+ if (existsSync(worktreeDir)) {
114
+ try {
115
+ for (const d of readdirSync(worktreeDir)) {
116
+ if (d.startsWith("M") && existsSync(join(worktreeDir, d, ".gsd", "auto.lock"))) {
117
+ mids.add(d);
118
+ }
119
+ }
120
+ } catch { /* skip */ }
121
+ }
122
+
123
+ return [...mids].sort();
124
+ }
125
+
126
+ function querySliceProgress(basePath: string, mid: string): SliceProgress[] {
127
+ const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
128
+ if (!existsSync(dbPath)) return [];
129
+
130
+ try {
131
+ const sql = `SELECT s.id, s.status, COUNT(t.id), SUM(CASE WHEN t.status='complete' THEN 1 ELSE 0 END) FROM slices s LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id WHERE s.milestone_id='${mid}' GROUP BY s.id ORDER BY s.id`;
132
+ const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
133
+ const out = (result.stdout || "").trim();
134
+ if (!out || result.status !== 0) return [];
135
+ return out.split("\n").map((line) => {
136
+ const [id, status, total, done] = line.split("|");
137
+ return { id, status, total: parseInt(total, 10), done: parseInt(done || "0", 10) };
138
+ });
139
+ } catch {
140
+ return [];
141
+ }
142
+ }
143
+
144
+ function extractCostFromNdjson(basePath: string, mid: string): number {
145
+ const stdoutPath = join(basePath, ".gsd", "parallel", `${mid}.stdout.log`);
146
+ if (!existsSync(stdoutPath)) return 0;
147
+ try {
148
+ const content = readFileSync(stdoutPath, "utf-8");
149
+ let total = 0;
150
+ for (const line of content.split("\n")) {
151
+ if (!line.includes("message_end")) continue;
152
+ try {
153
+ const obj = JSON.parse(line);
154
+ if (obj.type === "message_end") {
155
+ const cost = obj.message?.usage?.cost?.total;
156
+ if (typeof cost === "number") total += cost;
157
+ }
158
+ } catch { /* skip */ }
159
+ }
160
+ return total;
161
+ } catch {
162
+ return 0;
163
+ }
164
+ }
165
+
166
+ function queryRecentCompletions(basePath: string, mid: string): string[] {
167
+ const dbPath = join(basePath, ".gsd", "worktrees", mid, ".gsd", "gsd.db");
168
+ if (!existsSync(dbPath)) return [];
169
+ try {
170
+ const sql = `SELECT id, slice_id, one_liner FROM tasks WHERE milestone_id='${mid}' AND status='complete' AND completed_at IS NOT NULL ORDER BY completed_at DESC LIMIT 5`;
171
+ const result = spawnSync("sqlite3", [dbPath, sql], { timeout: 3000, encoding: "utf-8" });
172
+ const out = (result.stdout || "").trim();
173
+ if (!out || result.status !== 0) return [];
174
+ return out.split("\n").map((line) => {
175
+ const [taskId, sliceId, oneLiner] = line.split("|");
176
+ return `✓ ${mid}/${sliceId}/${taskId}${oneLiner ? ": " + oneLiner : ""}`;
177
+ });
178
+ } catch {
179
+ return [];
180
+ }
181
+ }
182
+
183
+ function collectWorkerData(basePath: string): WorkerView[] {
184
+ const mids = discoverWorkers(basePath);
185
+ const parallelDir = join(basePath, ".gsd", "parallel");
186
+ const workers: WorkerView[] = [];
187
+
188
+ for (const mid of mids) {
189
+ const status = readJsonSafe<StatusJson>(join(parallelDir, `${mid}.status.json`));
190
+ const lock = readJsonSafe<AutoLock>(join(basePath, ".gsd", "worktrees", mid, ".gsd", "auto.lock"));
191
+ const slices = querySliceProgress(basePath, mid);
192
+
193
+ const pid = lock?.pid || status?.pid || 0;
194
+ const alive = pid ? isPidAlive(pid) : false;
195
+
196
+ // Heartbeat: prefer status.json if PID matches, else use file mtime
197
+ let heartbeatAge = Infinity;
198
+ const statusPidMatches = status?.pid === pid && status?.lastHeartbeat;
199
+ if (statusPidMatches) {
200
+ heartbeatAge = Date.now() - status!.lastHeartbeat;
201
+ } else {
202
+ const mtimes: number[] = [];
203
+ const stdoutLog = join(parallelDir, `${mid}.stdout.log`);
204
+ const stderrLog = join(parallelDir, `${mid}.stderr.log`);
205
+ if (existsSync(stdoutLog)) mtimes.push(statSync(stdoutLog).mtimeMs);
206
+ if (existsSync(stderrLog)) mtimes.push(statSync(stderrLog).mtimeMs);
207
+ if (lock?.unitStartedAt) mtimes.push(new Date(lock.unitStartedAt).getTime());
208
+ if (mtimes.length > 0) heartbeatAge = Date.now() - Math.max(...mtimes);
209
+ }
210
+
211
+ let cost = status?.cost || 0;
212
+ if (cost === 0) cost = extractCostFromNdjson(basePath, mid);
213
+
214
+ const totalTasks = slices.reduce((sum, s) => sum + s.total, 0);
215
+ const doneTasks = slices.reduce((sum, s) => sum + s.done, 0);
216
+ const doneSlices = slices.filter((s) => s.status === "complete").length;
217
+
218
+ const elapsed = status?.startedAt
219
+ ? Date.now() - status.startedAt
220
+ : lock?.startedAt
221
+ ? Date.now() - new Date(lock.startedAt).getTime()
222
+ : 0;
223
+
224
+ // Errors from stderr (last 4KB, only new content)
225
+ const errors: string[] = [];
226
+ const stderrLog = join(parallelDir, `${mid}.stderr.log`);
227
+ if (existsSync(stderrLog)) {
228
+ const content = tailRead(stderrLog, 4096);
229
+ for (const line of content.trim().split("\n").slice(-5)) {
230
+ if (line.includes("error") || line.includes("Error") || line.includes("exited")) {
231
+ errors.push(line.trim());
232
+ }
233
+ }
234
+ }
235
+
236
+ workers.push({
237
+ mid,
238
+ pid,
239
+ alive,
240
+ state: alive ? "running" : (status?.state || "dead"),
241
+ cost,
242
+ heartbeatAge,
243
+ currentUnit: lock?.unitId || null,
244
+ unitType: lock?.unitType || null,
245
+ unitElapsed: lock?.unitStartedAt ? Date.now() - new Date(lock.unitStartedAt).getTime() : 0,
246
+ elapsed,
247
+ totalTasks,
248
+ doneTasks,
249
+ totalSlices: slices.length,
250
+ doneSlices,
251
+ slices,
252
+ errors,
253
+ });
254
+ }
255
+
256
+ return workers;
257
+ }
258
+
259
+ // ─── Rendering Helpers ────────────────────────────────────────────────────
260
+
261
+ function unitTypeLabel(unitType: string | null): string {
262
+ const labels: Record<string, string> = {
263
+ "execute-task": "EXEC",
264
+ "research-slice": "RSRCH",
265
+ "plan-slice": "PLAN",
266
+ "complete-slice": "DONE",
267
+ "complete-task": "DONE",
268
+ "reassess": "ASSESS",
269
+ "validate": "VALID",
270
+ "reassess-roadmap": "ASSESS",
271
+ };
272
+ return labels[unitType || ""] || (unitType || "---").toUpperCase().slice(0, 5);
273
+ }
274
+
275
+ function progressBar(done: number, total: number, width: number): string {
276
+ if (total === 0) return "░".repeat(width);
277
+ const filled = Math.round((done / total) * width);
278
+ return "█".repeat(filled) + "░".repeat(width - filled);
279
+ }
280
+
281
+ function healthGlyph(alive: boolean, heartbeatAge: number): string {
282
+ if (!alive) return "○";
283
+ return "●";
284
+ }
285
+
286
+ // ─── Overlay Class ────────────────────────────────────────────────────────
287
+
288
+ export class ParallelMonitorOverlay {
289
+ private tui: { requestRender: () => void };
290
+ private theme: Theme;
291
+ private onClose: () => void;
292
+ private basePath: string;
293
+ private refreshTimer: ReturnType<typeof setInterval>;
294
+ private workers: WorkerView[] = [];
295
+ private events: string[] = [];
296
+ private cachedLines?: string[];
297
+ private scrollOffset = 0;
298
+ private disposed = false;
299
+ private resizeHandler: (() => void) | null = null;
300
+
301
+ constructor(
302
+ tui: { requestRender: () => void },
303
+ theme: Theme,
304
+ onClose: () => void,
305
+ basePath?: string,
306
+ ) {
307
+ this.tui = tui;
308
+ this.theme = theme;
309
+ this.onClose = onClose;
310
+ this.basePath = basePath || process.cwd();
311
+
312
+ this.resizeHandler = () => {
313
+ if (this.disposed) return;
314
+ this.invalidate();
315
+ this.tui.requestRender();
316
+ };
317
+ process.stdout.on("resize", this.resizeHandler);
318
+
319
+ this.refresh();
320
+ this.refreshTimer = setInterval(() => this.refresh(), 5000);
321
+ }
322
+
323
+ private refresh(): void {
324
+ if (this.disposed) return;
325
+ this.workers = collectWorkerData(this.basePath);
326
+
327
+ // Collect completion events
328
+ for (const wk of this.workers) {
329
+ const completions = queryRecentCompletions(this.basePath, wk.mid);
330
+ for (const evt of completions) {
331
+ if (!this.events.includes(evt)) this.events.push(evt);
332
+ }
333
+ }
334
+ this.events = this.events.slice(-10);
335
+
336
+ this.cachedLines = undefined;
337
+ this.tui.requestRender();
338
+ }
339
+
340
+ dispose(): void {
341
+ this.disposed = true;
342
+ clearInterval(this.refreshTimer);
343
+ if (this.resizeHandler) {
344
+ process.stdout.removeListener("resize", this.resizeHandler);
345
+ this.resizeHandler = null;
346
+ }
347
+ }
348
+
349
+ handleInput(data: string): void {
350
+ if (matchesKey(data, Key.escape) || data === "q") {
351
+ this.dispose();
352
+ this.onClose();
353
+ return;
354
+ }
355
+ if (matchesKey(data, Key.down) || data === "j") {
356
+ this.scrollOffset++;
357
+ this.invalidate();
358
+ this.tui.requestRender();
359
+ return;
360
+ }
361
+ if (matchesKey(data, Key.up) || data === "k") {
362
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
363
+ this.invalidate();
364
+ this.tui.requestRender();
365
+ return;
366
+ }
367
+ }
368
+
369
+ invalidate(): void {
370
+ this.cachedLines = undefined;
371
+ }
372
+
373
+ render(width: number): string[] {
374
+ if (this.cachedLines) return this.cachedLines;
375
+
376
+ const t = this.theme;
377
+ const lines: string[] = [];
378
+ const w = Math.max(width, 60);
379
+
380
+ // Header
381
+ const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
382
+ const aliveCount = this.workers.filter((wk) => wk.alive).length;
383
+ const now = new Date().toLocaleTimeString();
384
+
385
+ lines.push(t.bold(t.fg("accent", " GSD Parallel Monitor ")));
386
+ lines.push(
387
+ t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
388
+ t.bold(`$${totalCost.toFixed(2)}`) +
389
+ t.fg("muted", " │ 5s refresh"),
390
+ );
391
+ lines.push(t.fg("muted", "─".repeat(w)));
392
+
393
+ if (this.workers.length === 0) {
394
+ lines.push("");
395
+ lines.push(t.fg("warning", " No parallel workers found."));
396
+ lines.push(t.fg("muted", " Run /gsd parallel start to begin."));
397
+ } else {
398
+ for (const wk of this.workers) {
399
+ lines.push("");
400
+
401
+ // Health + ID + state
402
+ const healthColor = wk.alive ? "success" : "error";
403
+ const glyph = healthGlyph(wk.alive, wk.heartbeatAge);
404
+ const stateText = wk.alive
405
+ ? t.fg("success", "RUNNING")
406
+ : t.fg("error", t.bold("DEAD"));
407
+ const heartbeatText = wk.heartbeatAge === Infinity
408
+ ? "never"
409
+ : formatDuration(wk.heartbeatAge) + " ago";
410
+
411
+ lines.push(
412
+ ` ${t.fg(healthColor, glyph)} ${t.bold(wk.mid)} ${stateText} ` +
413
+ t.fg("muted", `PID ${wk.pid} │ elapsed ${formatDuration(wk.elapsed)} │ `) +
414
+ `cost ${t.bold("$" + wk.cost.toFixed(2))} ` +
415
+ t.fg("muted", "│ heartbeat ") + t.fg(healthColor, heartbeatText),
416
+ );
417
+
418
+ // Current unit
419
+ if (wk.currentUnit) {
420
+ const phaseColor =
421
+ wk.unitType === "execute-task" ? "accent"
422
+ : wk.unitType === "research-slice" ? "warning"
423
+ : wk.unitType?.includes("complete") ? "success"
424
+ : "text";
425
+ lines.push(
426
+ ` ${t.fg("muted", "▸")} ${t.fg(phaseColor, unitTypeLabel(wk.unitType))} ${wk.currentUnit} ` +
427
+ t.fg("muted", `(${formatDuration(wk.unitElapsed)})`),
428
+ );
429
+ } else if (!wk.alive) {
430
+ lines.push(` ${t.fg("muted", "▸")} ${t.fg("error", "stopped")}`);
431
+ } else {
432
+ lines.push(` ${t.fg("muted", "▸ idle / between units")}`);
433
+ }
434
+
435
+ // Slice progress chips
436
+ if (wk.slices.length > 0) {
437
+ const chips = wk.slices.map((s) => {
438
+ const pct = s.total > 0 ? s.done / s.total : 0;
439
+ const color = s.status === "complete" ? "success" : pct > 0 ? "warning" : "muted";
440
+ return t.fg(color, `${s.id}:${s.done}/${s.total}`);
441
+ });
442
+ lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
443
+
444
+ // Task progress bar
445
+ const bar = progressBar(wk.doneTasks, wk.totalTasks, 25);
446
+ const pct = wk.totalTasks > 0 ? Math.round((wk.doneTasks / wk.totalTasks) * 100) : 0;
447
+ lines.push(
448
+ ` ${t.fg("muted", "tasks")} ${t.fg("success", bar)} ${wk.doneTasks}/${wk.totalTasks} ` +
449
+ t.fg("muted", `(${pct}%) │ slices done ${wk.doneSlices}/${wk.totalSlices}`),
450
+ );
451
+ }
452
+
453
+ // Errors
454
+ for (const err of wk.errors.slice(-2)) {
455
+ const truncated = err.length > w - 10 ? err.slice(0, w - 11) + "…" : err;
456
+ lines.push(` ${t.fg("error", "⚠ " + truncated)}`);
457
+ }
458
+ }
459
+ }
460
+
461
+ // Event feed
462
+ lines.push("");
463
+ lines.push(t.fg("muted", "─".repeat(w)));
464
+ lines.push(` ${t.bold("Recent Events")}`);
465
+
466
+ if (this.events.length === 0) {
467
+ lines.push(t.fg("muted", " No events yet..."));
468
+ } else {
469
+ for (const evt of this.events.slice(-8)) {
470
+ const mid = evt.match(/^✓ (M\d+)\//)?.[1] || "";
471
+ const truncated = evt.length > w - 10 ? evt.slice(0, w - 11) + "…" : evt;
472
+ lines.push(` ${t.fg("muted", "│")} ${t.fg("accent", mid)} ${truncated.replace(/^✓ M\d+\//, "")}`);
473
+ }
474
+ }
475
+
476
+ // Footer
477
+ lines.push("");
478
+ const allDone = this.workers.length > 0 && this.workers.every((wk) => !wk.alive);
479
+ if (allDone) {
480
+ lines.push(t.bold(t.fg("success", " ALL WORKERS COMPLETE")));
481
+ for (const wk of this.workers) {
482
+ lines.push(
483
+ ` ${wk.mid} $${wk.cost.toFixed(2)} │ ${wk.doneSlices}/${wk.totalSlices} slices ` +
484
+ `${wk.doneTasks}/${wk.totalTasks} tasks │ ${formatDuration(wk.elapsed)}`,
485
+ );
486
+ }
487
+ lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
488
+ }
489
+ lines.push(t.fg("muted", " ESC/q to close │ ↑↓ scroll"));
490
+
491
+ // Apply scroll — use terminal rows as height estimate
492
+ const termHeight = process.stdout.rows || 40;
493
+ const visible = lines.slice(this.scrollOffset, this.scrollOffset + termHeight);
494
+ this.cachedLines = visible;
495
+ return visible;
496
+ }
497
+ }
@@ -21,7 +21,7 @@ import { join, dirname } from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  import { gsdRoot } from "./paths.js";
23
23
  import { createWorktree, worktreePath } from "./worktree-manager.js";
24
- import { autoWorktreeBranch, runWorktreePostCreateHook } from "./auto-worktree.js";
24
+ import { autoWorktreeBranch, runWorktreePostCreateHook, syncGsdStateToWorktree } from "./auto-worktree.js";
25
25
  import { nativeBranchExists } from "./native-git-bridge.js";
26
26
  import { readIntegrationBranch } from "./git-service.js";
27
27
  import { resolveParallelConfig } from "./preferences.js";
@@ -507,6 +507,11 @@ function createMilestoneWorktree(basePath: string, milestoneId: string): string
507
507
  // Run post-create hook if configured
508
508
  runWorktreePostCreateHook(basePath, info.path);
509
509
 
510
+ // Copy .gsd/ planning artifacts (milestones, CONTEXT, ROADMAP, etc.) from the
511
+ // project root into the worktree. Without this, workers for newly-planned
512
+ // milestones can't find their roadmap and exit immediately (#2184 Bug 4).
513
+ syncGsdStateToWorktree(basePath, info.path);
514
+
510
515
  return info.path;
511
516
  }
512
517
 
@@ -83,10 +83,31 @@ let _lockAcquiredAt: number = 0;
83
83
 
84
84
  const LOCK_FILE = "auto.lock";
85
85
 
86
+ /**
87
+ * Derive the effective lock file name for the current process.
88
+ * In parallel worker mode (GSD_PARALLEL_WORKER + GSD_MILESTONE_LOCK),
89
+ * each worker uses a per-milestone lock file (`auto-<milestoneId>.lock`)
90
+ * to avoid contending on the shared `.gsd/auto.lock` (#2184).
91
+ */
92
+ export function effectiveLockFile(): string {
93
+ const mid = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : null;
94
+ return mid ? `auto-${mid}.lock` : LOCK_FILE;
95
+ }
96
+
97
+ /**
98
+ * Derive the OS-level lock target directory for the current process.
99
+ * In parallel worker mode, uses `.gsd/parallel/<milestoneId>/` instead of
100
+ * `.gsd/` so workers don't contend on the same proper-lockfile directory (#2184).
101
+ */
102
+ export function effectiveLockTarget(gsdDir: string): string {
103
+ const mid = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : null;
104
+ return mid ? join(gsdDir, "parallel", mid) : gsdDir;
105
+ }
106
+
86
107
  function lockPath(basePath: string): string {
87
108
  // If we have a snapshotted path from acquisition, use it for consistency
88
109
  if (_snapshotLockPath) return _snapshotLockPath;
89
- return join(gsdRoot(basePath), LOCK_FILE);
110
+ return join(gsdRoot(basePath), effectiveLockFile());
90
111
  }
91
112
 
92
113
  // ─── Stray Lock Cleanup ─────────────────────────────────────────────────────
@@ -265,14 +286,16 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
265
286
  }
266
287
 
267
288
  const gsdDir = gsdRoot(basePath);
289
+ const lockTarget = effectiveLockTarget(gsdDir);
268
290
 
269
291
  try {
270
- // Try to acquire an exclusive OS-level lock on the lock file.
271
- // We lock the directory (gsdRoot) since proper-lockfile works best
272
- // on directories, and the lock file itself may not exist yet.
273
- mkdirSync(gsdDir, { recursive: true });
292
+ // Try to acquire an exclusive OS-level lock on the lock target.
293
+ // We lock a directory since proper-lockfile works best on directories,
294
+ // and the lock file itself may not exist yet.
295
+ // In parallel worker mode, lockTarget is .gsd/parallel/<MID>/ (#2184).
296
+ mkdirSync(lockTarget, { recursive: true });
274
297
 
275
- const release = lockfile.lockSync(gsdDir, {
298
+ const release = lockfile.lockSync(lockTarget, {
276
299
  realpath: false,
277
300
  stale: 1_800_000, // 30 minutes — safe for laptop sleep / long event loop stalls
278
301
  update: 10_000, // Update lock mtime every 10s to prove liveness
@@ -283,7 +306,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
283
306
 
284
307
  // Safety net: clean up lock dir on process exit if _releaseFunction
285
308
  // wasn't called (e.g., normal exit after clean completion) (#1245).
286
- ensureExitHandler(gsdDir);
309
+ ensureExitHandler(lockTarget);
287
310
 
288
311
  // Write the informational lock data
289
312
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
@@ -298,12 +321,12 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
298
321
  // If no lock file or no alive process, try to clean up and re-acquire (#1245)
299
322
  if (!existingData || (existingPid && !isPidAlive(existingPid))) {
300
323
  try {
301
- const lockDir = join(gsdDir + ".lock");
324
+ const lockDir = join(lockTarget + ".lock");
302
325
  if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
303
326
  if (existsSync(lp)) unlinkSync(lp);
304
327
 
305
328
  // Retry acquisition after cleanup
306
- const release = lockfile.lockSync(gsdDir, {
329
+ const release = lockfile.lockSync(lockTarget, {
307
330
  realpath: false,
308
331
  stale: 1_800_000, // 30 minutes — match primary lock settings
309
332
  update: 10_000,
@@ -312,7 +335,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
312
335
  assignLockState(basePath, release, lp);
313
336
 
314
337
  // Safety net — uses centralized handler to avoid double-registration
315
- ensureExitHandler(gsdDir);
338
+ ensureExitHandler(lockTarget);
316
339
 
317
340
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
318
341
  return { acquired: true };
@@ -483,13 +506,24 @@ export function releaseSessionLock(basePath: string): void {
483
506
  // Non-fatal
484
507
  }
485
508
 
486
- // Remove the proper-lockfile directory (.gsd.lock/) for the current path
509
+ // Remove the proper-lockfile directory for the current lock target.
510
+ // In parallel worker mode, this is .gsd/parallel/<MID>.lock/ (#2184).
511
+ const gsdDir = gsdRoot(basePath);
512
+ const lockTarget = effectiveLockTarget(gsdDir);
487
513
  try {
488
- const lockDir = join(gsdRoot(basePath) + ".lock");
514
+ const lockDir = join(lockTarget + ".lock");
489
515
  if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
490
516
  } catch {
491
517
  // Non-fatal
492
518
  }
519
+ // Also clean the per-milestone parallel directory itself if it exists
520
+ if (lockTarget !== gsdDir) {
521
+ try {
522
+ if (existsSync(lockTarget)) rmSync(lockTarget, { recursive: true, force: true });
523
+ } catch {
524
+ // Non-fatal
525
+ }
526
+ }
493
527
 
494
528
  // Clean ALL registered lock paths (#1578) — lock files accumulate across
495
529
  // main project .gsd/, worktree .gsd/, and projects registry paths.
@@ -13,7 +13,7 @@
13
13
  * research identified as critical for skill quality.
14
14
  */
15
15
 
16
- import { existsSync, readFileSync, readdirSync } from "node:fs";
16
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { homedir } from "node:os";
19
19
  import type { UnitMetrics, MetricsLedger } from "./metrics.js";
@@ -210,7 +210,7 @@ export function formatSkillDetail(basePath: string, skillName: string): string {
210
210
  // Check for SKILL.md existence
211
211
  const skillPath = join(homedir(), ".agents", "skills", skillName, "SKILL.md");
212
212
  if (existsSync(skillPath)) {
213
- const stat = require("node:fs").statSync(skillPath);
213
+ const stat = statSync(skillPath);
214
214
  lines.push("");
215
215
  lines.push(`SKILL.md: ${skillPath}`);
216
216
  lines.push(`Last modified: ${stat.mtime.toISOString().slice(0, 10)}`);