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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/remote-questions-config.js +2 -2
  8. package/dist/resource-loader.js +100 -3
  9. package/dist/resources/extensions/async-jobs/index.js +10 -0
  10. package/dist/resources/extensions/browser-tools/index.js +3 -1
  11. package/dist/resources/extensions/browser-tools/package.json +3 -1
  12. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  13. package/dist/resources/extensions/cmux/index.js +55 -1
  14. package/dist/resources/extensions/context7/package.json +1 -1
  15. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  16. package/dist/resources/extensions/github-sync/cli.js +284 -0
  17. package/dist/resources/extensions/github-sync/index.js +73 -0
  18. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  19. package/dist/resources/extensions/github-sync/sync.js +424 -0
  20. package/dist/resources/extensions/github-sync/templates.js +118 -0
  21. package/dist/resources/extensions/github-sync/types.js +7 -0
  22. package/dist/resources/extensions/google-search/package.json +3 -1
  23. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  24. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  25. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  26. package/dist/resources/extensions/gsd/auto-loop.js +923 -787
  27. package/dist/resources/extensions/gsd/auto-post-unit.js +107 -70
  28. package/dist/resources/extensions/gsd/auto-prompts.js +205 -51
  29. package/dist/resources/extensions/gsd/auto-start.js +19 -3
  30. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  31. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  32. package/dist/resources/extensions/gsd/auto.js +149 -100
  33. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  34. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  35. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  36. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  37. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  38. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  39. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  40. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  41. package/dist/resources/extensions/gsd/captures.js +9 -1
  42. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  43. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  44. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  45. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  46. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  47. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  48. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  49. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  50. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  51. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  52. package/dist/resources/extensions/gsd/commands-handlers.js +17 -4
  53. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  54. package/dist/resources/extensions/gsd/commands.js +8 -1169
  55. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  56. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  57. package/dist/resources/extensions/gsd/detection.js +1 -2
  58. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  59. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  60. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  61. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  62. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  63. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  64. package/dist/resources/extensions/gsd/doctor.js +234 -12
  65. package/dist/resources/extensions/gsd/env-utils.js +29 -0
  66. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  67. package/dist/resources/extensions/gsd/export-html.js +46 -0
  68. package/dist/resources/extensions/gsd/export.js +1 -1
  69. package/dist/resources/extensions/gsd/files.js +48 -9
  70. package/dist/resources/extensions/gsd/forensics.js +1 -1
  71. package/dist/resources/extensions/gsd/git-service.js +30 -12
  72. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  73. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  74. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  75. package/dist/resources/extensions/gsd/health-widget.js +4 -87
  76. package/dist/resources/extensions/gsd/index.js +4 -1111
  77. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  78. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  79. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  80. package/dist/resources/extensions/gsd/package.json +1 -1
  81. package/dist/resources/extensions/gsd/paths.js +3 -0
  82. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  83. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  84. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  85. package/dist/resources/extensions/gsd/preferences.js +22 -11
  86. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  87. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  88. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  89. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  90. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  91. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  92. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  93. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  94. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  95. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  96. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  97. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  98. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  99. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  100. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  101. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  102. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  103. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  104. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  105. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  106. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  107. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  108. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  109. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  110. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  111. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  112. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  113. package/dist/resources/extensions/gsd/state.js +42 -23
  114. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  115. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  116. package/dist/resources/extensions/gsd/visualizer-data.js +27 -2
  117. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  118. package/dist/resources/extensions/gsd/worktree.js +35 -16
  119. package/dist/resources/extensions/mcp-client/index.js +14 -1
  120. package/dist/resources/extensions/remote-questions/status.js +4 -1
  121. package/dist/resources/extensions/remote-questions/store.js +4 -1
  122. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  123. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  124. package/dist/resources/extensions/subagent/index.js +12 -3
  125. package/dist/resources/extensions/subagent/isolation.js +2 -1
  126. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  127. package/dist/resources/extensions/universal-config/package.json +1 -1
  128. package/dist/welcome-screen.d.ts +13 -0
  129. package/dist/welcome-screen.js +97 -0
  130. package/package.json +1 -1
  131. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  132. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  133. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  134. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  135. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  137. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  140. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  143. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  146. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  147. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  148. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  149. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/skills.js +8 -2
  151. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  153. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  154. package/packages/pi-coding-agent/dist/index.js +1 -1
  155. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  157. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  158. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  160. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  164. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  179. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  180. package/packages/pi-coding-agent/package.json +1 -1
  181. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  182. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  183. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  184. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  185. package/packages/pi-coding-agent/src/core/skills.ts +11 -2
  186. package/packages/pi-coding-agent/src/index.ts +1 -0
  187. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  188. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  189. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  190. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  191. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  192. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/async-jobs/index.ts +11 -0
  195. package/src/resources/extensions/browser-tools/index.ts +3 -0
  196. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  197. package/src/resources/extensions/cmux/index.ts +57 -1
  198. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  199. package/src/resources/extensions/github-sync/cli.ts +364 -0
  200. package/src/resources/extensions/github-sync/index.ts +93 -0
  201. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  202. package/src/resources/extensions/github-sync/sync.ts +556 -0
  203. package/src/resources/extensions/github-sync/templates.ts +183 -0
  204. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  205. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  206. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  207. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  208. package/src/resources/extensions/github-sync/types.ts +47 -0
  209. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  210. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  211. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  212. package/src/resources/extensions/gsd/auto-loop.ts +1285 -1138
  213. package/src/resources/extensions/gsd/auto-post-unit.ts +90 -46
  214. package/src/resources/extensions/gsd/auto-prompts.ts +250 -53
  215. package/src/resources/extensions/gsd/auto-start.ts +24 -3
  216. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  217. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  218. package/src/resources/extensions/gsd/auto.ts +152 -111
  219. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  220. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  221. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  222. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  224. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  225. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  226. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  227. package/src/resources/extensions/gsd/captures.ts +10 -1
  228. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  229. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  230. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  231. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  232. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  233. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  234. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  235. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  236. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  237. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  238. package/src/resources/extensions/gsd/commands-handlers.ts +18 -3
  239. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  240. package/src/resources/extensions/gsd/commands.ts +10 -1307
  241. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  242. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  243. package/src/resources/extensions/gsd/detection.ts +2 -2
  244. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  245. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  246. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  247. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  248. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  249. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  250. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  251. package/src/resources/extensions/gsd/doctor.ts +243 -14
  252. package/src/resources/extensions/gsd/env-utils.ts +31 -0
  253. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  254. package/src/resources/extensions/gsd/export-html.ts +51 -0
  255. package/src/resources/extensions/gsd/export.ts +1 -1
  256. package/src/resources/extensions/gsd/files.ts +51 -11
  257. package/src/resources/extensions/gsd/forensics.ts +1 -1
  258. package/src/resources/extensions/gsd/git-service.ts +44 -10
  259. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  260. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  261. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  262. package/src/resources/extensions/gsd/health-widget.ts +4 -89
  263. package/src/resources/extensions/gsd/index.ts +12 -1307
  264. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  265. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  266. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  267. package/src/resources/extensions/gsd/paths.ts +4 -0
  268. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  269. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  270. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  271. package/src/resources/extensions/gsd/preferences.ts +25 -11
  272. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  273. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  274. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  275. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  276. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  277. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  278. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  279. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  280. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  281. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  282. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  283. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  284. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  285. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  286. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  287. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  288. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  289. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  290. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  291. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  292. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  293. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  294. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  295. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  296. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  297. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  298. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  299. package/src/resources/extensions/gsd/state.ts +39 -21
  300. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  301. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  302. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  303. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -77
  304. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  305. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  306. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  307. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  308. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  309. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  310. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  311. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  312. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  313. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  314. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  315. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  316. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  317. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  318. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  319. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  320. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  321. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  322. package/src/resources/extensions/gsd/types.ts +18 -1
  323. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  324. package/src/resources/extensions/gsd/visualizer-data.ts +52 -2
  325. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  326. package/src/resources/extensions/gsd/worktree.ts +35 -15
  327. package/src/resources/extensions/mcp-client/index.ts +17 -1
  328. package/src/resources/extensions/remote-questions/status.ts +5 -1
  329. package/src/resources/extensions/remote-questions/store.ts +5 -1
  330. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  331. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  332. package/src/resources/extensions/subagent/index.ts +12 -3
  333. package/src/resources/extensions/subagent/isolation.ts +3 -1
  334. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  335. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  336. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  337. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  338. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  339. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  340. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  341. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  342. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  343. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  344. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  345. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  346. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -1,7 +1,7 @@
1
- import { existsSync, mkdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
4
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
4
+ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
5
5
  import { deriveState, isMilestoneComplete } from "./state.js";
6
6
  import { invalidateAllCaches } from "./cache.js";
7
7
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -9,7 +9,7 @@ import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
9
9
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
10
10
  import { checkEnvironmentHealth } from "./doctor-environment.js";
11
11
  import { runProviderChecks } from "./doctor-providers.js";
12
- export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
12
+ export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
13
13
  export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
14
14
  export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
15
15
  /**
@@ -257,10 +257,23 @@ async function markSliceDoneInRoadmap(basePath, milestoneId, sliceId, fixesAppli
257
257
  fixesApplied.push(`marked ${sliceId} done in ${roadmapPath}`);
258
258
  }
259
259
  }
260
+ async function markSliceUndoneInRoadmap(basePath, milestoneId, sliceId, fixesApplied) {
261
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
262
+ if (!roadmapPath)
263
+ return;
264
+ const content = await loadFile(roadmapPath);
265
+ if (!content)
266
+ return;
267
+ const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"), `$1[ ] **${sliceId}:`);
268
+ if (updated !== content) {
269
+ await saveFile(roadmapPath, updated);
270
+ fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
271
+ }
272
+ }
260
273
  function matchesScope(unitId, scope) {
261
274
  if (!scope)
262
275
  return true;
263
- return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
276
+ return unitId === scope || unitId.startsWith(`${scope}/`);
264
277
  }
265
278
  function auditRequirements(content) {
266
279
  if (!content)
@@ -324,10 +337,98 @@ export async function selectDoctorScope(basePath, requestedScope) {
324
337
  }
325
338
  return state.registry[0]?.id;
326
339
  }
340
+ // ── Helper: circular dependency detection ──────────────────────────────────
341
+ function detectCircularDependencies(slices) {
342
+ const known = new Set(slices.map(s => s.id));
343
+ const adj = new Map();
344
+ for (const s of slices)
345
+ adj.set(s.id, s.depends.filter(d => known.has(d)));
346
+ const state = new Map();
347
+ for (const s of slices)
348
+ state.set(s.id, "unvisited");
349
+ const cycles = [];
350
+ function dfs(id, path) {
351
+ const st = state.get(id);
352
+ if (st === "done")
353
+ return;
354
+ if (st === "visiting") {
355
+ cycles.push([...path.slice(path.indexOf(id)), id]);
356
+ return;
357
+ }
358
+ state.set(id, "visiting");
359
+ for (const dep of adj.get(id) ?? [])
360
+ dfs(dep, [...path, id]);
361
+ state.set(id, "done");
362
+ }
363
+ for (const s of slices)
364
+ if (state.get(s.id) === "unvisited")
365
+ dfs(s.id, []);
366
+ return cycles;
367
+ }
368
+ async function appendDoctorHistory(basePath, report) {
369
+ try {
370
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
371
+ const errorCount = report.issues.filter(i => i.severity === "error").length;
372
+ const warningCount = report.issues.filter(i => i.severity === "warning").length;
373
+ const issueDetails = report.issues
374
+ .filter(i => i.severity === "error" || i.severity === "warning")
375
+ .slice(0, 10) // cap to keep JSONL lines bounded
376
+ .map(i => ({ severity: i.severity, code: i.code, message: i.message, unitId: i.unitId }));
377
+ // Human-readable one-line summary
378
+ const summaryParts = [];
379
+ if (report.ok) {
380
+ summaryParts.push("Clean");
381
+ }
382
+ else {
383
+ const counts = [];
384
+ if (errorCount > 0)
385
+ counts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
386
+ if (warningCount > 0)
387
+ counts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
388
+ summaryParts.push(counts.join(", "));
389
+ }
390
+ if (report.fixesApplied.length > 0) {
391
+ summaryParts.push(`${report.fixesApplied.length} fixed`);
392
+ }
393
+ if (issueDetails.length > 0) {
394
+ const topIssue = issueDetails.find(i => i.severity === "error") ?? issueDetails[0];
395
+ summaryParts.push(topIssue.message);
396
+ }
397
+ const entry = JSON.stringify({
398
+ ts: new Date().toISOString(),
399
+ ok: report.ok,
400
+ errors: errorCount,
401
+ warnings: warningCount,
402
+ fixes: report.fixesApplied.length,
403
+ codes: [...new Set(report.issues.map(i => i.code))],
404
+ issues: issueDetails.length > 0 ? issueDetails : undefined,
405
+ fixDescriptions: report.fixesApplied.length > 0 ? report.fixesApplied : undefined,
406
+ scope: report.scope,
407
+ summary: summaryParts.join(" · "),
408
+ });
409
+ const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
410
+ await saveFile(historyPath, existing + entry + "\n");
411
+ }
412
+ catch { /* non-fatal */ }
413
+ }
414
+ /** Read the last N doctor history entries. Returns most-recent-first. */
415
+ export async function readDoctorHistory(basePath, lastN = 50) {
416
+ try {
417
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
418
+ if (!existsSync(historyPath))
419
+ return [];
420
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
421
+ return lines.slice(-lastN).reverse().map(l => JSON.parse(l));
422
+ }
423
+ catch {
424
+ return [];
425
+ }
426
+ }
327
427
  export async function runGSDDoctor(basePath, options) {
328
428
  const issues = [];
329
429
  const fixesApplied = [];
330
430
  const fix = options?.fix === true;
431
+ const dryRun = options?.dryRun === true;
331
432
  const fixLevel = options?.fixLevel ?? "all";
332
433
  // Issue codes that represent completion state transitions — creating summary
333
434
  // stubs, marking slices/milestones done in the roadmap. These belong to the
@@ -336,12 +437,18 @@ export async function runGSDDoctor(basePath, options) {
336
437
  // detected and reported but never auto-fixed.
337
438
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
338
439
  const shouldFix = (code) => {
339
- if (!fix)
440
+ if (!fix || dryRun)
340
441
  return false;
341
442
  if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
342
443
  return false;
343
444
  return true;
344
445
  };
446
+ /** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
447
+ const dryRunCanFix = (code, message) => {
448
+ if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
449
+ fixesApplied.push(`[dry-run] would fix: ${message}`);
450
+ }
451
+ };
345
452
  const prefs = loadEffectiveGSDPreferences();
346
453
  if (prefs) {
347
454
  const prefIssues = validatePreferenceShape(prefs.preferences);
@@ -357,18 +464,30 @@ export async function runGSDDoctor(basePath, options) {
357
464
  });
358
465
  }
359
466
  }
360
- // Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files)
467
+ // Git health checks timed
468
+ const t0git = Date.now();
361
469
  const isolationMode = options?.isolationMode ??
362
470
  (prefs?.preferences?.git?.isolation === "none" ? "none" :
363
471
  prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
364
472
  await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
365
- // Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore)
473
+ const gitMs = Date.now() - t0git;
474
+ // Runtime health checks — timed
475
+ const t0runtime = Date.now();
366
476
  await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
367
- // Environment health checks (#1221: missing tools, port conflicts, stale deps, disk space)
368
- await checkEnvironmentHealth(basePath, issues, { includeRemote: !options?.scope });
477
+ const runtimeMs = Date.now() - t0runtime;
478
+ // Environment health checks timed
479
+ const t0env = Date.now();
480
+ await checkEnvironmentHealth(basePath, issues, {
481
+ includeRemote: !options?.scope,
482
+ includeBuild: options?.includeBuild,
483
+ includeTests: options?.includeTests,
484
+ });
485
+ const envMs = Date.now() - t0env;
369
486
  const milestonesPath = milestonesDir(basePath);
370
487
  if (!existsSync(milestonesPath)) {
371
- return { ok: issues.every(issue => issue.severity !== "error"), basePath, issues, fixesApplied };
488
+ const report = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
489
+ await appendDoctorHistory(basePath, report);
490
+ return report;
372
491
  }
373
492
  const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
374
493
  const requirementsContent = await loadFile(requirementsPath);
@@ -432,6 +551,46 @@ export async function runGSDDoctor(basePath, options) {
432
551
  if (!roadmapContent)
433
552
  continue;
434
553
  const roadmap = parseRoadmap(roadmapContent);
554
+ // ── Circular dependency detection ──────────────────────────────────────
555
+ for (const cycle of detectCircularDependencies(roadmap.slices)) {
556
+ issues.push({
557
+ severity: "error",
558
+ code: "circular_slice_dependency",
559
+ scope: "milestone",
560
+ unitId: milestoneId,
561
+ message: `Circular dependency detected: ${cycle.join(" → ")}`,
562
+ file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
563
+ fixable: false,
564
+ });
565
+ }
566
+ // ── Orphaned slice directories ─────────────────────────────────────────
567
+ try {
568
+ const slicesDir = join(milestonePath, "slices");
569
+ if (existsSync(slicesDir)) {
570
+ const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
571
+ for (const entry of readdirSync(slicesDir)) {
572
+ try {
573
+ if (!lstatSync(join(slicesDir, entry)).isDirectory())
574
+ continue;
575
+ }
576
+ catch {
577
+ continue;
578
+ }
579
+ if (!knownSliceIds.has(entry)) {
580
+ issues.push({
581
+ severity: "warning",
582
+ code: "orphaned_slice_directory",
583
+ scope: "milestone",
584
+ unitId: milestoneId,
585
+ message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
586
+ file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
587
+ fixable: false,
588
+ });
589
+ }
590
+ }
591
+ }
592
+ }
593
+ catch { /* non-fatal */ }
435
594
  for (const slice of roadmap.slices) {
436
595
  const unitId = `${milestoneId}/${slice.id}`;
437
596
  if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
@@ -502,6 +661,34 @@ export async function runGSDDoctor(basePath, options) {
502
661
  }
503
662
  continue;
504
663
  }
664
+ // ── Duplicate task IDs ───────────────────────────────────────────────
665
+ const taskIdCounts = new Map();
666
+ for (const task of plan.tasks)
667
+ taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
668
+ for (const [taskId, count] of taskIdCounts) {
669
+ if (count > 1) {
670
+ issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
671
+ message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
672
+ file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
673
+ }
674
+ }
675
+ // ── Task files on disk not in plan ────────────────────────────────────
676
+ try {
677
+ if (tasksDir) {
678
+ const planTaskIds = new Set(plan.tasks.map(t => t.id));
679
+ for (const f of readdirSync(tasksDir)) {
680
+ if (!f.endsWith("-SUMMARY.md"))
681
+ continue;
682
+ const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
683
+ if (!planTaskIds.has(diskTaskId)) {
684
+ issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
685
+ message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
686
+ file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
687
+ }
688
+ }
689
+ }
690
+ }
691
+ catch { /* non-fatal */ }
505
692
  let allTasksDone = plan.tasks.length > 0;
506
693
  for (const task of plan.tasks) {
507
694
  const taskUnitId = `${unitId}/${task.id}`;
@@ -517,6 +704,7 @@ export async function runGSDDoctor(basePath, options) {
517
704
  file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
518
705
  fixable: true,
519
706
  });
707
+ dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
520
708
  if (shouldFix("task_done_missing_summary")) {
521
709
  const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
522
710
  const stubContent = [
@@ -575,6 +763,22 @@ export async function runGSDDoctor(basePath, options) {
575
763
  }
576
764
  }
577
765
  }
766
+ // ── Future timestamp check ─────────────────────────────────────
767
+ if (task.done && hasSummary && summaryPath) {
768
+ try {
769
+ const rawSummary = await loadFile(summaryPath);
770
+ const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
771
+ if (m) {
772
+ const ts = new Date(m[1].trim());
773
+ if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
774
+ issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
775
+ message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
776
+ file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
777
+ }
778
+ }
779
+ }
780
+ catch { /* non-fatal */ }
781
+ }
578
782
  allTasksDone = allTasksDone && task.done;
579
783
  }
580
784
  // Blocker-without-replan detection
@@ -604,6 +808,12 @@ export async function runGSDDoctor(basePath, options) {
604
808
  }
605
809
  }
606
810
  }
811
+ // ── Stale REPLAN: exists but all tasks done ────────────────────────
812
+ if (replanPath && allTasksDone) {
813
+ issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
814
+ message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
815
+ file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
816
+ }
607
817
  const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
608
818
  const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
609
819
  const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
@@ -618,6 +828,7 @@ export async function runGSDDoctor(basePath, options) {
618
828
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
619
829
  fixable: true,
620
830
  });
831
+ dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
621
832
  if (shouldFix("all_tasks_done_missing_slice_summary"))
622
833
  await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
623
834
  }
@@ -631,6 +842,7 @@ export async function runGSDDoctor(basePath, options) {
631
842
  file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
632
843
  fixable: true,
633
844
  });
845
+ dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
634
846
  if (shouldFix("all_tasks_done_missing_slice_uat"))
635
847
  await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
636
848
  }
@@ -644,6 +856,7 @@ export async function runGSDDoctor(basePath, options) {
644
856
  file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
645
857
  fixable: true,
646
858
  });
859
+ dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
647
860
  if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
648
861
  await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
649
862
  }
@@ -658,6 +871,12 @@ export async function runGSDDoctor(basePath, options) {
658
871
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
659
872
  fixable: true,
660
873
  });
874
+ if (!allTasksDone) {
875
+ dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
876
+ if (shouldFix("slice_checked_missing_summary")) {
877
+ await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
878
+ }
879
+ }
661
880
  }
662
881
  if (slice.done && !hasSliceUat) {
663
882
  issues.push({
@@ -696,13 +915,16 @@ export async function runGSDDoctor(basePath, options) {
696
915
  });
697
916
  }
698
917
  }
699
- if (fix && fixesApplied.length > 0) {
918
+ if (fix && !dryRun && fixesApplied.length > 0) {
700
919
  await updateStateFile(basePath, fixesApplied);
701
920
  }
702
- return {
921
+ const report = {
703
922
  ok: issues.every(issue => issue.severity !== "error"),
704
923
  basePath,
705
924
  issues,
706
925
  fixesApplied,
926
+ timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
707
927
  };
928
+ await appendDoctorHistory(basePath, report);
929
+ return report;
708
930
  }
@@ -0,0 +1,29 @@
1
+ // GSD Extension — Environment variable utilities
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+ //
4
+ // Pure utility for checking existing env keys in .env files and process.env.
5
+ // Extracted from get-secrets-from-user.ts to avoid pulling in @gsd/pi-tui
6
+ // when only env-checking is needed (e.g. from files.ts during report generation).
7
+ import { readFile } from "node:fs/promises";
8
+ /**
9
+ * Check which keys already exist in a .env file or process.env.
10
+ * Returns the subset of `keys` that are already set.
11
+ */
12
+ export async function checkExistingEnvKeys(keys, envFilePath) {
13
+ let fileContent = "";
14
+ try {
15
+ fileContent = await readFile(envFilePath, "utf8");
16
+ }
17
+ catch {
18
+ // ENOENT or other read error — proceed with empty content
19
+ }
20
+ const existing = [];
21
+ for (const key of keys) {
22
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23
+ const regex = new RegExp(`^${escaped}\\s*=`, "m");
24
+ if (regex.test(fileContent) || key in process.env) {
25
+ existing.push(key);
26
+ }
27
+ }
28
+ return existing;
29
+ }
@@ -1,9 +1,10 @@
1
+ import { importExtensionModule } from "@gsd/pi-coding-agent";
1
2
  export function registerExitCommand(pi, deps = {}) {
2
3
  pi.registerCommand("exit", {
3
4
  description: "Exit GSD gracefully",
4
5
  handler: async (_args, ctx) => {
5
6
  // Stop auto-mode first so locks and activity state are cleaned up before shutdown.
6
- const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
7
+ const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
7
8
  await stopAuto(ctx, pi, "Graceful exit");
8
9
  ctx.shutdown();
9
10
  },
@@ -236,9 +236,55 @@ function buildHealthSection(data) {
236
236
  <td>${formatTokenCount(tb.tokens.total)}</td></tr>`).join('')}
237
237
  </tbody>
238
238
  </table>` : '';
239
+ // Progress score section
240
+ let progressHtml = '';
241
+ if (h.progressScore) {
242
+ const ps = h.progressScore;
243
+ const scoreColor = ps.level === 'green' ? '#22c55e' : ps.level === 'yellow' ? '#eab308' : '#ef4444';
244
+ const signalRows = ps.signals.map(s => {
245
+ const icon = s.kind === 'positive' ? '✓' : s.kind === 'negative' ? '✗' : '·';
246
+ const color = s.kind === 'positive' ? '#22c55e' : s.kind === 'negative' ? '#ef4444' : '#888';
247
+ return `<div style="margin-left:1em;color:${color}">${icon} ${esc(s.label)}</div>`;
248
+ }).join('');
249
+ progressHtml = `
250
+ <h3>Progress Score</h3>
251
+ <div style="font-size:1.1em;font-weight:bold;color:${scoreColor}">● ${esc(ps.summary)}</div>
252
+ ${signalRows}`;
253
+ }
254
+ // Doctor history section
255
+ let historyHtml = '';
256
+ const doctorHistory = h.doctorHistory ?? [];
257
+ if (doctorHistory.length > 0) {
258
+ const historyRows = doctorHistory.slice(0, 20).map(entry => {
259
+ const statusIcon = entry.ok ? '✓' : '✗';
260
+ const statusColor = entry.ok ? '#22c55e' : '#ef4444';
261
+ const ts = entry.ts.replace('T', ' ').slice(0, 19);
262
+ const scopeTag = entry.scope ? `<span class="mono" style="color:#888"> [${esc(entry.scope)}]</span>` : '';
263
+ const summaryText = entry.summary ? esc(entry.summary) : `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`;
264
+ const issueDetails = (entry.issues ?? []).slice(0, 3).map(i => {
265
+ const iColor = i.severity === 'error' ? '#ef4444' : '#eab308';
266
+ return `<div style="margin-left:2em;color:${iColor};font-size:0.85em">${i.severity === 'error' ? '✗' : '⚠'} ${esc(i.message)} <span class="mono" style="color:#888">${esc(i.unitId)}</span></div>`;
267
+ }).join('');
268
+ const fixDetails = (entry.fixDescriptions ?? []).slice(0, 2).map(f => `<div style="margin-left:2em;color:#22c55e;font-size:0.85em">↳ ${esc(f)}</div>`).join('');
269
+ return `<tr style="color:${statusColor}">
270
+ <td class="mono">${statusIcon}</td>
271
+ <td class="mono">${esc(ts)}${scopeTag}</td>
272
+ <td>${summaryText}</td>
273
+ </tr>
274
+ ${issueDetails || fixDetails ? `<tr><td colspan="3">${issueDetails}${fixDetails}</td></tr>` : ''}`;
275
+ }).join('');
276
+ historyHtml = `
277
+ <h3>Doctor Run History</h3>
278
+ <table class="tbl">
279
+ <thead><tr><th></th><th>Time</th><th>Summary</th></tr></thead>
280
+ <tbody>${historyRows}</tbody>
281
+ </table>`;
282
+ }
239
283
  return section('health', 'Health', `
240
284
  <table class="tbl tbl-kv"><tbody>${rows.join('')}</tbody></table>
241
285
  ${tierRows}
286
+ ${progressHtml}
287
+ ${historyHtml}
242
288
  `);
243
289
  }
244
290
  // ─── Section: Progress ────────────────────────────────────────────────────────
@@ -5,7 +5,7 @@ import { join, basename } from "node:path";
5
5
  import { exec } from "node:child_process";
6
6
  import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, formatCost, formatTokenCount, loadLedgerFromDisk, } from "./metrics.js";
7
7
  import { gsdRoot } from "./paths.js";
8
- import { formatDuration, fileLink } from "../shared/mod.js";
8
+ import { formatDuration, fileLink } from "../shared/format-utils.js";
9
9
  import { getErrorMessage } from "./error-utils.js";
10
10
  /**
11
11
  * Open a file in the user's default browser.
@@ -6,8 +6,8 @@ import { promises as fs } from 'node:fs';
6
6
  import { resolve } from 'node:path';
7
7
  import { atomicWriteAsync } from './atomic-write.js';
8
8
  import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
9
- import { findMilestoneIds } from './guided-flow.js';
10
- import { checkExistingEnvKeys } from '../get-secrets-from-user.js';
9
+ import { findMilestoneIds } from './milestone-ids.js';
10
+ import { checkExistingEnvKeys } from './env-utils.js';
11
11
  import { parseRoadmapSlices } from './roadmap-slices.js';
12
12
  import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
13
13
  import { debugTime, debugCount } from './debug-logger.js';
@@ -222,13 +222,48 @@ export function formatSecretsManifest(manifest) {
222
222
  return lines.join('\n') + '\n';
223
223
  }
224
224
  // ─── Slice Plan Parser ─────────────────────────────────────────────────────
225
+ function normalizeTaskPlanFrontmatter(frontmatter) {
226
+ const estimatedStepsRaw = frontmatter.estimated_steps;
227
+ const estimatedFilesRaw = frontmatter.estimated_files;
228
+ const skillsUsedRaw = frontmatter.skills_used;
229
+ const parseOptionalNumber = (value) => {
230
+ if (typeof value === 'number' && Number.isFinite(value))
231
+ return value;
232
+ if (typeof value === 'string' && value.trim()) {
233
+ const parsed = parseInt(value, 10);
234
+ if (Number.isFinite(parsed))
235
+ return parsed;
236
+ }
237
+ return undefined;
238
+ };
239
+ const estimated_steps = parseOptionalNumber(estimatedStepsRaw);
240
+ const estimated_files = parseOptionalNumber(estimatedFilesRaw);
241
+ const skills_used = Array.isArray(skillsUsedRaw)
242
+ ? skillsUsedRaw.map(v => String(v).trim()).filter(Boolean)
243
+ : typeof skillsUsedRaw === 'string' && skillsUsedRaw.trim()
244
+ ? [skillsUsedRaw.trim()]
245
+ : [];
246
+ return {
247
+ ...(estimated_steps !== undefined ? { estimated_steps } : {}),
248
+ ...(estimated_files !== undefined ? { estimated_files } : {}),
249
+ skills_used,
250
+ };
251
+ }
252
+ export function parseTaskPlanFile(content) {
253
+ const [fmLines] = splitFrontmatter(content);
254
+ const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
255
+ return {
256
+ frontmatter: normalizeTaskPlanFrontmatter(fm),
257
+ };
258
+ }
225
259
  export function parsePlan(content) {
226
260
  return cachedParse(content, 'plan', _parsePlanImpl);
227
261
  }
228
262
  function _parsePlanImpl(content) {
229
263
  const stopTimer = debugTime("parse-plan");
264
+ const [, body] = splitFrontmatter(content);
230
265
  // Try native parser first for better performance
231
- const nativeResult = nativeParsePlanFile(content);
266
+ const nativeResult = nativeParsePlanFile(body);
232
267
  if (nativeResult) {
233
268
  stopTimer({ native: true });
234
269
  return {
@@ -249,7 +284,7 @@ function _parsePlanImpl(content) {
249
284
  filesLikelyTouched: nativeResult.filesLikelyTouched,
250
285
  };
251
286
  }
252
- const lines = content.split('\n');
287
+ const lines = body.split('\n');
253
288
  const h1 = lines.find(l => l.startsWith('# '));
254
289
  let id = '';
255
290
  let title = '';
@@ -263,11 +298,11 @@ function _parsePlanImpl(content) {
263
298
  title = h1.slice(2).trim();
264
299
  }
265
300
  }
266
- const goal = extractBoldField(content, 'Goal') || '';
267
- const demo = extractBoldField(content, 'Demo') || '';
268
- const mhSection = extractSection(content, 'Must-Haves');
301
+ const goal = extractBoldField(body, 'Goal') || '';
302
+ const demo = extractBoldField(body, 'Demo') || '';
303
+ const mhSection = extractSection(body, 'Must-Haves');
269
304
  const mustHaves = mhSection ? parseBullets(mhSection) : [];
270
- const tasksSection = extractSection(content, 'Tasks');
305
+ const tasksSection = extractSection(body, 'Tasks');
271
306
  const tasks = [];
272
307
  if (tasksSection) {
273
308
  const taskLines = tasksSection.split('\n');
@@ -315,7 +350,7 @@ function _parsePlanImpl(content) {
315
350
  if (currentTask)
316
351
  tasks.push(currentTask);
317
352
  }
318
- const filesSection = extractSection(content, 'Files Likely Touched');
353
+ const filesSection = extractSection(body, 'Files Likely Touched');
319
354
  const filesLikelyTouched = filesSection ? parseBullets(filesSection) : [];
320
355
  const result = { id, title, goal, demo, mustHaves, tasks, filesLikelyTouched };
321
356
  stopTimer({ tasks: tasks.length });
@@ -692,6 +727,10 @@ export function extractUatType(content) {
692
727
  const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
693
728
  if (rawValue.startsWith('artifact-driven'))
694
729
  return 'artifact-driven';
730
+ if (rawValue.startsWith('browser-executable'))
731
+ return 'browser-executable';
732
+ if (rawValue.startsWith('runtime-executable'))
733
+ return 'runtime-executable';
695
734
  if (rawValue.startsWith('live-runtime'))
696
735
  return 'live-runtime';
697
736
  if (rawValue.startsWith('human-experience'))
@@ -21,7 +21,7 @@ import { deriveState } from "./state.js";
21
21
  import { isAutoActive } from "./auto.js";
22
22
  import { loadPrompt } from "./prompt-loader.js";
23
23
  import { gsdRoot } from "./paths.js";
24
- import { formatDuration } from "../shared/mod.js";
24
+ import { formatDuration } from "../shared/format-utils.js";
25
25
  import { getAutoWorktreePath } from "./auto-worktree.js";
26
26
  // ─── Entry Point ──────────────────────────────────────────────────────────────
27
27
  export async function handleForensics(args, ctx, pi) {