gsd-pi 2.29.0-dev.49d972f → 2.29.0-dev.4c155ee

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 (256) hide show
  1. package/README.md +24 -17
  2. package/dist/headless.js +4 -0
  3. package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
  4. package/dist/resources/extensions/gsd/auto-dashboard.ts +217 -65
  5. package/dist/resources/extensions/gsd/auto-dispatch.ts +32 -3
  6. package/dist/resources/extensions/gsd/auto-post-unit.ts +44 -12
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +40 -17
  8. package/dist/resources/extensions/gsd/auto-recovery.ts +2 -1
  9. package/dist/resources/extensions/gsd/auto-start.ts +18 -32
  10. package/dist/resources/extensions/gsd/auto-worktree.ts +21 -182
  11. package/dist/resources/extensions/gsd/auto.ts +2 -9
  12. package/dist/resources/extensions/gsd/captures.ts +4 -10
  13. package/dist/resources/extensions/gsd/commands-handlers.ts +2 -1
  14. package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +44 -14
  15. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  16. package/dist/resources/extensions/gsd/commands.ts +55 -2
  17. package/dist/resources/extensions/gsd/detection.ts +2 -1
  18. package/dist/resources/extensions/gsd/doctor-checks.ts +49 -1
  19. package/dist/resources/extensions/gsd/doctor-types.ts +3 -1
  20. package/dist/resources/extensions/gsd/forensics.ts +2 -2
  21. package/dist/resources/extensions/gsd/git-service.ts +3 -2
  22. package/dist/resources/extensions/gsd/gitignore.ts +9 -63
  23. package/dist/resources/extensions/gsd/gsd-db.ts +1 -165
  24. package/dist/resources/extensions/gsd/guided-flow.ts +8 -5
  25. package/dist/resources/extensions/gsd/index.ts +3 -3
  26. package/dist/resources/extensions/gsd/md-importer.ts +3 -2
  27. package/dist/resources/extensions/gsd/mechanical-completion.ts +430 -0
  28. package/dist/resources/extensions/gsd/migrate/command.ts +3 -2
  29. package/dist/resources/extensions/gsd/migrate/writer.ts +2 -1
  30. package/dist/resources/extensions/gsd/migrate-external.ts +123 -0
  31. package/dist/resources/extensions/gsd/paths.ts +24 -2
  32. package/dist/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  33. package/dist/resources/extensions/gsd/preferences-models.ts +7 -1
  34. package/dist/resources/extensions/gsd/preferences-validation.ts +2 -1
  35. package/dist/resources/extensions/gsd/preferences.ts +10 -5
  36. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  37. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  39. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  40. package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  41. package/dist/resources/extensions/gsd/repo-identity.ts +148 -0
  42. package/dist/resources/extensions/gsd/resource-version.ts +99 -0
  43. package/dist/resources/extensions/gsd/session-forensics.ts +4 -3
  44. package/dist/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  45. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  46. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  47. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  48. package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  49. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  50. package/dist/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  51. package/dist/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  52. package/dist/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  53. package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  54. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  55. package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  56. package/dist/resources/extensions/gsd/triage-resolution.ts +2 -1
  57. package/dist/resources/extensions/gsd/types.ts +2 -0
  58. package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  59. package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  60. package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  61. package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  62. package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  63. package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  64. package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  65. package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  66. package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  67. package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
  68. package/dist/resources/extensions/gsd/worktree-command.ts +1 -11
  69. package/dist/resources/extensions/gsd/worktree-manager.ts +3 -2
  70. package/dist/resources/extensions/gsd/worktree.ts +42 -5
  71. package/dist/resources/extensions/mcp-client/index.ts +459 -0
  72. package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
  73. package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  74. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  75. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  76. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  77. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  78. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  79. package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  80. package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  81. package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  82. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  83. package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  84. package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  85. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  86. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  87. package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  88. package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  89. package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  90. package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  91. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  92. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  93. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  94. package/dist/resources/skills/create-skill/SKILL.md +184 -0
  95. package/dist/resources/skills/create-skill/references/api-security.md +226 -0
  96. package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  97. package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
  98. package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
  99. package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
  100. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  101. package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  102. package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
  103. package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
  104. package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  105. package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
  106. package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
  107. package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  108. package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
  109. package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
  110. package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
  111. package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
  112. package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
  113. package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  114. package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  115. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  116. package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  117. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  118. package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  119. package/dist/resources/skills/react-best-practices/SKILL.md +1 -1
  120. package/package.json +1 -1
  121. package/packages/native/dist/native.d.ts +2 -0
  122. package/packages/native/dist/native.js +19 -5
  123. package/packages/native/src/native.ts +23 -9
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
  126. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -0
  129. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  130. package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
  131. package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -0
  132. package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
  133. package/src/resources/extensions/gsd/auto-dashboard.ts +217 -65
  134. package/src/resources/extensions/gsd/auto-dispatch.ts +32 -3
  135. package/src/resources/extensions/gsd/auto-post-unit.ts +44 -12
  136. package/src/resources/extensions/gsd/auto-prompts.ts +40 -17
  137. package/src/resources/extensions/gsd/auto-recovery.ts +2 -1
  138. package/src/resources/extensions/gsd/auto-start.ts +18 -32
  139. package/src/resources/extensions/gsd/auto-worktree.ts +21 -182
  140. package/src/resources/extensions/gsd/auto.ts +2 -9
  141. package/src/resources/extensions/gsd/captures.ts +4 -10
  142. package/src/resources/extensions/gsd/commands-handlers.ts +2 -1
  143. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +44 -14
  144. package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  145. package/src/resources/extensions/gsd/commands.ts +55 -2
  146. package/src/resources/extensions/gsd/detection.ts +2 -1
  147. package/src/resources/extensions/gsd/doctor-checks.ts +49 -1
  148. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  149. package/src/resources/extensions/gsd/forensics.ts +2 -2
  150. package/src/resources/extensions/gsd/git-service.ts +3 -2
  151. package/src/resources/extensions/gsd/gitignore.ts +9 -63
  152. package/src/resources/extensions/gsd/gsd-db.ts +1 -165
  153. package/src/resources/extensions/gsd/guided-flow.ts +8 -5
  154. package/src/resources/extensions/gsd/index.ts +3 -3
  155. package/src/resources/extensions/gsd/md-importer.ts +3 -2
  156. package/src/resources/extensions/gsd/mechanical-completion.ts +430 -0
  157. package/src/resources/extensions/gsd/migrate/command.ts +3 -2
  158. package/src/resources/extensions/gsd/migrate/writer.ts +2 -1
  159. package/src/resources/extensions/gsd/migrate-external.ts +123 -0
  160. package/src/resources/extensions/gsd/paths.ts +24 -2
  161. package/src/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  162. package/src/resources/extensions/gsd/preferences-models.ts +7 -1
  163. package/src/resources/extensions/gsd/preferences-validation.ts +2 -1
  164. package/src/resources/extensions/gsd/preferences.ts +10 -5
  165. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  166. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  168. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  169. package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  170. package/src/resources/extensions/gsd/repo-identity.ts +148 -0
  171. package/src/resources/extensions/gsd/resource-version.ts +99 -0
  172. package/src/resources/extensions/gsd/session-forensics.ts +4 -3
  173. package/src/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  174. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  175. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  176. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  177. package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  178. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  179. package/src/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  180. package/src/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  181. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  182. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  183. package/src/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  184. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  185. package/src/resources/extensions/gsd/triage-resolution.ts +2 -1
  186. package/src/resources/extensions/gsd/types.ts +2 -0
  187. package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  188. package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  189. package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  190. package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  191. package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  192. package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  193. package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  194. package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  195. package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  196. package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
  197. package/src/resources/extensions/gsd/worktree-command.ts +1 -11
  198. package/src/resources/extensions/gsd/worktree-manager.ts +3 -2
  199. package/src/resources/extensions/gsd/worktree.ts +42 -5
  200. package/src/resources/extensions/mcp-client/index.ts +459 -0
  201. package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
  202. package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  203. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  204. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  205. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  206. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  207. package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  208. package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  209. package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  210. package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  211. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  212. package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  213. package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  214. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  215. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  216. package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  217. package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  218. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  219. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  220. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  221. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  222. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  223. package/src/resources/skills/create-skill/SKILL.md +184 -0
  224. package/src/resources/skills/create-skill/references/api-security.md +226 -0
  225. package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  226. package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
  227. package/src/resources/skills/create-skill/references/core-principles.md +437 -0
  228. package/src/resources/skills/create-skill/references/executable-code.md +175 -0
  229. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  230. package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  231. package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
  232. package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
  233. package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  234. package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
  235. package/src/resources/skills/create-skill/references/using-templates.md +112 -0
  236. package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  237. package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
  238. package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
  239. package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
  240. package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
  241. package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
  242. package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  243. package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  244. package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  245. package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  246. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  247. package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  248. package/src/resources/skills/react-best-practices/SKILL.md +1 -1
  249. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
  250. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  251. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  252. package/dist/resources/extensions/mcporter/index.ts +0 -525
  253. package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
  254. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  255. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  256. package/src/resources/extensions/mcporter/index.ts +0 -525
package/README.md CHANGED
@@ -24,20 +24,21 @@ One command. Walk away. Come back to a built project with clean git history.
24
24
 
25
25
  ---
26
26
 
27
- ## What's New in v2.28
28
-
29
- - **`gsd headless query`**instant JSON snapshot of project state (~50ms, no LLM session)
30
- - **`/gsd update`**update GSD to the latest version without leaving your session
31
- - **`/gsd export --html --all`** generate retrospective HTML reports for all milestones at once
32
- - **Reliability hardening**atomic file writes, OAuth fetch timeouts, RPC exit detection, blob GC, LSP init retry with backoff
33
- - **RPC utilities** now part of the public API for headless/scripted integrations
34
- - **npm** established as the canonical package manager
35
- - **CI/CD Pipeline** — three-stage promotion (Dev → Test → Prod) with automated versioning
36
- - **Docker support** — containerized builds with multi-stage Dockerfile
37
- - **`/gsd keys`** — full API key lifecycle management (list, add, remove, test, rotate, doctor)
38
- - **Milestone parking** — park in-progress milestones to work on something else, unpark to resume
39
- - **Studio** — experimental Electron desktop app (early preview)
27
+ ## What's New in v2.29
28
+
29
+ - **Node.js 24 LTS**CI, Docker, and package config all upgraded to Node 24 (Krypton)
30
+ - **`searchExcludeDirs` setting**blacklist directories from `@` file autocomplete (e.g., `node_modules`, `dist`)
31
+ - **Automated releases** prod-release now auto-generates changelogs, bumps versions, and publishes to npm
32
+ - **`/gsd logs`**browse activity, debug, and metrics logs from within a session
33
+ - **Configurable screenshots** browser-tools now support custom resolution, format, and quality
34
+ - **Pre-commit secret scanning** automatic detection of hardcoded secrets in CI and locally
40
35
  - **Per-project MCP config** — `.gsd/mcp.json` for project-scoped MCP server definitions
36
+ - **API request metrics** — track request counts for Copilot/subscription users
37
+ - **`/gsd keys`** — full API key lifecycle management (list, add, remove, test, rotate, doctor)
38
+ - **Advisory verification gate** — auto-discovered checks (lint/test from package.json) no longer doom-loop on pre-existing errors
39
+ - **Worktree living doc sync** — DECISIONS, REQUIREMENTS, PROJECT, and KNOWLEDGE now sync between worktree and project root
40
+ - **Windows non-ASCII path support** — `cpSync` fallback for usernames with special characters
41
+ - **`needs-discussion` routing** — milestones with draft context now route to the interactive discussion flow instead of stopping
41
42
 
42
43
  See the full [Changelog](./CHANGELOG.md) for details.
43
44
 
@@ -62,6 +63,8 @@ Full documentation is available in the [`docs/`](./docs/) directory:
62
63
  - **[CI/CD Pipeline](./docs/ci-cd-pipeline.md)** — three-stage promotion pipeline (Dev → Test → Prod)
63
64
  - **[VS Code Extension](./vscode-extension/README.md)** — chat participant, sidebar dashboard, RPC integration
64
65
  - **[Visualizer](./docs/visualizer.md)** — workflow visualizer with stats and discussion status
66
+ - **[Remote Questions](./docs/remote-questions.md)** — route decisions to Slack or Discord when human input is needed
67
+ - **[Dynamic Model Routing](./docs/dynamic-model-routing.md)** — complexity-based model selection and budget pressure
65
68
  - **[Migration from v1](./docs/migration.md)** — `.planning` → `.gsd` migration
66
69
 
67
70
  ---
@@ -175,7 +178,7 @@ Auto mode is a state machine driven by files on disk. It reads `.gsd/STATE.md`,
175
178
 
176
179
  9. **Adaptive replanning** — After each slice completes, the roadmap is reassessed. If the work revealed new information that changes the plan, slices are reordered, added, or removed before continuing.
177
180
 
178
- 10. **Verification enforcement** — Configure shell commands (`npm run lint`, `npm run test`, etc.) that run automatically after task execution. Failures trigger auto-fix retries before advancing. Configurable via `verification_commands`, `verification_auto_fix`, and `verification_max_retries` preferences.
181
+ 10. **Verification enforcement** — Configure shell commands (`npm run lint`, `npm run test`, etc.) that run automatically after task execution. Failures trigger auto-fix retries before advancing. Auto-discovered checks from `package.json` run in advisory mode — they log warnings but don't block on pre-existing errors. Configurable via `verification_commands`, `verification_auto_fix`, and `verification_max_retries` preferences.
179
182
 
180
183
  11. **Milestone validation** — After all slices complete, a `validate-milestone` gate compares roadmap success criteria against actual results before sealing the milestone.
181
184
 
@@ -307,6 +310,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
307
310
  | `/gsd cleanup` | Archive phase directories from completed milestones |
308
311
  | `/gsd doctor` | Runtime health checks with auto-fix for common issues |
309
312
  | `/gsd keys` | API key manager — list, add, remove, test, rotate, doctor |
313
+ | `/gsd logs` | Browse activity, debug, and metrics logs |
310
314
  | `/gsd export --html` | Generate HTML report for current or completed milestone |
311
315
  | `/worktree` (`/wt`) | Git worktree lifecycle — create, switch, merge, remove |
312
316
  | `/voice` | Toggle real-time speech-to-text (macOS, Linux) |
@@ -447,6 +451,7 @@ auto_report: true
447
451
  | `verification_max_retries` | Max retries for verification failures (default: 2) |
448
452
  | `require_slice_discussion` | Pause auto-mode before each slice for human discussion review |
449
453
  | `auto_report` | Auto-generate HTML reports after milestone completion (default: true) |
454
+ | `searchExcludeDirs` | Directories to exclude from `@` file autocomplete (e.g., `["node_modules", ".git", "dist"]`) |
450
455
 
451
456
  ### Agent Instructions
452
457
 
@@ -478,7 +483,7 @@ See the full [Token Optimization Guide](./docs/token-optimization.md) for detail
478
483
 
479
484
  ### Bundled Tools
480
485
 
481
- GSD ships with 14 extensions, all loaded automatically:
486
+ GSD ships with 16 extensions, all loaded automatically:
482
487
 
483
488
  | Extension | What it provides |
484
489
  | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
@@ -490,12 +495,14 @@ GSD ships with 14 extensions, all loaded automatically:
490
495
  | **Background Shell** | Long-running process management with readiness detection |
491
496
  | **Subagent** | Delegated tasks with isolated context windows |
492
497
  | **Mac Tools** | macOS native app automation via Accessibility APIs |
493
- | **MCPorter** | Lazy on-demand MCP server integration |
498
+ | **MCP Client** | Native MCP server integration via @modelcontextprotocol/sdk |
494
499
  | **Voice** | Real-time speech-to-text transcription (macOS, Linux — Ubuntu 22.04+) |
495
500
  | **Slash Commands** | Custom command creation |
496
501
  | **LSP** | Language Server Protocol integration — diagnostics, go-to-definition, references, hover, symbols, rename, code actions |
497
502
  | **Ask User Questions** | Structured user input with single/multi-select |
498
503
  | **Secure Env Collect** | Masked secret collection without manual .env editing |
504
+ | **Remote Questions** | Route decisions to Slack/Discord when human input is needed in headless/CI mode |
505
+ | **Universal Config** | Discover and import MCP servers and rules from other AI coding tools |
499
506
 
500
507
  ### Bundled Agents
501
508
 
@@ -597,7 +604,7 @@ gsd (CLI binary)
597
604
 
598
605
  ## Requirements
599
606
 
600
- - **Node.js** ≥ 20.6.0 (22+ recommended)
607
+ - **Node.js** ≥ 22.0.0 (24 LTS recommended)
601
608
  - **An LLM provider** — any of the 20+ supported providers (see [Use Any Model](#use-any-model))
602
609
  - **Git** — initialized automatically if missing
603
610
 
package/dist/headless.js CHANGED
@@ -128,6 +128,10 @@ async function runHeadlessOnce(options, restartCount) {
128
128
  let interrupted = false;
129
129
  const startTime = Date.now();
130
130
  const isNewMilestone = options.command === 'new-milestone';
131
+ // new-milestone involves codebase investigation + artifact writing — needs more time
132
+ if (isNewMilestone && options.timeout === 300_000) {
133
+ options.timeout = 600_000; // 10 minutes
134
+ }
131
135
  // Supervised mode cannot share stdin with --context -
132
136
  if (options.supervised && options.context === '-') {
133
137
  process.stderr.write('[headless] Error: --supervised cannot be used with --context - (both require stdin)\n');
@@ -375,6 +375,19 @@ export function cleanupAll(): void {
375
375
  processes.clear();
376
376
  }
377
377
 
378
+ /**
379
+ * Kill all alive, non-persistent bg processes.
380
+ * Called between auto-mode units to prevent orphaned servers from
381
+ * keeping ports bound across task boundaries (#1209).
382
+ */
383
+ export function killSessionProcesses(): void {
384
+ for (const [id, bg] of processes) {
385
+ if (bg.alive && !bg.persistAcrossSessions) {
386
+ killProcess(id, "SIGTERM");
387
+ }
388
+ }
389
+ }
390
+
378
391
  async function waitForProcessExit(bg: BgProcess, timeoutMs: number): Promise<boolean> {
379
392
  if (!bg.alive) return true;
380
393
  await new Promise<void>((resolve) => {
@@ -11,6 +11,7 @@ import type { GSDState } from "./types.js";
11
11
  import { getCurrentBranch } from "./worktree.js";
12
12
  import { getActiveHook } from "./post-unit-hooks.js";
13
13
  import { getLedger, getProjectTotals, formatCost, formatTokenCount, formatTierSavings } from "./metrics.js";
14
+ import { getHealthTrend, getConsecutiveErrorUnits } from "./doctor-proactive.js";
14
15
  import {
15
16
  resolveMilestoneFile,
16
17
  resolveSliceFile,
@@ -204,6 +205,13 @@ export function estimateTimeRemaining(): string | null {
204
205
 
205
206
  // ─── Slice Progress Cache ─────────────────────────────────────────────────────
206
207
 
208
+ /** Cached task detail for the widget task checklist */
209
+ interface CachedTaskDetail {
210
+ id: string;
211
+ title: string;
212
+ done: boolean;
213
+ }
214
+
207
215
  /** Cached slice progress for the widget — avoid async in render */
208
216
  let cachedSliceProgress: {
209
217
  done: number;
@@ -211,6 +219,8 @@ let cachedSliceProgress: {
211
219
  milestoneId: string;
212
220
  /** Real task progress for the active slice, if its plan file exists */
213
221
  activeSliceTasks: { done: number; total: number } | null;
222
+ /** Full task list for the active slice checklist */
223
+ taskDetails: CachedTaskDetail[] | null;
214
224
  } | null = null;
215
225
 
216
226
  export function updateSliceProgressCache(base: string, mid: string, activeSid?: string): void {
@@ -221,6 +231,7 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
221
231
  const roadmap = parseRoadmap(content);
222
232
 
223
233
  let activeSliceTasks: { done: number; total: number } | null = null;
234
+ let taskDetails: CachedTaskDetail[] | null = null;
224
235
  if (activeSid) {
225
236
  try {
226
237
  const planFile = resolveSliceFile(base, mid, activeSid, "PLAN");
@@ -231,6 +242,7 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
231
242
  done: plan.tasks.filter(t => t.done).length,
232
243
  total: plan.tasks.length,
233
244
  };
245
+ taskDetails = plan.tasks.map(t => ({ id: t.id, title: t.title, done: t.done }));
234
246
  }
235
247
  } catch {
236
248
  // Non-fatal — just omit task count
@@ -242,13 +254,19 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
242
254
  total: roadmap.slices.length,
243
255
  milestoneId: mid,
244
256
  activeSliceTasks,
257
+ taskDetails,
245
258
  };
246
259
  } catch {
247
260
  // Non-fatal — widget just won't show progress bar
248
261
  }
249
262
  }
250
263
 
251
- export function getRoadmapSlicesSync(): { done: number; total: number; activeSliceTasks: { done: number; total: number } | null } | null {
264
+ export function getRoadmapSlicesSync(): {
265
+ done: number;
266
+ total: number;
267
+ activeSliceTasks: { done: number; total: number } | null;
268
+ taskDetails: CachedTaskDetail[] | null;
269
+ } | null {
252
270
  return cachedSliceProgress;
253
271
  }
254
272
 
@@ -349,87 +367,84 @@ export function updateProgressWidget(
349
367
  const lines: string[] = [];
350
368
  const pad = INDENT.base;
351
369
 
352
- // ── Line 1: Top bar ───────────────────────────────────────────────
370
+ // ── Top bar ─────────────────────────────────────────────────────
353
371
  lines.push(...ui.bar());
354
372
 
373
+ // ── Header: GSD AUTO ... elapsed ────────────────────────────────
355
374
  const dot = pulseBright
356
375
  ? theme.fg("accent", GLYPH.statusActive)
357
376
  : theme.fg("dim", GLYPH.statusPending);
358
377
  const elapsed = formatAutoElapsed(accessors.getAutoStartTime());
359
378
  const modeTag = accessors.isStepMode() ? "NEXT" : "AUTO";
360
- const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}`;
379
+ const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}`;
361
380
  const headerRight = elapsed ? theme.fg("dim", elapsed) : "";
362
381
  lines.push(rightAlign(headerLeft, headerRight, width));
363
382
 
364
- lines.push("");
365
-
366
- if (mid) {
367
- lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}`, width));
368
- }
369
-
383
+ // ── Context: project · slice · action (merged into one line) ────
384
+ const contextParts: string[] = [];
385
+ if (mid) contextParts.push(theme.fg("dim", mid.title));
370
386
  if (slice && unitType !== "research-milestone" && unitType !== "plan-milestone") {
371
- lines.push(truncateToWidth(
372
- `${pad}${theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`))}`,
373
- width,
374
- ));
387
+ contextParts.push(theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`)));
375
388
  }
376
-
377
- lines.push("");
378
-
379
389
  const isHook = unitType.startsWith("hook/");
380
390
  const target = isHook
381
391
  ? (unitId.split("/").pop() ?? unitId)
382
392
  : (task ? `${task.id}: ${task.title}` : unitId);
383
- const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
393
+ contextParts.push(`${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`);
394
+
384
395
  const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
385
396
  const phaseBadge = `${tierTag}${theme.fg("dim", phaseLabel)}`;
386
- lines.push(rightAlign(actionLeft, phaseBadge, width));
387
- lines.push("");
388
-
389
- if (mid) {
390
- const roadmapSlices = getRoadmapSlicesSync();
391
- if (roadmapSlices) {
392
- const { done, total, activeSliceTasks } = roadmapSlices;
393
- const barWidth = Math.max(8, Math.min(24, Math.floor(width * 0.3)));
394
- const pct = total > 0 ? done / total : 0;
395
- const filled = Math.round(pct * barWidth);
396
- const bar = theme.fg("success", "█".repeat(filled))
397
- + theme.fg("dim", "░".repeat(barWidth - filled));
398
-
399
- let meta = theme.fg("dim", `${done}/${total} slices`);
400
-
401
- if (activeSliceTasks && activeSliceTasks.total > 0) {
402
- // For hooks, show the trigger task number (done), not the next task (done + 1)
403
- const taskNum = isHook
404
- ? Math.max(activeSliceTasks.done, 1)
405
- : Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
406
- meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
407
- }
408
-
409
- // ETA estimate
410
- const eta = estimateTimeRemaining();
411
- if (eta) {
412
- meta += theme.fg("dim", ` · ${eta}`);
413
- }
397
+ const contextLine = contextParts.join(theme.fg("dim", " · "));
398
+ lines.push(rightAlign(`${pad}${contextLine}`, phaseBadge, width));
399
+
400
+ // ── Two-column body ─────────────────────────────────────────────
401
+ // Left: progress, ETA, next, stats (fixed) | Right: task checklist (fixed, adjacent)
402
+ // Both columns sit left-to-center; empty space is on the right.
403
+ const divider = theme.fg("dim", "│");
404
+ const minTwoColWidth = 100;
405
+ const rightColFixed = 44;
406
+ const colGap = 5; // breathing room between columns
407
+ // Left column takes remaining space — no truncation on wide terminals
408
+ const useTwoCol = width >= minTwoColWidth;
409
+ const rightColWidth = useTwoCol ? rightColFixed : 0;
410
+ const leftColWidth = useTwoCol ? width - rightColWidth - colGap : width;
411
+
412
+ const roadmapSlices = mid ? getRoadmapSlicesSync() : null;
413
+
414
+ // Build left column: progress bar, ETA, next step, token stats
415
+ const leftLines: string[] = [];
416
+
417
+ if (roadmapSlices) {
418
+ const { done, total, activeSliceTasks } = roadmapSlices;
419
+ const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
420
+ const pct = total > 0 ? done / total : 0;
421
+ const filled = Math.round(pct * barWidth);
422
+ const bar = theme.fg("success", "█".repeat(filled))
423
+ + theme.fg("dim", "░".repeat(barWidth - filled));
424
+
425
+ let meta = theme.fg("dim", `${done}/${total} slices`);
426
+ if (activeSliceTasks && activeSliceTasks.total > 0) {
427
+ const taskNum = isHook
428
+ ? Math.max(activeSliceTasks.done, 1)
429
+ : Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
430
+ meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
431
+ }
432
+ leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
414
433
 
415
- lines.push(truncateToWidth(`${pad}${bar} ${meta}`, width));
434
+ const eta = estimateTimeRemaining();
435
+ if (eta) {
436
+ leftLines.push(truncateToWidth(`${pad}${theme.fg("dim", eta)}`, leftColWidth));
416
437
  }
417
438
  }
418
439
 
419
- lines.push("");
420
-
421
440
  if (next) {
422
- lines.push(truncateToWidth(
441
+ leftLines.push(truncateToWidth(
423
442
  `${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
424
- width,
443
+ leftColWidth,
425
444
  ));
426
445
  }
427
446
 
428
- // ── Footer info (pwd, tokens, cost, context, model) ──────────────
429
- lines.push("");
430
- lines.push(truncateToWidth(theme.fg("dim", `${pad}${widgetPwd}`), width, theme.fg("dim", "…")));
431
-
432
- // Token stats from current unit session + cumulative cost from metrics
447
+ // Token stats
433
448
  {
434
449
  const cmdCtx = accessors.getCmdCtx();
435
450
  let totalInput = 0, totalOutput = 0;
@@ -464,7 +479,6 @@ export function updateProgressWidget(
464
479
  if (totalOutput) sp.push(`↓${formatWidgetTokens(totalOutput)}`);
465
480
  if (totalCacheRead) sp.push(`R${formatWidgetTokens(totalCacheRead)}`);
466
481
  if (totalCacheWrite) sp.push(`W${formatWidgetTokens(totalCacheWrite)}`);
467
- // Cache hit rate for current unit
468
482
  if (totalCacheRead + totalInput > 0) {
469
483
  const hitRate = Math.round((totalCacheRead / (totalCacheRead + totalInput)) * 100);
470
484
  sp.push(`\u26A1${hitRate}%`);
@@ -483,33 +497,134 @@ export function updateProgressWidget(
483
497
  sp.push(cxDisplay);
484
498
  }
485
499
 
486
- const sLeft = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
500
+ const tokenLine = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
487
501
  .join(theme.fg("dim", " "));
502
+ leftLines.push(truncateToWidth(`${pad}${tokenLine}`, leftColWidth));
488
503
 
489
504
  const modelId = cmdCtx?.model?.id ?? "";
490
505
  const modelProvider = cmdCtx?.model?.provider ?? "";
491
- const modelPhase = phaseLabel ? theme.fg("dim", `[${phaseLabel}] `) : "";
492
506
  const modelDisplay = modelProvider && modelId
493
507
  ? `${modelProvider}/${modelId}`
494
508
  : modelId;
495
- const sRight = modelDisplay
496
- ? `${modelPhase}${theme.fg("dim", modelDisplay)}`
497
- : "";
498
- lines.push(rightAlign(`${pad}${sLeft}`, sRight, width));
509
+ if (modelDisplay) {
510
+ leftLines.push(truncateToWidth(`${pad}${theme.fg("dim", modelDisplay)}`, leftColWidth));
511
+ }
499
512
 
500
- // Dynamic routing savings summary
513
+ // Dynamic routing savings
501
514
  if (mLedger && mLedger.units.some(u => u.tier)) {
502
515
  const savings = formatTierSavings(mLedger.units);
503
516
  if (savings) {
504
- lines.push(truncateToWidth(theme.fg("dim", `${pad}${savings}`), width));
517
+ leftLines.push(truncateToWidth(`${pad}${theme.fg("dim", savings)}`, leftColWidth));
518
+ }
519
+ }
520
+ }
521
+
522
+ // Build right column: task checklist (pegged to right edge)
523
+ const rightLines: string[] = [];
524
+ const taskDetails = roadmapSlices?.taskDetails ?? null;
525
+ const maxVisibleTasks = 8;
526
+ const rpad = " ";
527
+
528
+ if (useTwoCol) {
529
+ if (taskDetails && taskDetails.length > 0) {
530
+ const visibleTasks = taskDetails.slice(0, maxVisibleTasks);
531
+ for (const t of visibleTasks) {
532
+ const isCurrent = task && t.id === task.id;
533
+ const glyph = t.done
534
+ ? theme.fg("success", GLYPH.statusDone)
535
+ : isCurrent
536
+ ? theme.fg("accent", "▸")
537
+ : theme.fg("dim", " ");
538
+ const label = isCurrent
539
+ ? theme.fg("text", `${t.id}: ${t.title}`)
540
+ : t.done
541
+ ? theme.fg("dim", `${t.id}: ${t.title}`)
542
+ : theme.fg("text", `${t.id}: ${t.title}`);
543
+ rightLines.push(truncateToWidth(`${rpad}${glyph} ${label}`, rightColWidth));
544
+ }
545
+ if (taskDetails.length > maxVisibleTasks) {
546
+ rightLines.push(truncateToWidth(
547
+ `${rpad}${theme.fg("dim", ` …+${taskDetails.length - maxVisibleTasks} more`)}`,
548
+ rightColWidth,
549
+ ));
550
+ }
551
+ } else if (roadmapSlices?.activeSliceTasks) {
552
+ const { done: tDone, total: tTotal } = roadmapSlices.activeSliceTasks;
553
+ rightLines.push(`${rpad}${theme.fg("dim", `${tDone}/${tTotal} tasks`)}`);
554
+ }
555
+ } else {
556
+ // Narrow single-column: task list goes into left column
557
+ if (taskDetails && taskDetails.length > 0) {
558
+ for (const t of taskDetails.slice(0, maxVisibleTasks)) {
559
+ const isCurrent = task && t.id === task.id;
560
+ const glyph = t.done
561
+ ? theme.fg("success", GLYPH.statusDone)
562
+ : isCurrent
563
+ ? theme.fg("accent", "▸")
564
+ : theme.fg("dim", " ");
565
+ const label = isCurrent
566
+ ? theme.fg("text", `${t.id}: ${t.title}`)
567
+ : t.done
568
+ ? theme.fg("dim", `${t.id}: ${t.title}`)
569
+ : theme.fg("text", `${t.id}: ${t.title}`);
570
+ leftLines.push(truncateToWidth(`${pad}${glyph} ${label}`, leftColWidth));
571
+ }
572
+ }
573
+ // Add progress bar inline
574
+ if (roadmapSlices) {
575
+ const { done, total, activeSliceTasks } = roadmapSlices;
576
+ const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
577
+ const pct = total > 0 ? done / total : 0;
578
+ const filled = Math.round(pct * barWidth);
579
+ const bar = theme.fg("success", "█".repeat(filled))
580
+ + theme.fg("dim", "░".repeat(barWidth - filled));
581
+ let meta = theme.fg("dim", `${done}/${total} slices`);
582
+ if (activeSliceTasks && activeSliceTasks.total > 0) {
583
+ const taskNum = isHook
584
+ ? Math.max(activeSliceTasks.done, 1)
585
+ : Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
586
+ meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
587
+ }
588
+ const eta = estimateTimeRemaining();
589
+ if (eta) meta += theme.fg("dim", ` · ${eta}`);
590
+ leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
591
+ }
592
+ if (next) {
593
+ leftLines.push(truncateToWidth(
594
+ `${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
595
+ leftColWidth,
596
+ ));
597
+ }
598
+ }
599
+
600
+ // Compose columns
601
+ if (useTwoCol) {
602
+ const maxRows = Math.max(leftLines.length, rightLines.length);
603
+ if (maxRows > 0) {
604
+ lines.push(""); // spacer before columns
605
+ for (let i = 0; i < maxRows; i++) {
606
+ const left = padToWidth(leftLines[i] ?? "", leftColWidth);
607
+ const gap = " ".repeat(colGap - 2); // colGap minus divider and its trailing space
608
+ const right = rightLines[i] ?? "";
609
+ lines.push(truncateToWidth(`${left}${gap}${divider} ${right}`, width));
505
610
  }
506
611
  }
612
+ } else {
613
+ // Narrow single-column: just stack
614
+ if (leftLines.length > 0) {
615
+ lines.push("");
616
+ for (const l of leftLines) lines.push(l);
617
+ }
507
618
  }
508
619
 
620
+ // ── Footer: pwd + hints ─────────────────────────────────────────
621
+ lines.push("");
509
622
  const hintParts: string[] = [];
510
623
  hintParts.push("esc pause");
511
624
  hintParts.push(process.platform === "darwin" ? "⌃⌥G dashboard" : "Ctrl+Alt+G dashboard");
512
- lines.push(...ui.hints(hintParts));
625
+ const hintStr = theme.fg("dim", hintParts.join(" | "));
626
+ const pwdStr = theme.fg("dim", widgetPwd);
627
+ lines.push(rightAlign(`${pad}${pwdStr}`, hintStr, width));
513
628
 
514
629
  lines.push(...ui.bar());
515
630
 
@@ -535,6 +650,31 @@ export function updateProgressWidget(
535
650
  * Build a compact string-array representation of the progress widget.
536
651
  * Used as a fallback when the factory-based widget cannot render (RPC mode).
537
652
  */
653
+ // ─── Model Health Indicator ───────────────────────────────────────────────────
654
+
655
+ /**
656
+ * Compute a traffic-light health indicator from observable signals.
657
+ * 🟢 progressing well — no errors, trend stable/improving
658
+ * 🟡 struggling — some errors or degrading trend
659
+ * 🔴 stuck — consecutive errors, likely needs attention
660
+ */
661
+ export function getModelHealthIndicator(): { emoji: string; label: string } {
662
+ const trend = getHealthTrend();
663
+ const consecutiveErrors = getConsecutiveErrorUnits();
664
+
665
+ if (consecutiveErrors >= 3) {
666
+ return { emoji: "🔴", label: "stuck" };
667
+ }
668
+ if (consecutiveErrors >= 1 || trend === "degrading") {
669
+ return { emoji: "🟡", label: "struggling" };
670
+ }
671
+ if (trend === "improving") {
672
+ return { emoji: "🟢", label: "progressing well" };
673
+ }
674
+ // stable or unknown
675
+ return { emoji: "🟢", label: "progressing" };
676
+ }
677
+
538
678
  function buildProgressTextLines(
539
679
  verb: string,
540
680
  phaseLabel: string,
@@ -583,6 +723,11 @@ function buildProgressTextLines(
583
723
  }
584
724
 
585
725
  if (next) lines.push(` Next: ${next}`);
726
+
727
+ // Model health indicator
728
+ const health = getModelHealthIndicator();
729
+ lines.push(` Health: ${health.emoji} ${health.label}`);
730
+
586
731
  lines.push(` ${widgetPwd}`);
587
732
 
588
733
  return lines;
@@ -597,3 +742,10 @@ function rightAlign(left: string, right: string, width: number): string {
597
742
  const gap = Math.max(1, width - leftVis - rightVis);
598
743
  return truncateToWidth(left + " ".repeat(gap) + right, width);
599
744
  }
745
+
746
+ /** Pad a string with trailing spaces to fill exactly `colWidth` (ANSI-aware). */
747
+ function padToWidth(s: string, colWidth: number): string {
748
+ const vis = visibleWidth(s);
749
+ if (vis >= colWidth) return truncateToWidth(s, colWidth);
750
+ return s + " ".repeat(colWidth - vis);
751
+ }
@@ -12,7 +12,7 @@
12
12
  import type { GSDState } from "./types.js";
13
13
  import type { GSDPreferences } from "./preferences.js";
14
14
  import type { UatType } from "./files.js";
15
- import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
15
+ import { loadFile, extractUatType, loadActiveOverrides, parseRoadmap } from "./files.js";
16
16
  import {
17
17
  resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile,
18
18
  relSliceFile, buildMilestoneFileName,
@@ -123,11 +123,40 @@ const DISPATCH_RULES: DispatchRule[] = [
123
123
  };
124
124
  },
125
125
  },
126
+ {
127
+ name: "uat-verdict-gate (non-PASS blocks progression)",
128
+ match: async ({ mid, basePath, prefs }) => {
129
+ // Only applies when UAT dispatch is enabled
130
+ if (!prefs?.uat_dispatch) return null;
131
+
132
+ const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
133
+ const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
134
+ if (!roadmapContent) return null;
135
+
136
+ const roadmap = parseRoadmap(roadmapContent);
137
+ for (const slice of roadmap.slices.filter(s => s.done)) {
138
+ const resultFile = resolveSliceFile(basePath, mid, slice.id, "UAT-RESULT");
139
+ if (!resultFile) continue;
140
+ const content = await loadFile(resultFile);
141
+ if (!content) continue;
142
+ const verdictMatch = content.match(/verdict:\s*([\w-]+)/i);
143
+ const verdict = verdictMatch?.[1]?.toLowerCase();
144
+ if (verdict && verdict !== "pass" && verdict !== "passed") {
145
+ return {
146
+ action: "stop" as const,
147
+ reason: `UAT verdict for ${slice.id} is "${verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /gsd auto after fixing.`,
148
+ level: "warning" as const,
149
+ };
150
+ }
151
+ }
152
+ return null;
153
+ },
154
+ },
126
155
  {
127
156
  name: "reassess-roadmap (post-completion)",
128
157
  match: async ({ state, mid, midTitle, basePath, prefs }) => {
129
- // Phase skip: skip reassess when preference or profile says so
130
- if (prefs?.phases?.skip_reassess) return null;
158
+ // Reassess is opt-in: only fire when explicitly enabled
159
+ if (!prefs?.phases?.reassess_after_slice) return null;
131
160
  const needsReassess = await checkNeedsReassessment(basePath, mid, state);
132
161
  if (!needsReassess) return null;
133
162
  return {