gsd-pi 2.34.0-dev.ed0bfbf → 2.35.0-dev.30eec3f

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 (334) hide show
  1. package/dist/cli.js +7 -2
  2. package/dist/resource-loader.d.ts +1 -1
  3. package/dist/resource-loader.js +13 -1
  4. package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
  5. package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
  6. package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
  7. package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
  8. package/dist/resources/extensions/bg-shell/types.js +0 -2
  9. package/dist/resources/extensions/context7/index.js +5 -0
  10. package/dist/resources/extensions/get-secrets-from-user.js +2 -30
  11. package/dist/resources/extensions/google-search/index.js +5 -0
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
  13. package/dist/resources/extensions/gsd/auto-loop.js +10 -1
  14. package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
  15. package/dist/resources/extensions/gsd/auto-start.js +35 -2
  16. package/dist/resources/extensions/gsd/auto.js +59 -4
  17. package/dist/resources/extensions/gsd/changelog.js +162 -0
  18. package/dist/resources/extensions/gsd/commands-bootstrap.js +1 -0
  19. package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
  20. package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
  21. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +5 -1
  22. package/dist/resources/extensions/gsd/commands.js +8 -1
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  24. package/dist/resources/extensions/gsd/doctor-checks.js +113 -5
  25. package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
  26. package/dist/resources/extensions/gsd/doctor-proactive.js +22 -0
  27. package/dist/resources/extensions/gsd/doctor.js +36 -0
  28. package/dist/resources/extensions/gsd/files.js +11 -2
  29. package/dist/resources/extensions/gsd/gitignore.js +54 -7
  30. package/dist/resources/extensions/gsd/guided-flow.js +5 -3
  31. package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
  32. package/dist/resources/extensions/gsd/health-widget.js +97 -46
  33. package/dist/resources/extensions/gsd/index.js +10 -1
  34. package/dist/resources/extensions/gsd/migrate-external.js +55 -2
  35. package/dist/resources/extensions/gsd/paths.js +74 -7
  36. package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
  37. package/dist/resources/extensions/gsd/preferences-validation.js +54 -1
  38. package/dist/resources/extensions/gsd/preferences.js +2 -0
  39. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  40. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  41. package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
  42. package/dist/resources/extensions/gsd/session-lock.js +26 -2
  43. package/dist/resources/extensions/gsd/templates/plan.md +8 -0
  44. package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
  45. package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
  46. package/dist/resources/extensions/shared/mod.js +1 -1
  47. package/dist/resources/extensions/shared/sanitize.js +30 -0
  48. package/dist/resources/extensions/subagent/index.js +6 -14
  49. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  50. package/package.json +2 -1
  51. package/packages/pi-agent-core/dist/agent-loop.d.ts +14 -0
  52. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  53. package/packages/pi-agent-core/dist/agent-loop.js +24 -27
  54. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  55. package/packages/pi-agent-core/dist/agent.d.ts +1 -0
  56. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  57. package/packages/pi-agent-core/dist/agent.js +11 -22
  58. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  59. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  60. package/packages/pi-agent-core/dist/proxy.js +2 -8
  61. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  62. package/packages/pi-agent-core/src/agent-loop.ts +30 -27
  63. package/packages/pi-agent-core/src/agent.ts +12 -23
  64. package/packages/pi-agent-core/src/proxy.ts +2 -8
  65. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  66. package/packages/pi-ai/dist/providers/azure-openai-responses.js +5 -41
  67. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  68. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  69. package/packages/pi-ai/dist/providers/openai-completions.js +10 -73
  70. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  71. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  72. package/packages/pi-ai/dist/providers/openai-responses.js +9 -80
  73. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  74. package/packages/pi-ai/dist/providers/openai-shared.d.ts +65 -0
  75. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -0
  76. package/packages/pi-ai/dist/providers/openai-shared.js +146 -0
  77. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -0
  78. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +7 -135
  80. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  81. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  82. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +7 -135
  83. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  84. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts +46 -0
  85. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.d.ts.map +1 -0
  86. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js +160 -0
  87. package/packages/pi-ai/dist/utils/oauth/google-oauth-utils.js.map +1 -0
  88. package/packages/pi-ai/src/providers/azure-openai-responses.ts +11 -45
  89. package/packages/pi-ai/src/providers/openai-completions.ts +16 -86
  90. package/packages/pi-ai/src/providers/openai-responses.ts +16 -96
  91. package/packages/pi-ai/src/providers/openai-shared.ts +193 -0
  92. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +14 -162
  93. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +13 -161
  94. package/packages/pi-ai/src/utils/oauth/google-oauth-utils.ts +201 -0
  95. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +16 -63
  96. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/agent-session.js +104 -641
  98. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +0 -1
  100. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/auth-storage.js +4 -35
  102. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js +5 -43
  105. package/packages/pi-coding-agent/dist/core/compaction/branch-summarization.js.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +11 -69
  108. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +40 -0
  110. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/compaction/utils.js +78 -0
  112. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +77 -0
  114. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -0
  115. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +331 -0
  116. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -0
  117. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +2 -2
  118. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
  120. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +15 -0
  122. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/runner.js +129 -243
  124. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +49 -42
  126. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/types.js +2 -21
  128. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts +39 -0
  130. package/packages/pi-coding-agent/dist/core/lock-utils.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/lock-utils.js +89 -0
  132. package/packages/pi-coding-agent/dist/core/lock-utils.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +2 -0
  134. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/lsp/config.js +4 -1
  136. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/lsp/index.js +52 -107
  139. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +2 -21
  142. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +0 -1
  144. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/lsp/types.js +0 -28
  146. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/package-manager.js +2 -4
  149. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +2 -4
  151. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/resource-loader.js +46 -60
  153. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +87 -0
  155. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -0
  156. package/packages/pi-coding-agent/dist/core/retry-handler.js +295 -0
  157. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -0
  158. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -1
  159. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/session-manager.js +3 -28
  161. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +8 -0
  163. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/settings-manager.js +76 -166
  165. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/core/skills.js +1 -3
  168. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  169. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  170. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  171. package/packages/pi-coding-agent/dist/index.js +1 -1
  172. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js +9 -26
  176. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +1 -13
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts +44 -0
  181. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.d.ts.map +1 -0
  182. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js +61 -0
  183. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-render-utils.js.map +1 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js +6 -9
  186. package/packages/pi-coding-agent/dist/modes/interactive/components/tree-selector.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +65 -0
  188. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +6 -16
  190. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts +12 -0
  192. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  193. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +175 -0
  194. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -0
  195. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts +6 -0
  196. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.d.ts.map +1 -0
  197. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js +15 -0
  198. package/packages/pi-coding-agent/dist/modes/interactive/utils/shorten-path.js.map +1 -0
  199. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/modes/print-mode.js +2 -30
  201. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  202. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +2 -28
  204. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts +19 -0
  206. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.d.ts.map +1 -0
  207. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js +45 -0
  208. package/packages/pi-coding-agent/dist/modes/shared/command-context-actions.js.map +1 -0
  209. package/packages/pi-coding-agent/dist/utils/error.d.ts +5 -0
  210. package/packages/pi-coding-agent/dist/utils/error.d.ts.map +1 -0
  211. package/packages/pi-coding-agent/dist/utils/error.js +7 -0
  212. package/packages/pi-coding-agent/dist/utils/error.js.map +1 -0
  213. package/packages/pi-coding-agent/package.json +1 -1
  214. package/packages/pi-coding-agent/src/core/agent-session.ts +117 -745
  215. package/packages/pi-coding-agent/src/core/auth-storage.ts +4 -38
  216. package/packages/pi-coding-agent/src/core/compaction/branch-summarization.ts +7 -53
  217. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +14 -74
  218. package/packages/pi-coding-agent/src/core/compaction/utils.ts +100 -0
  219. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +424 -0
  220. package/packages/pi-coding-agent/src/core/extensions/index.ts +1 -21
  221. package/packages/pi-coding-agent/src/core/extensions/runner.ts +119 -243
  222. package/packages/pi-coding-agent/src/core/extensions/types.ts +50 -69
  223. package/packages/pi-coding-agent/src/core/lock-utils.ts +113 -0
  224. package/packages/pi-coding-agent/src/core/lsp/config.ts +4 -1
  225. package/packages/pi-coding-agent/src/core/lsp/index.ts +83 -152
  226. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +2 -22
  227. package/packages/pi-coding-agent/src/core/lsp/types.ts +0 -29
  228. package/packages/pi-coding-agent/src/core/package-manager.ts +1 -4
  229. package/packages/pi-coding-agent/src/core/resource-loader.ts +56 -69
  230. package/packages/pi-coding-agent/src/core/retry-handler.ts +359 -0
  231. package/packages/pi-coding-agent/src/core/session-manager.ts +3 -30
  232. package/packages/pi-coding-agent/src/core/settings-manager.ts +85 -164
  233. package/packages/pi-coding-agent/src/core/skills.ts +1 -4
  234. package/packages/pi-coding-agent/src/index.ts +1 -7
  235. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +17 -29
  236. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1 -13
  237. package/packages/pi-coding-agent/src/modes/interactive/components/tree-render-utils.ts +81 -0
  238. package/packages/pi-coding-agent/src/modes/interactive/components/tree-selector.ts +14 -19
  239. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +7 -18
  240. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +196 -0
  241. package/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts +14 -0
  242. package/packages/pi-coding-agent/src/modes/print-mode.ts +2 -30
  243. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -28
  244. package/packages/pi-coding-agent/src/modes/shared/command-context-actions.ts +53 -0
  245. package/packages/pi-coding-agent/src/utils/error.ts +6 -0
  246. package/packages/pi-tui/dist/components/markdown.d.ts +5 -0
  247. package/packages/pi-tui/dist/components/markdown.d.ts.map +1 -1
  248. package/packages/pi-tui/dist/components/markdown.js +25 -31
  249. package/packages/pi-tui/dist/components/markdown.js.map +1 -1
  250. package/packages/pi-tui/dist/keys.d.ts +0 -4
  251. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  252. package/packages/pi-tui/dist/keys.js +94 -162
  253. package/packages/pi-tui/dist/keys.js.map +1 -1
  254. package/packages/pi-tui/src/components/markdown.ts +25 -29
  255. package/packages/pi-tui/src/keys.ts +94 -173
  256. package/pkg/dist/modes/interactive/theme/theme.d.ts +65 -0
  257. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  258. package/pkg/dist/modes/interactive/theme/theme.js +6 -16
  259. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  260. package/pkg/dist/modes/interactive/theme/themes.d.ts +12 -0
  261. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  262. package/pkg/dist/modes/interactive/theme/themes.js +175 -0
  263. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -0
  264. package/pkg/package.json +1 -1
  265. package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
  266. package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
  267. package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
  268. package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
  269. package/src/resources/extensions/bg-shell/types.ts +0 -12
  270. package/src/resources/extensions/context7/index.ts +7 -0
  271. package/src/resources/extensions/get-secrets-from-user.ts +2 -35
  272. package/src/resources/extensions/google-search/index.ts +7 -0
  273. package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
  274. package/src/resources/extensions/gsd/auto-loop.ts +11 -1
  275. package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
  276. package/src/resources/extensions/gsd/auto-start.ts +42 -2
  277. package/src/resources/extensions/gsd/auto.ts +61 -3
  278. package/src/resources/extensions/gsd/changelog.ts +213 -0
  279. package/src/resources/extensions/gsd/commands-bootstrap.ts +1 -0
  280. package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
  281. package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
  282. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +5 -1
  283. package/src/resources/extensions/gsd/commands.ts +9 -1
  284. package/src/resources/extensions/gsd/docs/preferences-reference.md +10 -0
  285. package/src/resources/extensions/gsd/doctor-checks.ts +107 -5
  286. package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
  287. package/src/resources/extensions/gsd/doctor-proactive.ts +24 -0
  288. package/src/resources/extensions/gsd/doctor-types.ts +9 -1
  289. package/src/resources/extensions/gsd/doctor.ts +35 -0
  290. package/src/resources/extensions/gsd/files.ts +12 -2
  291. package/src/resources/extensions/gsd/gitignore.ts +54 -7
  292. package/src/resources/extensions/gsd/guided-flow.ts +5 -3
  293. package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
  294. package/src/resources/extensions/gsd/health-widget.ts +103 -59
  295. package/src/resources/extensions/gsd/index.ts +10 -1
  296. package/src/resources/extensions/gsd/migrate-external.ts +47 -2
  297. package/src/resources/extensions/gsd/paths.ts +73 -7
  298. package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
  299. package/src/resources/extensions/gsd/preferences-validation.ts +54 -1
  300. package/src/resources/extensions/gsd/preferences.ts +2 -0
  301. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  302. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  303. package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
  304. package/src/resources/extensions/gsd/session-lock.ts +29 -2
  305. package/src/resources/extensions/gsd/templates/plan.md +8 -0
  306. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
  307. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +98 -2
  308. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +59 -3
  309. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
  310. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
  311. package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
  312. package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
  313. package/src/resources/extensions/gsd/tests/preferences.test.ts +40 -2
  314. package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
  315. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
  316. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -0
  317. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
  318. package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
  319. package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
  320. package/src/resources/extensions/shared/mod.ts +1 -1
  321. package/src/resources/extensions/shared/sanitize.ts +36 -0
  322. package/src/resources/extensions/subagent/index.ts +6 -12
  323. package/src/resources/skills/create-gsd-extension/references/events-reference.md +4 -4
  324. package/dist/resources/extensions/shared/wizard-ui.js +0 -478
  325. package/packages/pi-coding-agent/dist/modes/interactive/theme/dark.json +0 -85
  326. package/packages/pi-coding-agent/dist/modes/interactive/theme/light.json +0 -84
  327. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.json +0 -335
  328. package/packages/pi-coding-agent/src/modes/interactive/theme/dark.json +0 -85
  329. package/packages/pi-coding-agent/src/modes/interactive/theme/light.json +0 -84
  330. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
  331. package/pkg/dist/modes/interactive/theme/dark.json +0 -85
  332. package/pkg/dist/modes/interactive/theme/light.json +0 -84
  333. package/pkg/dist/modes/interactive/theme/theme-schema.json +0 -335
  334. package/src/resources/extensions/shared/wizard-ui.ts +0 -551
@@ -6,8 +6,8 @@
6
6
  * Both idempotent — non-destructive if already present.
7
7
  */
8
8
  import { join } from "node:path";
9
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
- import { nativeRmCached } from "./native-git-bridge.js";
9
+ import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { nativeRmCached, nativeLsFiles } from "./native-git-bridge.js";
11
11
  import { gsdRoot } from "./paths.js";
12
12
  /**
13
13
  * GSD runtime patterns for git index cleanup.
@@ -67,12 +67,48 @@ const BASELINE_PATTERNS = [
67
67
  "tmp/",
68
68
  ];
69
69
  /**
70
- * Ensure basePath/.gitignore contains a blanket `.gsd/` ignore.
71
- * Creates the file if missing; appends `.gsd/` if not present.
70
+ * Check whether `.gsd/` contains files tracked by git.
71
+ * If so, the project intentionally keeps `.gsd/` in version control
72
+ * and we must NOT add `.gsd` to `.gitignore` or attempt migration.
73
+ *
74
+ * Returns true if git tracks at least one file under `.gsd/`.
75
+ * Returns false (safe to ignore) if:
76
+ * - Not a git repo
77
+ * - `.gsd/` is a symlink (external state, should be ignored)
78
+ * - `.gsd/` doesn't exist
79
+ * - No tracked files found under `.gsd/`
80
+ */
81
+ export function hasGitTrackedGsdFiles(basePath) {
82
+ const localGsd = join(basePath, ".gsd");
83
+ // If .gsd doesn't exist or is already a symlink, no tracked files concern
84
+ if (!existsSync(localGsd))
85
+ return false;
86
+ try {
87
+ if (lstatSync(localGsd).isSymbolicLink())
88
+ return false;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ // Check if git tracks any files under .gsd/
94
+ try {
95
+ const tracked = nativeLsFiles(basePath, ".gsd");
96
+ return tracked.length > 0;
97
+ }
98
+ catch {
99
+ // Not a git repo or git not available — safe to proceed
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Ensure basePath/.gitignore contains baseline ignore patterns.
105
+ * Creates the file if missing; appends missing patterns.
72
106
  * Returns true if the file was created or modified, false if already complete.
73
107
  *
74
- * `.gsd/` state is managed externally (symlinked to `~/.gsd/projects/<hash>/`),
75
- * so the entire directory is always gitignored.
108
+ * **Safety check:** If `.gsd/` contains git-tracked files (i.e., the project
109
+ * intentionally keeps `.gsd/` in version control), the `.gsd` ignore pattern
110
+ * is excluded to prevent data loss. Only the `.gsd` pattern is affected —
111
+ * all other baseline patterns are still applied normally.
76
112
  */
77
113
  export function ensureGitignore(basePath, options) {
78
114
  // If manage_gitignore is explicitly false, do not touch .gitignore at all
@@ -88,8 +124,14 @@ export function ensureGitignore(basePath, options) {
88
124
  .split("\n")
89
125
  .map((l) => l.trim())
90
126
  .filter((l) => l && !l.startsWith("#")));
127
+ // Determine which patterns to apply. If .gsd/ has tracked files,
128
+ // exclude the ".gsd" pattern to prevent deleting tracked state.
129
+ const gsdIsTracked = hasGitTrackedGsdFiles(basePath);
130
+ const patternsToApply = gsdIsTracked
131
+ ? BASELINE_PATTERNS.filter((p) => p !== ".gsd")
132
+ : BASELINE_PATTERNS;
91
133
  // Find patterns not yet present
92
- const missing = BASELINE_PATTERNS.filter((p) => !existingLines.has(p));
134
+ const missing = patternsToApply.filter((p) => !existingLines.has(p));
93
135
  if (missing.length === 0)
94
136
  return false;
95
137
  // Build the block to append
@@ -111,6 +153,11 @@ export function ensureGitignore(basePath, options) {
111
153
  * already in the index even after .gitignore is updated.
112
154
  *
113
155
  * Only removes from the index (`--cached`), never from disk. Idempotent.
156
+ *
157
+ * Note: These are strictly runtime/ephemeral paths (activity logs, lock files,
158
+ * metrics, STATE.md). They are always safe to untrack, even when the project
159
+ * intentionally keeps other `.gsd/` files (like PROJECT.md, milestones/) in
160
+ * version control.
114
161
  */
115
162
  export function untrackRuntimeFiles(basePath) {
116
163
  const runtimePaths = GSD_RUNTIME_PATTERNS;
@@ -154,7 +154,7 @@ function parseMilestoneSequenceFromProject(content) {
154
154
  * This is the only way the wizard triggers work — everything else is the LLM's job.
155
155
  */
156
156
  function dispatchWorkflow(pi, note, customType = "gsd-run") {
157
- const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
157
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
158
158
  const workflow = readFileSync(workflowPath, "utf-8");
159
159
  pi.sendMessage({
160
160
  customType,
@@ -661,9 +661,11 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
661
661
  untrackRuntimeFiles(basePath);
662
662
  // ── Self-heal stale runtime records from crashed auto-mode sessions ──
663
663
  selfHealRuntimeRecords(basePath, ctx);
664
- // Check for crash from previous auto-mode session
664
+ // Check for crash from previous auto-mode session.
665
+ // Skip if the lock was written by the current process — acquireSessionLock()
666
+ // writes to the same file, so we'd always false-positive (#1398).
665
667
  const crashLock = readCrashLock(basePath);
666
- if (crashLock) {
668
+ if (crashLock && crashLock.pid !== process.pid) {
667
669
  clearLock(basePath);
668
670
  // Bootstrap crash with zero completed units = no work was lost.
669
671
  // Auto-discard instead of prompting the user — this commonly happens
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pure GSD health widget logic.
3
+ *
4
+ * Separates project-state detection and line rendering from the widget's
5
+ * runtime integrations so the regressions can be tested directly.
6
+ */
7
+ import { existsSync, readdirSync } from "node:fs";
8
+ import { gsdRoot } from "./paths.js";
9
+ import { join } from "node:path";
10
+ export function detectHealthWidgetProjectState(basePath) {
11
+ const root = gsdRoot(basePath);
12
+ if (!existsSync(root))
13
+ return "none";
14
+ // Lightweight milestone count — avoids the full detectProjectState() scan
15
+ // (CI markers, Makefile targets, etc.) that is unnecessary on the 60s refresh.
16
+ try {
17
+ const milestonesDir = join(root, "milestones");
18
+ if (existsSync(milestonesDir)) {
19
+ const entries = readdirSync(milestonesDir, { withFileTypes: true });
20
+ if (entries.some(e => e.isDirectory()))
21
+ return "active";
22
+ }
23
+ }
24
+ catch { /* non-fatal */ }
25
+ return "initialized";
26
+ }
27
+ function formatCost(n) {
28
+ return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
29
+ }
30
+ function formatProgress(progress) {
31
+ if (!progress)
32
+ return null;
33
+ const parts = [];
34
+ parts.push(`M ${progress.milestones.done}/${progress.milestones.total}`);
35
+ if (progress.slices)
36
+ parts.push(`S ${progress.slices.done}/${progress.slices.total}`);
37
+ if (progress.tasks)
38
+ parts.push(`T ${progress.tasks.done}/${progress.tasks.total}`);
39
+ return parts.length > 0 ? `Progress: ${parts.join(" · ")}` : null;
40
+ }
41
+ function formatEnvironmentSummary(errorCount, warningCount) {
42
+ if (errorCount <= 0 && warningCount <= 0)
43
+ return null;
44
+ const parts = [];
45
+ if (errorCount > 0)
46
+ parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
47
+ if (warningCount > 0)
48
+ parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
49
+ return `Env: ${parts.join(", ")}`;
50
+ }
51
+ function formatBudgetSummary(data) {
52
+ if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
53
+ const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
54
+ return `Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`;
55
+ }
56
+ if (data.budgetSpent > 0) {
57
+ return `Spent: ${formatCost(data.budgetSpent)}`;
58
+ }
59
+ return null;
60
+ }
61
+ function buildExecutionHeadline(data) {
62
+ const status = data.executionStatus ?? "Active project";
63
+ const target = data.executionTarget ?? data.blocker ?? "loading status…";
64
+ return ` GSD ${status}${target ? ` - ${target}` : ""}`;
65
+ }
66
+ /**
67
+ * Build compact health lines for the widget.
68
+ * Returns a string array suitable for setWidget().
69
+ */
70
+ export function buildHealthLines(data) {
71
+ if (data.projectState === "none") {
72
+ return [" GSD No project loaded — run /gsd to start"];
73
+ }
74
+ if (data.projectState === "initialized") {
75
+ return [" GSD Project initialized — run /gsd to continue setup"];
76
+ }
77
+ const lines = [buildExecutionHeadline(data)];
78
+ const details = [];
79
+ const progress = formatProgress(data.progress);
80
+ if (progress)
81
+ details.push(progress);
82
+ if (data.providerIssue)
83
+ details.push(data.providerIssue);
84
+ const environment = formatEnvironmentSummary(data.environmentErrorCount, data.environmentWarningCount);
85
+ if (environment)
86
+ details.push(environment);
87
+ const budget = formatBudgetSummary(data);
88
+ if (budget)
89
+ details.push(budget);
90
+ if (data.eta)
91
+ details.push(data.eta);
92
+ if (details.length > 0) {
93
+ lines.push(` ${details.join(" │ ")}`);
94
+ }
95
+ return lines;
96
+ }
@@ -11,21 +11,23 @@ import { runProviderChecks, summariseProviderIssues } from "./doctor-providers.j
11
11
  import { runEnvironmentChecks } from "./doctor-environment.js";
12
12
  import { loadEffectiveGSDPreferences } from "./preferences.js";
13
13
  import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
14
+ import { describeNextUnit, estimateTimeRemaining, updateSliceProgressCache } from "./auto-dashboard.js";
14
15
  import { projectRoot } from "./commands.js";
16
+ import { deriveState, invalidateStateCache } from "./state.js";
17
+ import { buildHealthLines, detectHealthWidgetProjectState, } from "./health-widget-core.js";
15
18
  // ── Data loader ────────────────────────────────────────────────────────────────
16
- function loadHealthWidgetData(basePath) {
17
- let hasProject = false;
19
+ function loadBaseHealthWidgetData(basePath) {
18
20
  let budgetCeiling;
19
21
  let budgetSpent = 0;
20
22
  let providerIssue = null;
21
23
  let environmentErrorCount = 0;
22
24
  let environmentWarningCount = 0;
25
+ const projectState = detectHealthWidgetProjectState(basePath);
23
26
  try {
24
27
  const prefs = loadEffectiveGSDPreferences();
25
28
  budgetCeiling = prefs?.preferences?.budget_ceiling;
26
29
  const ledger = loadLedgerFromDisk(basePath);
27
30
  if (ledger) {
28
- hasProject = true;
29
31
  const totals = getProjectTotals(ledger.units ?? []);
30
32
  budgetSpent = totals.cost;
31
33
  }
@@ -47,7 +49,7 @@ function loadHealthWidgetData(basePath) {
47
49
  }
48
50
  catch { /* non-fatal */ }
49
51
  return {
50
- hasProject,
52
+ projectState,
51
53
  budgetCeiling,
52
54
  budgetSpent,
53
55
  providerIssue,
@@ -56,50 +58,85 @@ function loadHealthWidgetData(basePath) {
56
58
  lastRefreshed: Date.now(),
57
59
  };
58
60
  }
59
- // ── Rendering ──────────────────────────────────────────────────────────────────
60
- function formatCost(n) {
61
- return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
61
+ function compactText(text, max = 64) {
62
+ const trimmed = text.replace(/\s+/g, " ").trim();
63
+ if (trimmed.length <= max)
64
+ return trimmed;
65
+ return `${trimmed.slice(0, max - 1).trimEnd()}…`;
62
66
  }
63
- /**
64
- * Build compact health lines for the widget.
65
- * Returns a string array suitable for setWidget().
66
- */
67
- export function buildHealthLines(data) {
68
- if (!data.hasProject) {
69
- return [" GSD No project loaded — run /gsd to start"];
70
- }
71
- const parts = [];
72
- // System status signal
73
- const totalIssues = data.environmentErrorCount + data.environmentWarningCount + (data.providerIssue ? 1 : 0);
74
- if (totalIssues === 0) {
75
- parts.push(" System OK");
76
- }
77
- else if (data.environmentErrorCount > 0 || data.providerIssue?.includes("✗")) {
78
- parts.push(`✗ ${totalIssues} issue${totalIssues > 1 ? "s" : ""}`);
79
- }
80
- else {
81
- parts.push(`⚠ ${totalIssues} warning${totalIssues > 1 ? "s" : ""}`);
67
+ function summarizeExecutionStatus(state) {
68
+ switch (state.phase) {
69
+ case "blocked": return "Blocked";
70
+ case "paused": return "Paused";
71
+ case "complete": return "Complete";
72
+ case "executing": return "Executing";
73
+ case "planning": return "Planning";
74
+ case "pre-planning": return "Pre-planning";
75
+ case "summarizing": return "Summarizing";
76
+ case "validating-milestone": return "Validating";
77
+ case "completing-milestone": return "Completing";
78
+ case "needs-discussion": return "Needs discussion";
79
+ case "replanning-slice": return "Replanning";
80
+ default: return "Active";
82
81
  }
83
- // Budget
84
- if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
85
- const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
86
- parts.push(`Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`);
87
- }
88
- else if (data.budgetSpent > 0) {
89
- parts.push(`Spent: ${formatCost(data.budgetSpent)}`);
90
- }
91
- // Provider issue (if any)
92
- if (data.providerIssue) {
93
- parts.push(data.providerIssue);
82
+ }
83
+ function summarizeExecutionTarget(state) {
84
+ switch (state.phase) {
85
+ case "needs-discussion":
86
+ return state.activeMilestone ? `Discuss ${state.activeMilestone.id}` : "Discuss milestone draft";
87
+ case "pre-planning":
88
+ return state.activeMilestone ? `Plan ${state.activeMilestone.id}` : "Research & plan milestone";
89
+ case "planning":
90
+ return state.activeSlice ? `Plan ${state.activeSlice.id}` : "Plan next slice";
91
+ case "executing":
92
+ return state.activeTask ? `Execute ${state.activeTask.id}` : "Execute next task";
93
+ case "summarizing":
94
+ return state.activeSlice ? `Complete ${state.activeSlice.id}` : "Complete current slice";
95
+ case "validating-milestone":
96
+ return state.activeMilestone ? `Validate ${state.activeMilestone.id}` : "Validate milestone";
97
+ case "completing-milestone":
98
+ return state.activeMilestone ? `Complete ${state.activeMilestone.id}` : "Complete milestone";
99
+ case "replanning-slice":
100
+ return state.activeSlice ? `Replan ${state.activeSlice.id}` : "Replan current slice";
101
+ case "blocked":
102
+ return `waiting on ${compactText(state.blockers[0] ?? state.nextAction, 56)}`;
103
+ case "paused":
104
+ return compactText(state.nextAction || "waiting to resume", 56);
105
+ case "complete":
106
+ return "All milestones complete";
107
+ default:
108
+ return compactText(describeNextUnit(state).label, 56);
94
109
  }
95
- // Environment issues
96
- if (data.environmentErrorCount > 0) {
97
- parts.push(`Env: ${data.environmentErrorCount} error${data.environmentErrorCount > 1 ? "s" : ""}`);
110
+ }
111
+ async function enrichHealthWidgetData(basePath, baseData) {
112
+ if (baseData.projectState !== "active")
113
+ return baseData;
114
+ try {
115
+ invalidateStateCache();
116
+ const state = await deriveState(basePath);
117
+ if (state.activeMilestone) {
118
+ // Warm the slice-progress cache so estimateTimeRemaining() has data
119
+ updateSliceProgressCache(basePath, state.activeMilestone.id, state.activeSlice?.id);
120
+ }
121
+ return {
122
+ ...baseData,
123
+ executionPhase: state.phase,
124
+ executionStatus: summarizeExecutionStatus(state),
125
+ executionTarget: summarizeExecutionTarget(state),
126
+ nextAction: state.nextAction,
127
+ blocker: state.blockers[0] ?? null,
128
+ activeMilestoneId: state.activeMilestone?.id,
129
+ activeSliceId: state.activeSlice?.id,
130
+ activeTaskId: state.activeTask?.id,
131
+ progress: state.progress,
132
+ eta: state.phase === "blocked" || state.phase === "paused" || state.phase === "complete"
133
+ ? null
134
+ : estimateTimeRemaining(),
135
+ };
98
136
  }
99
- else if (data.environmentWarningCount > 0) {
100
- parts.push(`Env: ${data.environmentWarningCount} warning${data.environmentWarningCount > 1 ? "s" : ""}`);
137
+ catch {
138
+ return baseData;
101
139
  }
102
- return [` ${parts.join(" │ ")}`];
103
140
  }
104
141
  // ── Widget init ────────────────────────────────────────────────────────────────
105
142
  const REFRESH_INTERVAL_MS = 60_000;
@@ -112,19 +149,33 @@ export function initHealthWidget(ctx) {
112
149
  return;
113
150
  const basePath = projectRoot();
114
151
  // String-array fallback — used in RPC mode (factory is a no-op there)
115
- const initialData = loadHealthWidgetData(basePath);
152
+ const initialData = loadBaseHealthWidgetData(basePath);
116
153
  ctx.ui.setWidget("gsd-health", buildHealthLines(initialData), { placement: "belowEditor" });
117
154
  // Factory-based widget for TUI mode — replaces the string-array above
118
155
  ctx.ui.setWidget("gsd-health", (_tui, _theme) => {
119
156
  let data = initialData;
120
157
  let cachedLines;
121
- const refreshTimer = setInterval(() => {
158
+ let refreshInFlight = false;
159
+ const refresh = async () => {
160
+ if (refreshInFlight)
161
+ return;
162
+ refreshInFlight = true;
122
163
  try {
123
- data = loadHealthWidgetData(basePath);
164
+ const baseData = loadBaseHealthWidgetData(basePath);
165
+ data = await enrichHealthWidgetData(basePath, baseData);
124
166
  cachedLines = undefined;
125
167
  _tui.requestRender();
126
168
  }
127
169
  catch { /* non-fatal */ }
170
+ finally {
171
+ refreshInFlight = false;
172
+ }
173
+ };
174
+ // Fire first enrichment immediately. requestRender() inside is a no-op
175
+ // if the widget has not yet rendered, so this is safe before factory return.
176
+ void refresh();
177
+ const refreshTimer = setInterval(() => {
178
+ void refresh();
128
179
  }, REFRESH_INTERVAL_MS);
129
180
  return {
130
181
  render(_width) {
@@ -140,7 +140,16 @@ export default function (pi) {
140
140
  // Pipe closed — nothing we can write; just exit cleanly
141
141
  process.exit(0);
142
142
  }
143
- // Re-throw anything that isn't EPIPE so real crashes still surface
143
+ if (err.code === "ENOENT" &&
144
+ err.syscall?.startsWith("spawn")) {
145
+ // spawn ENOENT — command not found (e.g., npx on Windows).
146
+ // This surfaces as an uncaught exception from child_process but
147
+ // is not a fatal process error. Log and continue instead of
148
+ // crashing auto-mode (#1384).
149
+ process.stderr.write(`[gsd] spawn ENOENT: ${err.path ?? "unknown"} — command not found\n`);
150
+ return;
151
+ }
152
+ // Re-throw anything that isn't EPIPE/ENOENT so real crashes still surface
144
153
  throw err;
145
154
  };
146
155
  process.on("uncaughtException", _gsdEpipeGuard);
@@ -5,10 +5,11 @@
5
5
  * `~/.gsd/projects/<hash>/` state directory. After migration, a
6
6
  * symlink replaces the original directory so all paths remain valid.
7
7
  */
8
- import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
8
+ import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { externalGsdRoot } from "./repo-identity.js";
11
11
  import { getErrorMessage } from "./error-utils.js";
12
+ import { hasGitTrackedGsdFiles } from "./gitignore.js";
12
13
  /**
13
14
  * Migrate a legacy in-project `.gsd/` directory to external storage.
14
15
  *
@@ -42,6 +43,27 @@ export function migrateToExternalState(basePath) {
42
43
  catch (err) {
43
44
  return { migrated: false, error: `Cannot stat .gsd: ${getErrorMessage(err)}` };
44
45
  }
46
+ // Skip if .gsd/ contains git-tracked files — the project intentionally
47
+ // keeps .gsd/ in version control and migration would destroy that.
48
+ if (hasGitTrackedGsdFiles(basePath)) {
49
+ return { migrated: false };
50
+ }
51
+ // Skip if .gsd/worktrees/ has active worktree directories (#1337).
52
+ // On Windows, active git worktrees hold OS-level directory handles that
53
+ // prevent rename/delete. Attempting migration causes EBUSY and data loss.
54
+ const worktreesDir = join(localGsd, "worktrees");
55
+ if (existsSync(worktreesDir)) {
56
+ try {
57
+ const entries = readdirSync(worktreesDir, { withFileTypes: true });
58
+ if (entries.some(e => e.isDirectory())) {
59
+ return { migrated: false };
60
+ }
61
+ }
62
+ catch {
63
+ // Can't read worktrees dir — skip migration to be safe
64
+ return { migrated: false };
65
+ }
66
+ }
45
67
  const externalPath = externalGsdRoot(basePath);
46
68
  const migratingPath = join(basePath, ".gsd.migrating");
47
69
  try {
@@ -89,7 +111,38 @@ export function migrateToExternalState(basePath) {
89
111
  }
90
112
  // Create symlink .gsd -> external path
91
113
  symlinkSync(externalPath, localGsd, "junction");
92
- // Remove .gsd.migrating
114
+ // Verify the symlink resolves correctly before removing the backup (#1377).
115
+ // On Windows, junction creation can silently succeed but resolve to the wrong
116
+ // target, or the external dir may not be accessible. If verification fails,
117
+ // restore from the backup.
118
+ try {
119
+ const resolved = realpathSync(localGsd);
120
+ const resolvedExternal = realpathSync(externalPath);
121
+ if (resolved !== resolvedExternal) {
122
+ // Symlink points to wrong target — restore backup
123
+ try {
124
+ rmSync(localGsd, { force: true });
125
+ }
126
+ catch { /* may not exist */ }
127
+ renameSync(migratingPath, localGsd);
128
+ return { migrated: false, error: `Migration verification failed: symlink resolves to ${resolved}, expected ${resolvedExternal}` };
129
+ }
130
+ // Verify we can read through the symlink
131
+ readdirSync(localGsd);
132
+ }
133
+ catch (verifyErr) {
134
+ // Symlink broken or unreadable — restore backup
135
+ try {
136
+ rmSync(localGsd, { force: true });
137
+ }
138
+ catch { /* may not exist */ }
139
+ try {
140
+ renameSync(migratingPath, localGsd);
141
+ }
142
+ catch { /* best-effort restore */ }
143
+ return { migrated: false, error: `Migration verification failed: ${getErrorMessage(verifyErr)}` };
144
+ }
145
+ // Remove .gsd.migrating only after symlink is verified
93
146
  rmSync(migratingPath, { recursive: true, force: true });
94
147
  return { migrated: true };
95
148
  }
@@ -9,7 +9,8 @@
9
9
  * via prefix matching, so existing projects work without migration.
10
10
  */
11
11
  import { readdirSync, existsSync, realpathSync, Dirent } from "node:fs";
12
- import { join } from "node:path";
12
+ import { join, dirname, normalize } from "node:path";
13
+ import { spawnSync } from "node:child_process";
13
14
  import { nativeScanGsdTree } from "./native-parser-bridge.js";
14
15
  import { DIR_CACHE_MAX } from "./constants.js";
15
16
  // ─── Directory Listing Cache ──────────────────────────────────────────────────
@@ -263,15 +264,81 @@ const LEGACY_GSD_ROOT_FILES = {
263
264
  OVERRIDES: "overrides.md",
264
265
  KNOWLEDGE: "knowledge.md",
265
266
  };
267
+ // ─── GSD Root Discovery ───────────────────────────────────────────────────────
268
+ const gsdRootCache = new Map();
269
+ /** Exported for tests only — do not call in production code. */
270
+ export function _clearGsdRootCache() {
271
+ gsdRootCache.clear();
272
+ }
273
+ /**
274
+ * Resolve the `.gsd` directory for a given project base path.
275
+ *
276
+ * Probe order:
277
+ * 1. basePath/.gsd — fast path (common case)
278
+ * 2. git rev-parse root — handles cwd-is-a-subdirectory
279
+ * 3. Walk up from basePath — handles moved .gsd in an ancestor (bounded by git root)
280
+ * 4. basePath/.gsd — creation fallback (init scenario)
281
+ *
282
+ * Result is cached per basePath for the process lifetime.
283
+ */
266
284
  export function gsdRoot(basePath) {
267
- const local = join(basePath, ".gsd");
285
+ const cached = gsdRootCache.get(basePath);
286
+ if (cached)
287
+ return cached;
288
+ const result = probeGsdRoot(basePath);
289
+ gsdRootCache.set(basePath, result);
290
+ return result;
291
+ }
292
+ function probeGsdRoot(rawBasePath) {
293
+ // 1. Fast path — check the input path directly
294
+ const local = join(rawBasePath, ".gsd");
295
+ if (existsSync(local))
296
+ return local;
297
+ // Resolve symlinks so path comparisons work correctly across platforms
298
+ // (e.g. macOS /var → /private/var). Use rawBasePath as fallback if not resolvable.
299
+ let basePath;
268
300
  try {
269
- const resolved = realpathSync(local);
270
- if (resolved !== local)
271
- return resolved; // symlink resolved
301
+ basePath = realpathSync.native(rawBasePath);
302
+ }
303
+ catch {
304
+ basePath = rawBasePath;
305
+ }
306
+ // 2. Git root anchor — used as both probe target and walk-up boundary
307
+ // Only walk if we're inside a git project — prevents escaping into
308
+ // unrelated filesystem territory when running outside any repo.
309
+ let gitRoot = null;
310
+ try {
311
+ const out = spawnSync("git", ["rev-parse", "--show-toplevel"], {
312
+ cwd: basePath,
313
+ encoding: "utf-8",
314
+ });
315
+ if (out.status === 0) {
316
+ const r = out.stdout.trim();
317
+ if (r)
318
+ gitRoot = normalize(r);
319
+ }
320
+ }
321
+ catch { /* git not available */ }
322
+ if (gitRoot) {
323
+ const candidate = join(gitRoot, ".gsd");
324
+ if (existsSync(candidate))
325
+ return candidate;
326
+ }
327
+ // 3. Walk up from basePath to the git root (only if we are in a subdirectory)
328
+ if (gitRoot && basePath !== gitRoot) {
329
+ let cur = dirname(basePath);
330
+ while (cur !== basePath) {
331
+ const candidate = join(cur, ".gsd");
332
+ if (existsSync(candidate))
333
+ return candidate;
334
+ if (cur === gitRoot)
335
+ break;
336
+ basePath = cur;
337
+ cur = dirname(cur);
338
+ }
272
339
  }
273
- catch { /* doesn't exist yet — fall through */ }
274
- return local; // backwards compat: unmigrated projects
340
+ // 4. Fallback for init/creation
341
+ return local;
275
342
  }
276
343
  export function milestonesDir(basePath) {
277
344
  return join(gsdRoot(basePath), "milestones");
@@ -111,10 +111,13 @@ function dequeueNextHook(basePath) {
111
111
  };
112
112
  // Build the prompt with variable substitution
113
113
  const [mid, sid, tid] = triggerUnitId.split("/");
114
- const prompt = config.prompt
114
+ let prompt = config.prompt
115
115
  .replace(/\{milestoneId\}/g, mid ?? "")
116
116
  .replace(/\{sliceId\}/g, sid ?? "")
117
117
  .replace(/\{taskId\}/g, tid ?? "");
118
+ // Inject browser safety instruction for hooks that may use browser tools (#1345).
119
+ // Vite HMR and other persistent connections prevent networkidle from resolving.
120
+ prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
118
121
  return {
119
122
  hookName: config.name,
120
123
  prompt,