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,14 +1,15 @@
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
 
4
4
  import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
5
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
5
+ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
6
6
  import { deriveState, isMilestoneComplete } from "./state.js";
7
7
  import { invalidateAllCaches } from "./cache.js";
8
8
  import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
9
9
 
10
- import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
10
+ import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
11
11
  import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
12
+ import type { RoadmapSliceEntry } from "./types.js";
12
13
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
13
14
  import { checkEnvironmentHealth } from "./doctor-environment.js";
14
15
  import { runProviderChecks } from "./doctor-providers.js";
@@ -17,7 +18,7 @@ import { runProviderChecks } from "./doctor-providers.js";
17
18
  // All public types and functions from extracted modules are re-exported here
18
19
  // so that existing imports from "./doctor.js" continue to work unchanged.
19
20
  export type { DoctorSeverity, DoctorIssueCode, DoctorIssue, DoctorReport, DoctorSummary } from "./doctor-types.js";
20
- export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
21
+ export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
21
22
  export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport, type EnvironmentCheckResult } from "./doctor-environment.js";
22
23
  export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport, type ProgressScore, type ProgressLevel } from "./progress-score.js";
23
24
 
@@ -279,9 +280,24 @@ async function markSliceDoneInRoadmap(basePath: string, milestoneId: string, sli
279
280
  }
280
281
  }
281
282
 
283
+ async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, sliceId: string, fixesApplied: string[]): Promise<void> {
284
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
285
+ if (!roadmapPath) return;
286
+ const content = await loadFile(roadmapPath);
287
+ if (!content) return;
288
+ const updated = content.replace(
289
+ new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"),
290
+ `$1[ ] **${sliceId}:`,
291
+ );
292
+ if (updated !== content) {
293
+ await saveFile(roadmapPath, updated);
294
+ fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
295
+ }
296
+ }
297
+
282
298
  function matchesScope(unitId: string, scope?: string): boolean {
283
299
  if (!scope) return true;
284
- return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
300
+ return unitId === scope || unitId.startsWith(`${scope}/`);
285
301
  }
286
302
 
287
303
  function auditRequirements(content: string | null): DoctorIssue[] {
@@ -350,10 +366,104 @@ export async function selectDoctorScope(basePath: string, requestedScope?: strin
350
366
  return state.registry[0]?.id;
351
367
  }
352
368
 
353
- export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch" }): Promise<import("./doctor-types.js").DoctorReport> {
369
+ // ── Helper: circular dependency detection ──────────────────────────────────
370
+ function detectCircularDependencies(slices: RoadmapSliceEntry[]): string[][] {
371
+ const known = new Set(slices.map(s => s.id));
372
+ const adj = new Map<string, string[]>();
373
+ for (const s of slices) adj.set(s.id, s.depends.filter(d => known.has(d)));
374
+ const state = new Map<string, "unvisited" | "visiting" | "done">();
375
+ for (const s of slices) state.set(s.id, "unvisited");
376
+ const cycles: string[][] = [];
377
+ function dfs(id: string, path: string[]): void {
378
+ const st = state.get(id);
379
+ if (st === "done") return;
380
+ if (st === "visiting") { cycles.push([...path.slice(path.indexOf(id)), id]); return; }
381
+ state.set(id, "visiting");
382
+ for (const dep of adj.get(id) ?? []) dfs(dep, [...path, id]);
383
+ state.set(id, "done");
384
+ }
385
+ for (const s of slices) if (state.get(s.id) === "unvisited") dfs(s.id, []);
386
+ return cycles;
387
+ }
388
+
389
+ // ── Helper: doctor run history ──────────────────────────────────────────────
390
+ export interface DoctorHistoryEntry {
391
+ ts: string;
392
+ ok: boolean;
393
+ errors: number;
394
+ warnings: number;
395
+ fixes: number;
396
+ codes: string[];
397
+ /** Issue messages with severity and scope (added in Phase 2). */
398
+ issues?: Array<{ severity: string; code: string; message: string; unitId: string }>;
399
+ /** Fix descriptions applied during this run (added in Phase 2). */
400
+ fixDescriptions?: string[];
401
+ /** Milestone/slice scope this doctor run was scoped to (e.g. "M001/S02"). */
402
+ scope?: string;
403
+ /** Human-readable one-line summary of this doctor run. */
404
+ summary?: string;
405
+ }
406
+
407
+ async function appendDoctorHistory(basePath: string, report: DoctorReport): Promise<void> {
408
+ try {
409
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
410
+ const errorCount = report.issues.filter(i => i.severity === "error").length;
411
+ const warningCount = report.issues.filter(i => i.severity === "warning").length;
412
+ const issueDetails = report.issues
413
+ .filter(i => i.severity === "error" || i.severity === "warning")
414
+ .slice(0, 10) // cap to keep JSONL lines bounded
415
+ .map(i => ({ severity: i.severity, code: i.code, message: i.message, unitId: i.unitId }));
416
+
417
+ // Human-readable one-line summary
418
+ const summaryParts: string[] = [];
419
+ if (report.ok) {
420
+ summaryParts.push("Clean");
421
+ } else {
422
+ const counts: string[] = [];
423
+ if (errorCount > 0) counts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
424
+ if (warningCount > 0) counts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
425
+ summaryParts.push(counts.join(", "));
426
+ }
427
+ if (report.fixesApplied.length > 0) {
428
+ summaryParts.push(`${report.fixesApplied.length} fixed`);
429
+ }
430
+ if (issueDetails.length > 0) {
431
+ const topIssue = issueDetails.find(i => i.severity === "error") ?? issueDetails[0]!;
432
+ summaryParts.push(topIssue.message);
433
+ }
434
+
435
+ const entry = JSON.stringify({
436
+ ts: new Date().toISOString(),
437
+ ok: report.ok,
438
+ errors: errorCount,
439
+ warnings: warningCount,
440
+ fixes: report.fixesApplied.length,
441
+ codes: [...new Set(report.issues.map(i => i.code))],
442
+ issues: issueDetails.length > 0 ? issueDetails : undefined,
443
+ fixDescriptions: report.fixesApplied.length > 0 ? report.fixesApplied : undefined,
444
+ scope: (report as any).scope as string | undefined,
445
+ summary: summaryParts.join(" · "),
446
+ } satisfies DoctorHistoryEntry);
447
+ const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
448
+ await saveFile(historyPath, existing + entry + "\n");
449
+ } catch { /* non-fatal */ }
450
+ }
451
+
452
+ /** Read the last N doctor history entries. Returns most-recent-first. */
453
+ export async function readDoctorHistory(basePath: string, lastN = 50): Promise<DoctorHistoryEntry[]> {
454
+ try {
455
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
456
+ if (!existsSync(historyPath)) return [];
457
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
458
+ return lines.slice(-lastN).reverse().map(l => JSON.parse(l) as DoctorHistoryEntry);
459
+ } catch { return []; }
460
+ }
461
+
462
+ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; dryRun?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch"; includeBuild?: boolean; includeTests?: boolean }): Promise<DoctorReport> {
354
463
  const issues: DoctorIssue[] = [];
355
464
  const fixesApplied: string[] = [];
356
465
  const fix = options?.fix === true;
466
+ const dryRun = options?.dryRun === true;
357
467
  const fixLevel = options?.fixLevel ?? "all";
358
468
 
359
469
  // Issue codes that represent completion state transitions — creating summary
@@ -364,11 +474,18 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
364
474
 
365
475
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
366
476
  const shouldFix = (code: DoctorIssueCode): boolean => {
367
- if (!fix) return false;
477
+ if (!fix || dryRun) return false;
368
478
  if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false;
369
479
  return true;
370
480
  };
371
481
 
482
+ /** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
483
+ const dryRunCanFix = (code: DoctorIssueCode, message: string): void => {
484
+ if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
485
+ fixesApplied.push(`[dry-run] would fix: ${message}`);
486
+ }
487
+ };
488
+
372
489
  const prefs = loadEffectiveGSDPreferences();
373
490
  if (prefs) {
374
491
  const prefIssues = validatePreferenceShape(prefs.preferences);
@@ -385,21 +502,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
385
502
  }
386
503
  }
387
504
 
388
- // Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files)
505
+ // Git health checks timed
506
+ const t0git = Date.now();
389
507
  const isolationMode: "none" | "worktree" | "branch" = options?.isolationMode ??
390
508
  (prefs?.preferences?.git?.isolation === "none" ? "none" :
391
509
  prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
392
510
  await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
511
+ const gitMs = Date.now() - t0git;
393
512
 
394
- // Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore)
513
+ // Runtime health checks timed
514
+ const t0runtime = Date.now();
395
515
  await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
516
+ const runtimeMs = Date.now() - t0runtime;
396
517
 
397
- // Environment health checks (#1221: missing tools, port conflicts, stale deps, disk space)
398
- await checkEnvironmentHealth(basePath, issues, { includeRemote: !options?.scope });
518
+ // Environment health checks timed
519
+ const t0env = Date.now();
520
+ await checkEnvironmentHealth(basePath, issues, {
521
+ includeRemote: !options?.scope,
522
+ includeBuild: options?.includeBuild,
523
+ includeTests: options?.includeTests,
524
+ });
525
+ const envMs = Date.now() - t0env;
399
526
 
400
527
  const milestonesPath = milestonesDir(basePath);
401
528
  if (!existsSync(milestonesPath)) {
402
- return { ok: issues.every(issue => issue.severity !== "error"), basePath, issues, fixesApplied };
529
+ const report: DoctorReport = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
530
+ await appendDoctorHistory(basePath, report);
531
+ return report;
403
532
  }
404
533
 
405
534
  const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
@@ -465,6 +594,43 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
465
594
  if (!roadmapContent) continue;
466
595
  const roadmap = parseRoadmap(roadmapContent);
467
596
 
597
+ // ── Circular dependency detection ──────────────────────────────────────
598
+ for (const cycle of detectCircularDependencies(roadmap.slices)) {
599
+ issues.push({
600
+ severity: "error",
601
+ code: "circular_slice_dependency",
602
+ scope: "milestone",
603
+ unitId: milestoneId,
604
+ message: `Circular dependency detected: ${cycle.join(" → ")}`,
605
+ file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
606
+ fixable: false,
607
+ });
608
+ }
609
+
610
+ // ── Orphaned slice directories ─────────────────────────────────────────
611
+ try {
612
+ const slicesDir = join(milestonePath, "slices");
613
+ if (existsSync(slicesDir)) {
614
+ const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
615
+ for (const entry of readdirSync(slicesDir)) {
616
+ try {
617
+ if (!lstatSync(join(slicesDir, entry)).isDirectory()) continue;
618
+ } catch { continue; }
619
+ if (!knownSliceIds.has(entry)) {
620
+ issues.push({
621
+ severity: "warning",
622
+ code: "orphaned_slice_directory",
623
+ scope: "milestone",
624
+ unitId: milestoneId,
625
+ message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
626
+ file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
627
+ fixable: false,
628
+ });
629
+ }
630
+ }
631
+ }
632
+ } catch { /* non-fatal */ }
633
+
468
634
  for (const slice of roadmap.slices) {
469
635
  const unitId = `${milestoneId}/${slice.id}`;
470
636
  if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId) continue;
@@ -539,6 +705,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
539
705
  continue;
540
706
  }
541
707
 
708
+ // ── Duplicate task IDs ───────────────────────────────────────────────
709
+ const taskIdCounts = new Map<string, number>();
710
+ for (const task of plan.tasks) taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
711
+ for (const [taskId, count] of taskIdCounts) {
712
+ if (count > 1) {
713
+ issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
714
+ message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
715
+ file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
716
+ }
717
+ }
718
+
719
+ // ── Task files on disk not in plan ────────────────────────────────────
720
+ try {
721
+ if (tasksDir) {
722
+ const planTaskIds = new Set(plan.tasks.map(t => t.id));
723
+ for (const f of readdirSync(tasksDir)) {
724
+ if (!f.endsWith("-SUMMARY.md")) continue;
725
+ const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
726
+ if (!planTaskIds.has(diskTaskId)) {
727
+ issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
728
+ message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
729
+ file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
730
+ }
731
+ }
732
+ }
733
+ } catch { /* non-fatal */ }
734
+
542
735
  let allTasksDone = plan.tasks.length > 0;
543
736
  for (const task of plan.tasks) {
544
737
  const taskUnitId = `${unitId}/${task.id}`;
@@ -555,6 +748,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
555
748
  file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
556
749
  fixable: true,
557
750
  });
751
+ dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
558
752
  if (shouldFix("task_done_missing_summary")) {
559
753
  const stubPath = join(
560
754
  basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks",
@@ -618,6 +812,22 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
618
812
  }
619
813
  }
620
814
 
815
+ // ── Future timestamp check ─────────────────────────────────────
816
+ if (task.done && hasSummary && summaryPath) {
817
+ try {
818
+ const rawSummary = await loadFile(summaryPath);
819
+ const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
820
+ if (m) {
821
+ const ts = new Date(m[1].trim());
822
+ if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
823
+ issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
824
+ message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
825
+ file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
826
+ }
827
+ }
828
+ } catch { /* non-fatal */ }
829
+ }
830
+
621
831
  allTasksDone = allTasksDone && task.done;
622
832
  }
623
833
 
@@ -646,6 +856,13 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
646
856
  }
647
857
  }
648
858
 
859
+ // ── Stale REPLAN: exists but all tasks done ────────────────────────
860
+ if (replanPath && allTasksDone) {
861
+ issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
862
+ message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
863
+ file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
864
+ }
865
+
649
866
  const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
650
867
  const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
651
868
  const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
@@ -661,6 +878,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
661
878
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
662
879
  fixable: true,
663
880
  });
881
+ dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
664
882
  if (shouldFix("all_tasks_done_missing_slice_summary")) await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
665
883
  }
666
884
 
@@ -674,6 +892,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
674
892
  file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
675
893
  fixable: true,
676
894
  });
895
+ dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
677
896
  if (shouldFix("all_tasks_done_missing_slice_uat")) await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
678
897
  }
679
898
 
@@ -687,6 +906,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
687
906
  file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
688
907
  fixable: true,
689
908
  });
909
+ dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
690
910
  if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
691
911
  await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
692
912
  }
@@ -702,6 +922,12 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
702
922
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
703
923
  fixable: true,
704
924
  });
925
+ if (!allTasksDone) {
926
+ dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
927
+ if (shouldFix("slice_checked_missing_summary")) {
928
+ await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
929
+ }
930
+ }
705
931
  }
706
932
 
707
933
  if (slice.done && !hasSliceUat) {
@@ -744,14 +970,17 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
744
970
  }
745
971
  }
746
972
 
747
- if (fix && fixesApplied.length > 0) {
973
+ if (fix && !dryRun && fixesApplied.length > 0) {
748
974
  await updateStateFile(basePath, fixesApplied);
749
975
  }
750
976
 
751
- return {
977
+ const report: DoctorReport = {
752
978
  ok: issues.every(issue => issue.severity !== "error"),
753
979
  basePath,
754
980
  issues,
755
981
  fixesApplied,
982
+ timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
756
983
  };
984
+ await appendDoctorHistory(basePath, report);
985
+ return report;
757
986
  }
@@ -0,0 +1,31 @@
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
+
8
+ import { readFile } from "node:fs/promises";
9
+
10
+ /**
11
+ * Check which keys already exist in a .env file or process.env.
12
+ * Returns the subset of `keys` that are already set.
13
+ */
14
+ export async function checkExistingEnvKeys(keys: string[], envFilePath: string): Promise<string[]> {
15
+ let fileContent = "";
16
+ try {
17
+ fileContent = await readFile(envFilePath, "utf8");
18
+ } catch {
19
+ // ENOENT or other read error — proceed with empty content
20
+ }
21
+
22
+ const existing: string[] = [];
23
+ for (const key of keys) {
24
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
+ const regex = new RegExp(`^${escaped}\\s*=`, "m");
26
+ if (regex.test(fileContent) || key in process.env) {
27
+ existing.push(key);
28
+ }
29
+ }
30
+ return existing;
31
+ }
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
1
+ import { importExtensionModule, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
2
 
3
3
  type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI, reason?: string) => Promise<void>;
4
4
 
@@ -10,7 +10,7 @@ export function registerExitCommand(
10
10
  description: "Exit GSD gracefully",
11
11
  handler: async (_args: string, ctx: ExtensionCommandContext) => {
12
12
  // Stop auto-mode first so locks and activity state are cleaned up before shutdown.
13
- const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
13
+ const stopAuto = deps.stopAuto ?? (await importExtensionModule<typeof import("./auto.js")>(import.meta.url, "./auto.js")).stopAuto;
14
14
  await stopAuto(ctx, pi, "Graceful exit");
15
15
  ctx.shutdown();
16
16
  },
@@ -296,9 +296,60 @@ function buildHealthSection(data: VisualizerData): string {
296
296
  </tbody>
297
297
  </table>` : '';
298
298
 
299
+ // Progress score section
300
+ let progressHtml = '';
301
+ if (h.progressScore) {
302
+ const ps = h.progressScore;
303
+ const scoreColor = ps.level === 'green' ? '#22c55e' : ps.level === 'yellow' ? '#eab308' : '#ef4444';
304
+ const signalRows = ps.signals.map(s => {
305
+ const icon = s.kind === 'positive' ? '✓' : s.kind === 'negative' ? '✗' : '·';
306
+ const color = s.kind === 'positive' ? '#22c55e' : s.kind === 'negative' ? '#ef4444' : '#888';
307
+ return `<div style="margin-left:1em;color:${color}">${icon} ${esc(s.label)}</div>`;
308
+ }).join('');
309
+ progressHtml = `
310
+ <h3>Progress Score</h3>
311
+ <div style="font-size:1.1em;font-weight:bold;color:${scoreColor}">● ${esc(ps.summary)}</div>
312
+ ${signalRows}`;
313
+ }
314
+
315
+ // Doctor history section
316
+ let historyHtml = '';
317
+ const doctorHistory = h.doctorHistory ?? [];
318
+ if (doctorHistory.length > 0) {
319
+ const historyRows = doctorHistory.slice(0, 20).map(entry => {
320
+ const statusIcon = entry.ok ? '✓' : '✗';
321
+ const statusColor = entry.ok ? '#22c55e' : '#ef4444';
322
+ const ts = entry.ts.replace('T', ' ').slice(0, 19);
323
+ const scopeTag = entry.scope ? `<span class="mono" style="color:#888"> [${esc(entry.scope)}]</span>` : '';
324
+ const summaryText = entry.summary ? esc(entry.summary) : `${entry.errors} errors, ${entry.warnings} warnings, ${entry.fixes} fixes`;
325
+ const issueDetails = (entry.issues ?? []).slice(0, 3).map(i => {
326
+ const iColor = i.severity === 'error' ? '#ef4444' : '#eab308';
327
+ 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>`;
328
+ }).join('');
329
+ const fixDetails = (entry.fixDescriptions ?? []).slice(0, 2).map(f =>
330
+ `<div style="margin-left:2em;color:#22c55e;font-size:0.85em">↳ ${esc(f)}</div>`
331
+ ).join('');
332
+ return `<tr style="color:${statusColor}">
333
+ <td class="mono">${statusIcon}</td>
334
+ <td class="mono">${esc(ts)}${scopeTag}</td>
335
+ <td>${summaryText}</td>
336
+ </tr>
337
+ ${issueDetails || fixDetails ? `<tr><td colspan="3">${issueDetails}${fixDetails}</td></tr>` : ''}`;
338
+ }).join('');
339
+
340
+ historyHtml = `
341
+ <h3>Doctor Run History</h3>
342
+ <table class="tbl">
343
+ <thead><tr><th></th><th>Time</th><th>Summary</th></tr></thead>
344
+ <tbody>${historyRows}</tbody>
345
+ </table>`;
346
+ }
347
+
299
348
  return section('health', 'Health', `
300
349
  <table class="tbl tbl-kv"><tbody>${rows.join('')}</tbody></table>
301
350
  ${tierRows}
351
+ ${progressHtml}
352
+ ${historyHtml}
302
353
  `);
303
354
  }
304
355
 
@@ -11,7 +11,7 @@ import {
11
11
  } from "./metrics.js";
12
12
  import type { UnitMetrics } from "./metrics.js";
13
13
  import { gsdRoot } from "./paths.js";
14
- import { formatDuration, fileLink } from "../shared/mod.js";
14
+ import { formatDuration, fileLink } from "../shared/format-utils.js";
15
15
  import { getErrorMessage } from "./error-utils.js";
16
16
 
17
17
  /**
@@ -7,11 +7,11 @@ import { promises as fs } from 'node:fs';
7
7
  import { resolve } from 'node:path';
8
8
  import { atomicWriteAsync } from './atomic-write.js';
9
9
  import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
10
- import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
10
+ import { milestoneIdSort, findMilestoneIds } from './milestone-ids.js';
11
11
 
12
12
  import type {
13
13
  Roadmap, BoundaryMapEntry,
14
- SlicePlan, TaskPlanEntry,
14
+ SlicePlan, TaskPlanEntry, TaskPlanFile, TaskPlanFrontmatter,
15
15
  Summary, SummaryFrontmatter, SummaryRequires, FileModified,
16
16
  Continue, ContinueFrontmatter, ContinueStatus,
17
17
  RequirementCounts,
@@ -20,7 +20,7 @@ import type {
20
20
  ManifestStatus,
21
21
  } from './types.js';
22
22
 
23
- import { checkExistingEnvKeys } from '../get-secrets-from-user.js';
23
+ import { checkExistingEnvKeys } from './env-utils.js';
24
24
  import { parseRoadmapSlices } from './roadmap-slices.js';
25
25
  import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
26
26
  import { debugTime, debugCount } from './debug-logger.js';
@@ -277,14 +277,52 @@ export function formatSecretsManifest(manifest: SecretsManifest): string {
277
277
 
278
278
  // ─── Slice Plan Parser ─────────────────────────────────────────────────────
279
279
 
280
+ function normalizeTaskPlanFrontmatter(frontmatter: Record<string, unknown>): TaskPlanFrontmatter {
281
+ const estimatedStepsRaw = frontmatter.estimated_steps;
282
+ const estimatedFilesRaw = frontmatter.estimated_files;
283
+ const skillsUsedRaw = frontmatter.skills_used;
284
+
285
+ const parseOptionalNumber = (value: unknown): number | undefined => {
286
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
287
+ if (typeof value === 'string' && value.trim()) {
288
+ const parsed = parseInt(value, 10);
289
+ if (Number.isFinite(parsed)) return parsed;
290
+ }
291
+ return undefined;
292
+ };
293
+
294
+ const estimated_steps = parseOptionalNumber(estimatedStepsRaw);
295
+ const estimated_files = parseOptionalNumber(estimatedFilesRaw);
296
+ const skills_used = Array.isArray(skillsUsedRaw)
297
+ ? skillsUsedRaw.map(v => String(v).trim()).filter(Boolean)
298
+ : typeof skillsUsedRaw === 'string' && skillsUsedRaw.trim()
299
+ ? [skillsUsedRaw.trim()]
300
+ : [];
301
+
302
+ return {
303
+ ...(estimated_steps !== undefined ? { estimated_steps } : {}),
304
+ ...(estimated_files !== undefined ? { estimated_files } : {}),
305
+ skills_used,
306
+ };
307
+ }
308
+
309
+ export function parseTaskPlanFile(content: string): TaskPlanFile {
310
+ const [fmLines] = splitFrontmatter(content);
311
+ const fm = fmLines ? parseFrontmatterMap(fmLines) : {};
312
+ return {
313
+ frontmatter: normalizeTaskPlanFrontmatter(fm),
314
+ };
315
+ }
316
+
280
317
  export function parsePlan(content: string): SlicePlan {
281
318
  return cachedParse(content, 'plan', _parsePlanImpl);
282
319
  }
283
320
 
284
321
  function _parsePlanImpl(content: string): SlicePlan {
285
322
  const stopTimer = debugTime("parse-plan");
323
+ const [, body] = splitFrontmatter(content);
286
324
  // Try native parser first for better performance
287
- const nativeResult = nativeParsePlanFile(content);
325
+ const nativeResult = nativeParsePlanFile(body);
288
326
  if (nativeResult) {
289
327
  stopTimer({ native: true });
290
328
  return {
@@ -306,7 +344,7 @@ function _parsePlanImpl(content: string): SlicePlan {
306
344
  };
307
345
  }
308
346
 
309
- const lines = content.split('\n');
347
+ const lines = body.split('\n');
310
348
 
311
349
  const h1 = lines.find(l => l.startsWith('# '));
312
350
  let id = '';
@@ -321,13 +359,13 @@ function _parsePlanImpl(content: string): SlicePlan {
321
359
  }
322
360
  }
323
361
 
324
- const goal = extractBoldField(content, 'Goal') || '';
325
- const demo = extractBoldField(content, 'Demo') || '';
362
+ const goal = extractBoldField(body, 'Goal') || '';
363
+ const demo = extractBoldField(body, 'Demo') || '';
326
364
 
327
- const mhSection = extractSection(content, 'Must-Haves');
365
+ const mhSection = extractSection(body, 'Must-Haves');
328
366
  const mustHaves = mhSection ? parseBullets(mhSection) : [];
329
367
 
330
- const tasksSection = extractSection(content, 'Tasks');
368
+ const tasksSection = extractSection(body, 'Tasks');
331
369
  const tasks: TaskPlanEntry[] = [];
332
370
 
333
371
  if (tasksSection) {
@@ -375,7 +413,7 @@ function _parsePlanImpl(content: string): SlicePlan {
375
413
  if (currentTask) tasks.push(currentTask);
376
414
  }
377
415
 
378
- const filesSection = extractSection(content, 'Files Likely Touched');
416
+ const filesSection = extractSection(body, 'Files Likely Touched');
379
417
  const filesLikelyTouched = filesSection ? parseBullets(filesSection) : [];
380
418
 
381
419
  const result = { id, title, goal, demo, mustHaves, tasks, filesLikelyTouched };
@@ -775,7 +813,7 @@ export function parseTaskPlanIO(content: string): { inputFiles: string[]; output
775
813
  * The four UAT classification types recognised by GSD auto-mode.
776
814
  * `undefined` is returned (not this union) when no type can be determined.
777
815
  */
778
- export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed';
816
+ export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed' | 'browser-executable' | 'runtime-executable';
779
817
 
780
818
  /**
781
819
  * Extract the UAT type from a UAT file's raw content.
@@ -799,6 +837,8 @@ export function extractUatType(content: string): UatType | undefined {
799
837
  const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
800
838
 
801
839
  if (rawValue.startsWith('artifact-driven')) return 'artifact-driven';
840
+ if (rawValue.startsWith('browser-executable')) return 'browser-executable';
841
+ if (rawValue.startsWith('runtime-executable')) return 'runtime-executable';
802
842
  if (rawValue.startsWith('live-runtime')) return 'live-runtime';
803
843
  if (rawValue.startsWith('human-experience')) return 'human-experience';
804
844
  if (rawValue.startsWith('mixed')) return 'mixed';
@@ -27,7 +27,7 @@ import { deriveState } from "./state.js";
27
27
  import { isAutoActive } from "./auto.js";
28
28
  import { loadPrompt } from "./prompt-loader.js";
29
29
  import { gsdRoot } from "./paths.js";
30
- import { formatDuration } from "../shared/mod.js";
30
+ import { formatDuration } from "../shared/format-utils.js";
31
31
  import { getAutoWorktreePath } from "./auto-worktree.js";
32
32
 
33
33
  // ─── Types ────────────────────────────────────────────────────────────────────