@opengsd/gsd-pi 1.0.2-dev.867e002 → 1.0.2-dev.88c4dc5

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 (337) hide show
  1. package/dist/onboarding.js +22 -3
  2. package/dist/resource-loader.js +3 -1
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/context7/index.js +12 -2
  5. package/dist/resources/extensions/get-secrets-from-user.js +16 -16
  6. package/dist/resources/extensions/google-cli/index.js +30 -0
  7. package/dist/resources/extensions/google-cli/models.js +55 -0
  8. package/dist/resources/extensions/google-cli/package.json +11 -0
  9. package/dist/resources/extensions/google-cli/readiness.js +12 -0
  10. package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
  11. package/dist/resources/extensions/gsd/auto/loop.js +62 -1
  12. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -2
  13. package/dist/resources/extensions/gsd/auto/phases.js +37 -0
  14. package/dist/resources/extensions/gsd/auto/run-unit.js +8 -0
  15. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  16. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -7
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +21 -11
  18. package/dist/resources/extensions/gsd/auto-prompts.js +5 -236
  19. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  20. package/dist/resources/extensions/gsd/auto-start.js +232 -49
  21. package/dist/resources/extensions/gsd/auto.js +6 -1
  22. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
  23. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -2
  24. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +39 -5
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -7
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -27
  27. package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
  28. package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
  29. package/dist/resources/extensions/gsd/commands-usage.js +105 -1
  30. package/dist/resources/extensions/gsd/config-overlay.js +20 -14
  31. package/dist/resources/extensions/gsd/context-overlay.js +22 -16
  32. package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
  33. package/dist/resources/extensions/gsd/doctor-engine-checks.js +87 -0
  34. package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
  35. package/dist/resources/extensions/gsd/doctor.js +6 -1
  36. package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
  37. package/dist/resources/extensions/gsd/guided-flow.js +5 -6
  38. package/dist/resources/extensions/gsd/key-manager.js +45 -13
  39. package/dist/resources/extensions/gsd/milestone-reopen-events.js +28 -0
  40. package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
  41. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
  42. package/dist/resources/extensions/gsd/preferences-skills.js +11 -4
  43. package/dist/resources/extensions/gsd/preferences.js +14 -2
  44. package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
  45. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
  46. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/system.md +1 -3
  48. package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
  49. package/dist/resources/extensions/gsd/repository-registry.js +3 -1
  50. package/dist/resources/extensions/gsd/safety/evidence-collector.js +11 -4
  51. package/dist/resources/extensions/gsd/skill-activation.js +233 -0
  52. package/dist/resources/extensions/gsd/skill-catalog.data.js +820 -0
  53. package/dist/resources/extensions/gsd/skill-catalog.install.js +179 -0
  54. package/dist/resources/extensions/gsd/skill-catalog.js +5 -1028
  55. package/dist/resources/extensions/gsd/skill-discovery.js +121 -79
  56. package/dist/resources/extensions/gsd/skill-scope.js +52 -0
  57. package/dist/resources/extensions/gsd/skill-telemetry.js +6 -39
  58. package/dist/resources/extensions/gsd/skills.js +60 -0
  59. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +351 -0
  60. package/dist/resources/extensions/gsd/state-reconciliation/index.js +41 -0
  61. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +4 -0
  62. package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
  63. package/dist/resources/extensions/gsd/tools/exec-tool.js +42 -8
  64. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +63 -2
  65. package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
  66. package/dist/resources/extensions/gsd/unit-context-manifest.js +35 -26
  67. package/dist/resources/extensions/gsd/user-input-boundary.js +1 -1
  68. package/dist/resources/extensions/gsd/vision-ask.js +22 -0
  69. package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
  70. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
  71. package/dist/resources/extensions/search-the-web/native-search.js +57 -8
  72. package/dist/resources/extensions/shared/confirm-ui.js +9 -6
  73. package/dist/resources/extensions/shared/dialog-frame.js +42 -0
  74. package/dist/resources/extensions/shared/interview-ui.js +42 -30
  75. package/dist/resources/extensions/shared/next-action-ui.js +6 -6
  76. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  77. package/dist/web/standalone/.next/BUILD_ID +1 -1
  78. package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
  79. package/dist/web/standalone/.next/build-manifest.json +2 -2
  80. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  81. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.html +1 -1
  98. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app-paths-manifest.json +4 -4
  105. package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
  106. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  108. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  109. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  110. package/package.json +1 -1
  111. package/packages/cloud-mcp-gateway/package.json +2 -2
  112. package/packages/contracts/dist/rpc.test.js +5 -0
  113. package/packages/contracts/dist/rpc.test.js.map +1 -1
  114. package/packages/contracts/dist/workflow.d.ts +7 -0
  115. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  116. package/packages/contracts/dist/workflow.js +8 -0
  117. package/packages/contracts/dist/workflow.js.map +1 -1
  118. package/packages/contracts/dist/workflow.test.js +1 -0
  119. package/packages/contracts/dist/workflow.test.js.map +1 -1
  120. package/packages/contracts/package.json +1 -1
  121. package/packages/daemon/package.json +4 -4
  122. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +1 -0
  123. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
  124. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +22 -8
  125. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
  126. package/packages/gsd-agent-core/package.json +5 -5
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
  138. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
  140. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
  142. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
  144. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
  146. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
  147. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  148. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  149. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
  150. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
  151. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
  152. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  153. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
  154. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  155. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  156. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  157. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  158. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
  159. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  160. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
  161. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  162. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
  163. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
  164. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
  165. package/packages/gsd-agent-modes/package.json +7 -7
  166. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  167. package/packages/mcp-server/dist/workflow-tools.js +28 -5
  168. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  169. package/packages/mcp-server/package.json +3 -3
  170. package/packages/native/package.json +1 -1
  171. package/packages/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
  172. package/packages/pi-agent-core/dist/harness/skills.js +6 -0
  173. package/packages/pi-agent-core/dist/harness/skills.js.map +1 -1
  174. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts +7 -0
  175. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts.map +1 -1
  176. package/packages/pi-agent-core/dist/harness/system-prompt.js +7 -0
  177. package/packages/pi-agent-core/dist/harness/system-prompt.js.map +1 -1
  178. package/packages/pi-agent-core/package.json +1 -1
  179. package/packages/pi-ai/dist/models.generated.d.ts +8 -59
  180. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  181. package/packages/pi-ai/dist/models.generated.js +21 -72
  182. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  183. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  184. package/packages/pi-ai/dist/providers/anthropic.js +50 -0
  185. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  186. package/packages/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  187. package/packages/pi-ai/dist/providers/openai-responses-shared.js +28 -4
  188. package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  189. package/packages/pi-ai/dist/types.d.ts +2 -0
  190. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  191. package/packages/pi-ai/dist/types.js.map +1 -1
  192. package/packages/pi-ai/package.json +1 -1
  193. package/packages/pi-coding-agent/README.md +1 -1
  194. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +2 -2
  195. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.js +1 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/core/extensions/runner.js +8 -2
  201. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  202. package/packages/pi-coding-agent/dist/core/skills.d.ts +3 -0
  203. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  204. package/packages/pi-coding-agent/dist/core/skills.js +3 -0
  205. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  206. package/packages/pi-coding-agent/package.json +7 -7
  207. package/packages/pi-tui/package.json +1 -1
  208. package/packages/rpc-client/package.json +2 -2
  209. package/pkg/package.json +1 -1
  210. package/src/resources/extensions/context7/index.ts +15 -2
  211. package/src/resources/extensions/get-secrets-from-user.ts +17 -16
  212. package/src/resources/extensions/google-cli/index.ts +34 -0
  213. package/src/resources/extensions/google-cli/models.ts +57 -0
  214. package/src/resources/extensions/google-cli/package.json +11 -0
  215. package/src/resources/extensions/google-cli/readiness.ts +15 -0
  216. package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
  217. package/src/resources/extensions/gsd/auto/loop.ts +74 -1
  218. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -2
  219. package/src/resources/extensions/gsd/auto/phases.ts +46 -0
  220. package/src/resources/extensions/gsd/auto/run-unit.ts +10 -0
  221. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  222. package/src/resources/extensions/gsd/auto-dispatch.ts +31 -11
  223. package/src/resources/extensions/gsd/auto-post-unit.ts +43 -14
  224. package/src/resources/extensions/gsd/auto-prompts.ts +4 -284
  225. package/src/resources/extensions/gsd/auto-recovery.ts +10 -7
  226. package/src/resources/extensions/gsd/auto-start.ts +307 -56
  227. package/src/resources/extensions/gsd/auto.ts +6 -1
  228. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
  229. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +9 -4
  230. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +42 -5
  231. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
  232. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -28
  233. package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
  234. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
  235. package/src/resources/extensions/gsd/commands-usage.ts +110 -5
  236. package/src/resources/extensions/gsd/config-overlay.ts +19 -16
  237. package/src/resources/extensions/gsd/context-overlay.ts +24 -19
  238. package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
  239. package/src/resources/extensions/gsd/doctor-engine-checks.ts +99 -0
  240. package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
  241. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  242. package/src/resources/extensions/gsd/doctor.ts +6 -1
  243. package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
  244. package/src/resources/extensions/gsd/guided-flow.ts +5 -6
  245. package/src/resources/extensions/gsd/key-manager.ts +57 -14
  246. package/src/resources/extensions/gsd/milestone-reopen-events.ts +28 -0
  247. package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
  248. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
  249. package/src/resources/extensions/gsd/preferences-skills.ts +11 -4
  250. package/src/resources/extensions/gsd/preferences.ts +17 -2
  251. package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
  252. package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
  253. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  254. package/src/resources/extensions/gsd/prompts/system.md +1 -3
  255. package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
  256. package/src/resources/extensions/gsd/repository-registry.ts +3 -1
  257. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -4
  258. package/src/resources/extensions/gsd/skill-activation.ts +292 -0
  259. package/src/resources/extensions/gsd/skill-catalog.data.ts +858 -0
  260. package/src/resources/extensions/gsd/skill-catalog.install.ts +205 -0
  261. package/src/resources/extensions/gsd/skill-catalog.ts +16 -1087
  262. package/src/resources/extensions/gsd/skill-discovery.ts +134 -78
  263. package/src/resources/extensions/gsd/skill-scope.ts +63 -0
  264. package/src/resources/extensions/gsd/skill-telemetry.ts +6 -40
  265. package/src/resources/extensions/gsd/skills.ts +75 -0
  266. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +499 -0
  267. package/src/resources/extensions/gsd/state-reconciliation/index.ts +40 -0
  268. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +8 -0
  269. package/src/resources/extensions/gsd/state-reconciliation/types.ts +30 -0
  270. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +328 -2
  271. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +21 -0
  272. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +28 -2
  273. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +30 -0
  274. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +41 -0
  275. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +12 -0
  276. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
  277. package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
  278. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
  279. package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
  280. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
  281. package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
  282. package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
  283. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
  284. package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +1 -0
  285. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
  286. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +1 -1
  287. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
  288. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +101 -1
  289. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +30 -0
  290. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
  291. package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
  292. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
  293. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
  294. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
  295. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +93 -0
  296. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
  297. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +116 -11
  298. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +30 -1
  299. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
  300. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +111 -0
  301. package/src/resources/extensions/gsd/tests/skill-scope-auto.test.ts +67 -0
  302. package/src/resources/extensions/gsd/tests/skills.test.ts +55 -0
  303. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
  304. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +303 -0
  305. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +19 -0
  306. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
  307. package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
  308. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
  309. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +18 -0
  310. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +26 -0
  311. package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
  312. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
  313. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +74 -1
  314. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +82 -0
  315. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
  316. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
  317. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
  318. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
  319. package/src/resources/extensions/gsd/tools/exec-tool.ts +42 -10
  320. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +82 -5
  321. package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
  322. package/src/resources/extensions/gsd/unit-context-manifest.ts +37 -26
  323. package/src/resources/extensions/gsd/user-input-boundary.ts +1 -1
  324. package/src/resources/extensions/gsd/vision-ask.ts +28 -0
  325. package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
  326. package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
  327. package/src/resources/extensions/search-the-web/native-search.ts +60 -8
  328. package/src/resources/extensions/shared/confirm-ui.ts +8 -12
  329. package/src/resources/extensions/shared/dialog-frame.ts +71 -0
  330. package/src/resources/extensions/shared/interview-ui.ts +43 -42
  331. package/src/resources/extensions/shared/next-action-ui.ts +6 -6
  332. package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
  333. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
  334. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
  335. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  336. /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → PkhJfy4kKo4yUHj1wY7_q}/_buildManifest.js +0 -0
  337. /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → PkhJfy4kKo4yUHj1wY7_q}/_ssgManifest.js +0 -0
@@ -36,6 +36,7 @@ import {
36
36
  truncateToWidth,
37
37
  type TUI,
38
38
  } from "@gsd/pi-tui";
39
+ import { renderSharedDialogFrame } from "./dialog-frame.js";
39
40
  import { mergeSideBySide } from "./layout-utils.js";
40
41
  import { makeUI, INDENT } from "./ui.js";
41
42
 
@@ -126,6 +127,10 @@ const DIVIDER_CHARS = " │ ";
126
127
  const DIVIDER_WIDTH = 3;
127
128
  const PREVIEW_MAX_LINES = 20; // hard cap — keeps total ≤ 24 rows for single-question
128
129
 
130
+ function dialogContentWidth(width: number): number {
131
+ return width < 4 ? Math.max(1, width) : Math.max(1, width - 4);
132
+ }
133
+
129
134
  // ─── Wrap-up screen ───────────────────────────────────────────────────────────
130
135
 
131
136
  export async function showWrapUpScreen(
@@ -157,11 +162,12 @@ export async function showWrapUpScreen(
157
162
 
158
163
  function render(width: number): string[] {
159
164
  if (cachedLines) return cachedLines;
160
- const ui = makeUI(theme, width);
165
+ const contentWidth = dialogContentWidth(width);
166
+ const ui = makeUI(theme, contentWidth);
161
167
  const lines: string[] = [];
162
168
  const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
163
169
 
164
- push(ui.bar(), ui.blank(), ui.header(` ${opts.headline}`), ui.blank());
170
+ push(ui.blank());
165
171
  if (opts.progress) push(ui.meta(` ${opts.progress}`), ui.blank());
166
172
 
167
173
  if (cursorIdx === 1) {
@@ -175,14 +181,11 @@ export async function showWrapUpScreen(
175
181
  } else {
176
182
  push(ui.actionUnselected(2, opts.keepGoingLabel, "Continue with another batch of questions."));
177
183
  }
178
- push(
179
- ui.blank(),
180
- ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"]),
181
- ui.bar(),
182
- );
184
+ push(ui.blank());
183
185
 
184
- cachedLines = lines;
185
- return lines;
186
+ const footer = ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"])[0] ?? "";
187
+ cachedLines = renderSharedDialogFrame(theme, opts.headline, lines, width, { footer });
188
+ return cachedLines;
186
189
  }
187
190
 
188
191
  return {
@@ -469,11 +472,13 @@ export async function showInterviewRound(
469
472
  // ── Review screen ────────────────────────────────────────────────
470
473
 
471
474
  function renderReviewScreen(width: number): string[] {
472
- const ui = makeUI(theme, width);
475
+ const contentWidth = dialogContentWidth(width);
476
+ const title = opts.reviewHeadline ?? "Review your answers";
477
+ const ui = makeUI(theme, contentWidth);
473
478
  const lines: string[] = [];
474
479
  const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
475
480
 
476
- push(ui.bar(), ui.blank(), ui.header(` ${opts.reviewHeadline ?? "Review your answers"}`), ui.blank());
481
+ push(ui.blank());
477
482
 
478
483
  for (let i = 0; i < questions.length; i++) {
479
484
  const q = questions[i];
@@ -500,24 +505,22 @@ export async function showInterviewRound(
500
505
  push(
501
506
  ui.actionSelected(0, "Submit answers"),
502
507
  ui.blank(),
503
- ui.hints(["← to go back and edit", "enter to submit", `esc to ${opts.exitLabel ?? "end interview"}`]),
504
- ui.bar(),
505
508
  );
506
509
 
507
- return lines;
510
+ const footer = ui.hints(["← to go back and edit", "enter to submit", `esc to ${opts.exitLabel ?? "end interview"}`])[0] ?? "";
511
+ return renderSharedDialogFrame(theme, title, lines, width, { footer });
508
512
  }
509
513
 
510
514
  // ── Exit confirm screen ──────────────────────────────────────────
511
515
 
512
516
  function renderExitConfirm(width: number): string[] {
513
- const ui = makeUI(theme, width);
517
+ const contentWidth = dialogContentWidth(width);
518
+ const title = opts.exitHeadline ?? "End interview?";
519
+ const ui = makeUI(theme, contentWidth);
514
520
  const lines: string[] = [];
515
521
  const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
516
522
 
517
523
  push(
518
- ui.bar(),
519
- ui.blank(),
520
- ui.header(` ${opts.exitHeadline ?? "End interview?"}`),
521
524
  ui.blank(),
522
525
  ui.subtitle(" Answers from this batch won't be saved."),
523
526
  ui.blank(),
@@ -538,13 +541,10 @@ export async function showInterviewRound(
538
541
  } else {
539
542
  push(ui.actionUnselected(2, exitActionLabel, "Exit and discard this batch of answers."));
540
543
  }
541
- push(
542
- ui.blank(),
543
- ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"]),
544
- ui.bar(),
545
- );
544
+ push(ui.blank());
546
545
 
547
- return lines;
546
+ const footer = ui.hints(["↑/↓ to choose", "1/2 to quick-select", "enter to confirm"])[0] ?? "";
547
+ return renderSharedDialogFrame(theme, title, lines, width, { footer });
548
548
  }
549
549
 
550
550
  // ── Preview helpers ──────────────────────────────────────────────
@@ -648,17 +648,17 @@ export async function showInterviewRound(
648
648
  if (showingExitConfirm) { cachedLines = renderExitConfirm(width); return cachedLines; }
649
649
  if (showingReview) { cachedLines = renderReviewScreen(width); return cachedLines; }
650
650
 
651
+ const contentWidth = dialogContentWidth(width);
652
+ const title = questions[currentIdx]?.header || "GSD Interview";
651
653
  const useSideBySide = questionHasAnyPreview()
652
- && width >= (MIN_OPTIONS_WIDTH + MIN_PREVIEW_WIDTH + DIVIDER_WIDTH);
654
+ && contentWidth >= (MIN_OPTIONS_WIDTH + MIN_PREVIEW_WIDTH + DIVIDER_WIDTH);
653
655
 
654
656
  if (useSideBySide) {
655
657
  // ── Preview path ──────────────────────────────────────
656
- const ui = makeUI(theme, width);
658
+ const ui = makeUI(theme, contentWidth);
657
659
  const lines: string[] = [];
658
660
  const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
659
661
 
660
- push(ui.bar());
661
-
662
662
  if (isMultiQuestion) {
663
663
  const unanswered = questions.filter((_, i) => !isQuestionAnswered(i)).length;
664
664
  const answeredSet = new Set(questions.map((_, i) => i).filter(i => isQuestionAnswered(i)));
@@ -680,12 +680,15 @@ export async function showInterviewRound(
680
680
  // component: spinner/loader (1-2), status line (1), tool header (1),
681
681
  // plus a safety margin for future additions.
682
682
  const termRows = (typeof process !== "undefined" && process.stdout?.rows) || 24;
683
- const footerLines = 3; // blank + hints + bar
683
+ const footerLines = 5; // body spacer + frame top/footer/bottom chrome
684
684
  const tuiChrome = 5;
685
685
  const maxBody = Math.min(PREVIEW_MAX_LINES, Math.max(6, termRows - lines.length - footerLines - tuiChrome));
686
686
 
687
- const previewWidth = Math.max(MIN_PREVIEW_WIDTH, Math.floor(width * PREVIEW_RATIO));
688
- const leftWidth = Math.max(MIN_OPTIONS_WIDTH, width - previewWidth - DIVIDER_WIDTH);
687
+ const previewWidth = Math.max(
688
+ MIN_PREVIEW_WIDTH,
689
+ Math.min(contentWidth - MIN_OPTIONS_WIDTH - DIVIDER_WIDTH, Math.floor(contentWidth * PREVIEW_RATIO)),
690
+ );
691
+ const leftWidth = Math.max(MIN_OPTIONS_WIDTH, contentWidth - previewWidth - DIVIDER_WIDTH);
689
692
 
690
693
  const fullLeft = renderOptionsColumn(leftWidth);
691
694
  const leftLines = fullLeft.slice(0, maxBody);
@@ -709,7 +712,7 @@ export async function showInterviewRound(
709
712
  while (leftLines.length < maxBody) leftLines.push("");
710
713
  while (rightLines.length < maxBody) rightLines.push("");
711
714
  const divider = theme.fg("dim", DIVIDER_CHARS);
712
- lines.push(...mergeSideBySide(leftLines, rightLines, leftWidth, divider, width));
715
+ lines.push(...mergeSideBySide(leftLines, rightLines, leftWidth, divider, contentWidth));
713
716
 
714
717
  // Footer
715
718
  push(ui.blank());
@@ -729,15 +732,15 @@ export async function showInterviewRound(
729
732
  hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
730
733
  }
731
734
  hints.push("esc to exit");
732
- push(ui.hints(hints), ui.bar());
735
+ const footer = ui.hints(hints)[0] ?? "";
733
736
 
734
- cachedLines = lines;
735
- return lines;
737
+ cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
738
+ return cachedLines;
736
739
  }
737
740
 
738
741
  // ── Original path — no preview, untouched ────────────────
739
742
 
740
- const ui = makeUI(theme, width);
743
+ const ui = makeUI(theme, contentWidth);
741
744
  const lines: string[] = [];
742
745
  const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
743
746
 
@@ -745,8 +748,6 @@ export async function showInterviewRound(
745
748
  const st = states[currentIdx];
746
749
  const multiSel = isMultiSelect(currentIdx);
747
750
 
748
- push(ui.bar());
749
-
750
751
  // ── Progress header ────────────────────────────────────────────
751
752
  if (isMultiQuestion) {
752
753
  const unanswered = questions.filter((_, i) => !isQuestionAnswered(i)).length;
@@ -809,7 +810,7 @@ export async function showInterviewRound(
809
810
  if (st.notesVisible || focusNotes) {
810
811
  push(ui.blank(), ui.notesLabel(focusNotes));
811
812
  if (focusNotes) {
812
- for (const line of getEditor().render(width - 2)) lines.push(truncateToWidth(` ${line}`, width));
813
+ for (const line of getEditor().render(contentWidth - 2)) lines.push(truncateToWidth(` ${line}`, contentWidth));
813
814
  } else if (st.notes) {
814
815
  push(ui.notesText(st.notes));
815
816
  }
@@ -833,10 +834,10 @@ export async function showInterviewRound(
833
834
  hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
834
835
  }
835
836
  hints.push("esc to exit");
836
- push(ui.hints(hints), ui.bar());
837
+ const footer = ui.hints(hints)[0] ?? "";
837
838
 
838
- cachedLines = lines;
839
- return lines;
839
+ cachedLines = renderSharedDialogFrame(theme, title, lines, width, { footer });
840
+ return cachedLines;
840
841
  }
841
842
 
842
843
  return {
@@ -44,6 +44,7 @@
44
44
  import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
45
45
  import { type Theme } from "@gsd/pi-coding-agent";
46
46
  import { Key, matchesKey, type TUI } from "@gsd/pi-tui";
47
+ import { renderSharedDialogFrame } from "./dialog-frame.js";
47
48
  import { makeUI } from "./ui.js";
48
49
 
49
50
  // ─── Public API ───────────────────────────────────────────────────────────────
@@ -142,14 +143,14 @@ export async function showNextAction(
142
143
 
143
144
  function render(width: number): string[] {
144
145
  if (cachedLines) return cachedLines;
145
- const ui = makeUI(theme, width);
146
+ const contentWidth = Math.max(1, width - 4);
147
+ const ui = makeUI(theme, contentWidth);
146
148
  const lines: string[] = [];
147
149
  const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
148
150
 
149
151
  // ── Header — uses success colour to signal completion ────────────
150
152
  // Note: next-action intentionally uses "success" for its bar/title
151
153
  // to distinguish it from regular accent-coloured screens.
152
- push(ui.bar());
153
154
  push(ui.blank());
154
155
  push(ui.header(` ✓ ${opts.title}`));
155
156
 
@@ -192,11 +193,10 @@ export async function showNextAction(
192
193
 
193
194
  // ── Footer ────────────────────────────────────────────────────────
194
195
  const numHint = allActions.map((_, i) => `${i + 1}`).join("/");
195
- push(ui.hints([`↑/↓ to choose`, `${numHint} to quick-select`, `enter to confirm`]));
196
- push(ui.bar());
196
+ const footer = ui.hints([`↑/↓ to choose`, `${numHint} to quick-select`, `enter to confirm`])[0] ?? "";
197
197
 
198
- cachedLines = lines;
199
- return lines;
198
+ cachedLines = renderSharedDialogFrame(theme, "GSD Next Action", lines, width, { footer });
199
+ return cachedLines;
200
200
  }
201
201
 
202
202
  return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
@@ -0,0 +1,57 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { stripVTControlCharacters } from "node:util";
4
+
5
+ import { visibleWidth } from "@gsd/pi-tui";
6
+
7
+ import { showConfirm } from "../confirm-ui.js";
8
+
9
+ function assertFullOuterBorder(lines: string[], width: number): void {
10
+ assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
11
+ for (const [index, line] of lines.entries()) {
12
+ assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
13
+ }
14
+ const top = stripVTControlCharacters(lines[0] ?? "");
15
+ const bottom = stripVTControlCharacters(lines.at(-1) ?? "");
16
+ assert.match(top, /^[╭┌].*[╮┐]$/);
17
+ assert.match(bottom, /^[╰└].*[╯┘]$/);
18
+ for (let index = 1; index < lines.length - 1; index++) {
19
+ const line = stripVTControlCharacters(lines[index] ?? "");
20
+ assert.match(line, /^[│┃├]/, `line ${index} missing left border: ${line}`);
21
+ assert.match(line, /[│┃┤]$/, `line ${index} missing right border: ${line}`);
22
+ }
23
+ }
24
+
25
+ describe("showConfirm", () => {
26
+ it("renders the confirmation prompt inside a full border", async () => {
27
+ let rendered: string[] = [];
28
+ const theme = {
29
+ fg: (_color: string, text: string) => text,
30
+ bold: (text: string) => text,
31
+ };
32
+
33
+ const ctx = {
34
+ hasUI: true,
35
+ ui: {
36
+ custom: async (factory: any) => {
37
+ let resolved: boolean | undefined;
38
+ const component = factory({ requestRender() {} }, theme, null, (value: boolean) => {
39
+ resolved = value;
40
+ });
41
+ rendered = component.render(80);
42
+ component.handleInput("\r");
43
+ return resolved as never;
44
+ },
45
+ },
46
+ };
47
+
48
+ const result = await showConfirm(ctx as any, {
49
+ title: "Confirm action",
50
+ message: "Proceed with the change?",
51
+ });
52
+
53
+ assert.equal(result, true);
54
+ assertFullOuterBorder(rendered, 80);
55
+ assert.match(stripVTControlCharacters(rendered[0] ?? ""), /Confirm action/);
56
+ });
57
+ });
@@ -0,0 +1,163 @@
1
+ // gsd-pi — Shared interview UI dialog border contract
2
+
3
+ import assert from "node:assert/strict";
4
+ import { before, describe, it } from "node:test";
5
+
6
+ import { visibleWidth } from "@gsd/pi-tui";
7
+ import { initTheme } from "@gsd/pi-coding-agent";
8
+ import {
9
+ showInterviewRound,
10
+ showWrapUpScreen,
11
+ type Question,
12
+ type RoundResult,
13
+ type WrapUpResult,
14
+ } from "../interview-ui.js";
15
+
16
+ const ANSI_PATTERN = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
17
+ const ENTER = "\r";
18
+ const ESC = "\x1b";
19
+
20
+ before(() => { initTheme(); });
21
+
22
+ type RenderWidget = {
23
+ render(width: number): string[];
24
+ handleInput(input: string): void;
25
+ };
26
+
27
+ function stripAnsi(text: string): string {
28
+ return text.replace(ANSI_PATTERN, "");
29
+ }
30
+
31
+ function assertFullOuterBorder(lines: string[], width: number): void {
32
+ assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
33
+
34
+ for (const [index, line] of lines.entries()) {
35
+ assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
36
+ }
37
+
38
+ const top = stripAnsi(lines[0] ?? "");
39
+ const bottom = stripAnsi(lines.at(-1) ?? "");
40
+ assert.match(top, /^╭.*╮$/, `top border missing full corners: ${top}`);
41
+ assert.match(bottom, /^╰.*╯$/, `bottom border missing full corners: ${bottom}`);
42
+
43
+ for (let index = 1; index < lines.length - 1; index++) {
44
+ const line = stripAnsi(lines[index] ?? "");
45
+ assert.match(line, /^[│├]/, `line ${index} missing left border: ${line}`);
46
+ assert.match(line, /[│┤]$/, `line ${index} missing right border: ${line}`);
47
+ }
48
+ }
49
+
50
+ function mockTheme() {
51
+ return {
52
+ fg: (_color: string, text: string) => text,
53
+ bold: (text: string) => text,
54
+ dim: (text: string) => text,
55
+ italic: (text: string) => text,
56
+ strikethrough: (text: string) => text,
57
+ accent: (text: string) => text,
58
+ success: (text: string) => text,
59
+ warning: (text: string) => text,
60
+ error: (text: string) => text,
61
+ info: (text: string) => text,
62
+ muted: (text: string) => text,
63
+ dimmed: (text: string) => text,
64
+ };
65
+ }
66
+
67
+ async function captureInterviewWidget(
68
+ questions: Question[],
69
+ ): Promise<RenderWidget> {
70
+ let widget: RenderWidget | undefined;
71
+
72
+ await showInterviewRound(questions, {}, {
73
+ ui: {
74
+ custom: (factory: any) => {
75
+ widget = factory(
76
+ { requestRender: () => {} },
77
+ mockTheme(),
78
+ {},
79
+ (_result: RoundResult) => {},
80
+ );
81
+ return Promise.resolve({ endInterview: false, answers: {} });
82
+ },
83
+ },
84
+ } as any);
85
+
86
+ assert.ok(widget, "interview widget should be created");
87
+ return widget;
88
+ }
89
+
90
+ async function captureWrapUpWidget(): Promise<RenderWidget> {
91
+ let widget: RenderWidget | undefined;
92
+
93
+ await showWrapUpScreen({
94
+ headline: "Ready to wrap up?",
95
+ progress: "4 questions answered",
96
+ keepGoingLabel: "Keep going",
97
+ satisfiedLabel: "I'm satisfied",
98
+ }, {
99
+ ui: {
100
+ custom: (factory: any) => {
101
+ widget = factory(
102
+ { requestRender: () => {} },
103
+ mockTheme(),
104
+ {},
105
+ (_result: WrapUpResult) => {},
106
+ );
107
+ return Promise.resolve({ satisfied: false });
108
+ },
109
+ },
110
+ } as any);
111
+
112
+ assert.ok(widget, "wrap-up widget should be created");
113
+ return widget;
114
+ }
115
+
116
+ describe("interview-ui dialog borders", () => {
117
+ const questions: Question[] = [
118
+ {
119
+ id: "project_type",
120
+ header: "Project Type",
121
+ question: "What type of project?",
122
+ options: [
123
+ { label: "Web App", description: "Frontend or full-stack" },
124
+ { label: "CLI Tool", description: "Command-line utility" },
125
+ ],
126
+ },
127
+ ];
128
+
129
+ it("renders the main question screen with a full border", async () => {
130
+ const widget = await captureInterviewWidget(questions);
131
+ assertFullOuterBorder(widget.render(80), 80);
132
+ });
133
+
134
+ it("renders the preview split screen with a full border", async () => {
135
+ const widget = await captureInterviewWidget([{
136
+ ...questions[0],
137
+ options: [
138
+ {
139
+ label: "Web App",
140
+ description: "Frontend or full-stack",
141
+ preview: "### Stack\n\nUse React with a typed API boundary.",
142
+ },
143
+ ],
144
+ }]);
145
+ assertFullOuterBorder(widget.render(100), 100);
146
+ });
147
+
148
+ it("renders review and exit confirmation screens with full borders", async () => {
149
+ const widget = await captureInterviewWidget(questions);
150
+
151
+ widget.handleInput(ENTER);
152
+ assertFullOuterBorder(widget.render(80), 80);
153
+
154
+ const exitWidget = await captureInterviewWidget(questions);
155
+ exitWidget.handleInput(ESC);
156
+ assertFullOuterBorder(exitWidget.render(80), 80);
157
+ });
158
+
159
+ it("renders the wrap-up screen with a full border", async () => {
160
+ const widget = await captureWrapUpWidget();
161
+ assertFullOuterBorder(widget.render(80), 80);
162
+ });
163
+ });
@@ -15,9 +15,27 @@
15
15
 
16
16
  import { describe, it } from "node:test";
17
17
  import assert from "node:assert/strict";
18
+ import { stripVTControlCharacters } from "node:util";
19
+ import { visibleWidth } from "@gsd/pi-tui";
18
20
 
19
21
  import { showNextAction } from "../next-action-ui.js";
20
22
 
23
+ function assertFullOuterBorder(lines: string[], width: number): void {
24
+ assert.ok(lines.length >= 2, "dialog must include top and bottom borders");
25
+ for (const [index, line] of lines.entries()) {
26
+ assert.equal(visibleWidth(line), width, `line ${index} must fill dialog width`);
27
+ }
28
+ const top = stripVTControlCharacters(lines[0] ?? "");
29
+ const bottom = stripVTControlCharacters(lines.at(-1) ?? "");
30
+ assert.match(top, /^[╭┌].*[╮┐]$/);
31
+ assert.match(bottom, /^[╰└].*[╯┘]$/);
32
+ for (let index = 1; index < lines.length - 1; index++) {
33
+ const line = stripVTControlCharacters(lines[index] ?? "");
34
+ assert.match(line, /^[│┃├]/, `line ${index} missing left border: ${line}`);
35
+ assert.match(line, /[│┃┤]$/, `line ${index} missing right border: ${line}`);
36
+ }
37
+ }
38
+
21
39
  describe("showNextAction ctx.hasUI guard (#5125 lockup root protection)", () => {
22
40
  it("returns 'not_yet' immediately when ctx.hasUI is false (no UI calls)", async () => {
23
41
  let customCalled = 0;
@@ -152,4 +170,41 @@ describe("showNextAction ctx.hasUI guard (#5125 lockup root protection)", () =>
152
170
  assert.equal(result, "beta", "TUI selection should be returned verbatim");
153
171
  assert.equal(selectCalled, 0, "ctx.ui.select fallback must NOT fire when custom returns a value");
154
172
  });
173
+
174
+ it("renders the interactive next-action menu inside a full border", async () => {
175
+ let rendered: string[] = [];
176
+ const theme = {
177
+ fg: (_color: string, text: string) => text,
178
+ bold: (text: string) => text,
179
+ };
180
+
181
+ const ctx = {
182
+ hasUI: true,
183
+ ui: {
184
+ custom: async (factory: any) => {
185
+ let resolved: string | undefined;
186
+ const component = factory({ requestRender() {} }, theme, null, (value: string) => {
187
+ resolved = value;
188
+ });
189
+ rendered = component.render(80);
190
+ component.handleInput("\r");
191
+ return resolved as never;
192
+ },
193
+ select: async () => undefined,
194
+ },
195
+ };
196
+
197
+ const result = await showNextAction(ctx as any, {
198
+ title: "GSD — test",
199
+ summary: ["summary"],
200
+ actions: [
201
+ { id: "alpha", label: "Alpha", description: "first", recommended: true },
202
+ { id: "beta", label: "Beta", description: "second" },
203
+ ],
204
+ });
205
+
206
+ assert.equal(result, "alpha");
207
+ assertFullOuterBorder(rendered, 80);
208
+ assert.match(stripVTControlCharacters(rendered[0] ?? ""), /GSD Next Action/);
209
+ });
155
210
  });
@@ -31,7 +31,7 @@ GSD auto-discovers skills at session start and during auto-mode:
31
31
 
32
32
  **Session start:** All discovered skills are enumerated and their names + descriptions are injected into the system prompt as `<available_skills>`.
33
33
 
34
- **Auto-mode discovery:** `skill-discovery.ts` takes a snapshot of the skills directory at auto-mode start. On each unit boundary it diffs against the snapshot. Any new skills found are injected via a `<newly_discovered_skills>` XML block so the LLM sees them without requiring `/reload`.
34
+ **Auto-mode discovery:** `skill-discovery.ts` snapshots installed skill names at auto-mode start. When new skills appear mid-run, GSD reloads the resource catalog so they appear in the standard `<available_skills>` block (no separate XML injection).
35
35
 
36
36
  **Manual reload:** Running `/reload` re-scans the skill directories and updates the available skills list mid-session.
37
37
  </skill_discovery>