@opengsd/gsd-pi 1.5.0-dev.553dc518 → 1.5.0-dev.c4c8885f

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 (302) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/ask-user-questions.js +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  4. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
  6. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +7 -2
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
  9. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  11. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  12. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  13. package/dist/resources/extensions/gsd/auto-start.js +54 -31
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  15. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  16. package/dist/resources/extensions/gsd/auto.js +97 -31
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  18. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +67 -6
  19. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  20. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  21. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  22. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  23. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  24. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  25. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  26. package/dist/resources/extensions/gsd/flat-phase-migration.js +18 -0
  27. package/dist/resources/extensions/gsd/gsd-db.js +12 -11
  28. package/dist/resources/extensions/gsd/guided-flow.js +16 -7
  29. package/dist/resources/extensions/gsd/journal.js +1 -1
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
  31. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  32. package/dist/resources/extensions/gsd/metrics.js +2 -2
  33. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  34. package/dist/resources/extensions/gsd/paths.js +87 -52
  35. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  36. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  37. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  38. package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
  39. package/dist/resources/extensions/gsd/preferences.js +20 -0
  40. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  41. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  42. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  43. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  44. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  45. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  46. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  47. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  48. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  49. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  50. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  51. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  52. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  53. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  54. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  55. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  56. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  57. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  58. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  59. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  60. package/dist/resources/extensions/gsd/workspace.js +5 -14
  61. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  62. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  116. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  118. package/dist/web/standalone/.next/server/app/index.html +1 -1
  119. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  126. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  127. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  128. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  130. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  131. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  132. package/dist/worktree-cli-create.d.ts +10 -0
  133. package/dist/worktree-cli-create.js +11 -0
  134. package/dist/worktree-cli-format.d.ts +12 -0
  135. package/dist/worktree-cli-format.js +38 -0
  136. package/dist/worktree-cli-plan.d.ts +16 -0
  137. package/dist/worktree-cli-plan.js +19 -0
  138. package/dist/worktree-cli-session.d.ts +13 -0
  139. package/dist/worktree-cli-session.js +18 -0
  140. package/dist/worktree-cli-status.d.ts +35 -0
  141. package/dist/worktree-cli-status.js +54 -0
  142. package/dist/worktree-cli.d.ts +1 -16
  143. package/dist/worktree-cli.js +28 -122
  144. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  145. package/integrations/hermes/pyproject.toml +1 -1
  146. package/package.json +1 -1
  147. package/packages/cloud-mcp-gateway/package.json +2 -2
  148. package/packages/contracts/package.json +1 -1
  149. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  150. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  151. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  152. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  153. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  154. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  155. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  156. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  157. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  158. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  159. package/packages/daemon/dist/orchestrator.js +13 -303
  160. package/packages/daemon/dist/orchestrator.js.map +1 -1
  161. package/packages/daemon/dist/orchestrator.test.js +11 -1
  162. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  163. package/packages/daemon/package.json +4 -4
  164. package/packages/gsd-agent-core/package.json +5 -5
  165. package/packages/gsd-agent-modes/package.json +7 -7
  166. package/packages/mcp-server/package.json +4 -4
  167. package/packages/native/package.json +1 -1
  168. package/packages/pi-agent-core/package.json +1 -1
  169. package/packages/pi-ai/package.json +1 -1
  170. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  171. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  173. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  175. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  177. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  179. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  180. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  181. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  182. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  183. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  185. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  187. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/index.js +1 -0
  189. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/package.json +2 -2
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/ask-user-questions.ts +1 -1
  195. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  196. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  197. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  198. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  199. package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
  200. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
  201. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +9 -2
  202. package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
  203. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  204. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  205. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  206. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  207. package/src/resources/extensions/gsd/auto-start.ts +58 -29
  208. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  209. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  210. package/src/resources/extensions/gsd/auto.ts +103 -30
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  212. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +66 -6
  213. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  214. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  215. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  216. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  217. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  218. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  219. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +20 -0
  221. package/src/resources/extensions/gsd/gsd-db.ts +20 -11
  222. package/src/resources/extensions/gsd/guided-flow.ts +19 -7
  223. package/src/resources/extensions/gsd/journal.ts +1 -1
  224. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
  225. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  226. package/src/resources/extensions/gsd/metrics.ts +2 -2
  227. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  228. package/src/resources/extensions/gsd/paths.ts +108 -51
  229. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  230. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  231. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  232. package/src/resources/extensions/gsd/preferences-types.ts +24 -1
  233. package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
  234. package/src/resources/extensions/gsd/preferences.ts +20 -0
  235. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  236. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  237. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  238. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  239. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  240. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  241. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  242. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  243. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  244. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  245. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  246. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  247. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  248. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  249. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  250. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  251. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  252. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +27 -0
  253. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
  254. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  255. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  256. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  257. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  258. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  259. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  260. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  261. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  262. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  263. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  264. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  265. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  266. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  267. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  268. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  269. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  270. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  271. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  272. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  273. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  274. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  275. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +93 -0
  276. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  277. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  278. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  279. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  280. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  281. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  282. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  283. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  284. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  285. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  286. package/src/resources/extensions/gsd/types.ts +23 -4
  287. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  288. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  289. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  290. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  291. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  292. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  293. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  294. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  295. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  296. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  297. package/src/resources/extensions/gsd/workspace.ts +5 -21
  298. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  299. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  300. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  301. /package/dist/web/standalone/.next/static/{ZpFDazP-38PXBPiI_LdZf → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
  302. /package/dist/web/standalone/.next/static/{ZpFDazP-38PXBPiI_LdZf → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
@@ -8,10 +8,16 @@ import type { AutoSession } from "../auto/session.js";
8
8
  import type { LoopDeps } from "../auto/loop-deps.js";
9
9
  import { gsdRoot } from "../paths.js";
10
10
  import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
11
- import { setUnifiedAuditEnabled } from "./audit-toggle.js";
12
- import { resolveUokFlags } from "./flags.js";
11
+ import {
12
+ getUnifiedAuditOverride,
13
+ restoreUnifiedAuditOverride,
14
+ setUnifiedAuditEnabled,
15
+ setUnifiedAuditSuppressedForBasePath,
16
+ } from "./audit-toggle.js";
17
+ import { resolveUokFlags, type UokFlags } from "./flags.js";
13
18
  import { createTurnObserver } from "./loop-adapter.js";
14
19
  import { incrementLegacyTelemetry } from "../legacy-telemetry.js";
20
+ import { logWarning } from "../workflow-logger.js";
15
21
 
16
22
  interface RunAutoLoopWithUokArgs {
17
23
  ctx: ExtensionContext;
@@ -45,80 +51,195 @@ function writeParityEvent(basePath: string, event: Record<string, unknown>): voi
45
51
  }
46
52
  }
47
53
 
54
+ type UokKernelPathLabel = "uok-kernel" | "legacy-wrapper" | "legacy-fallback";
55
+
56
+ interface UokKernelRunPlan {
57
+ flags: UokFlags;
58
+ pathLabel: UokKernelPathLabel;
59
+ traceId: string;
60
+ useKernel: boolean;
61
+ }
62
+
48
63
  function resolveKernelPathLabel(
49
- flags: ReturnType<typeof resolveUokFlags>,
50
- ): "uok-kernel" | "legacy-wrapper" | "legacy-fallback" {
64
+ flags: UokFlags,
65
+ ): UokKernelPathLabel {
51
66
  if (flags.legacyFallback) return "legacy-fallback";
52
67
  return flags.enabled ? "uok-kernel" : "legacy-wrapper";
53
68
  }
54
69
 
55
- export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<void> {
56
- const { ctx, pi, s, deps, runKernelLoop, runLegacyLoop } = args;
57
- const prefs = deps.loadEffectiveGSDPreferences()?.preferences;
58
- const flags = resolveUokFlags(prefs);
59
- setUnifiedAuditEnabled(flags.auditUnified);
70
+ function createUokKernelRunPlan(input: {
71
+ preferences: ReturnType<LoopDeps["loadEffectiveGSDPreferences"]> | undefined;
72
+ autoStartTime?: unknown;
73
+ }): UokKernelRunPlan {
74
+ const flags = resolveUokFlags(input.preferences?.preferences);
60
75
  const pathLabel = resolveKernelPathLabel(flags);
61
- if (pathLabel !== "uok-kernel") {
62
- incrementLegacyTelemetry("legacy.uokFallbackUsed");
63
- }
64
-
65
- writeParityEvent(s.basePath, {
66
- ts: new Date().toISOString(),
67
- path: pathLabel,
76
+ return {
68
77
  flags,
69
- phase: "enter",
70
- });
78
+ pathLabel,
79
+ traceId: `session:${String(input.autoStartTime || Date.now())}`,
80
+ useKernel: flags.enabled,
81
+ };
82
+ }
83
+
84
+ interface KernelAuditStateController {
85
+ apply(enabled: boolean): void;
86
+ restore(): void;
87
+ }
88
+
89
+ function createKernelAuditStateController(basePath: string): KernelAuditStateController {
90
+ const previousAuditOverride = getUnifiedAuditOverride();
91
+ return {
92
+ apply(enabled): void {
93
+ setUnifiedAuditSuppressedForBasePath(basePath, !enabled);
94
+ setUnifiedAuditEnabled(enabled);
95
+ },
96
+ restore(): void {
97
+ restoreUnifiedAuditOverride(previousAuditOverride);
98
+ },
99
+ };
100
+ }
71
101
 
72
- if (flags.auditUnified) {
102
+ function emitKernelEnterAudit(input: {
103
+ basePath: string;
104
+ plan: UokKernelRunPlan;
105
+ sessionId?: string;
106
+ }): boolean {
107
+ if (!input.plan.flags.auditUnified) return true;
108
+
109
+ try {
73
110
  emitUokAuditEvent(
74
- s.basePath,
111
+ input.basePath,
75
112
  buildAuditEnvelope({
76
- traceId: `session:${String(s.autoStartTime || Date.now())}`,
113
+ traceId: input.plan.traceId,
77
114
  category: "orchestration",
78
115
  type: "uok-kernel-enter",
79
116
  payload: {
80
- flags,
81
- sessionId: ctx.sessionManager?.getSessionId?.(),
117
+ flags: input.plan.flags,
118
+ sessionId: input.sessionId,
82
119
  },
83
120
  }),
84
121
  );
122
+ return true;
123
+ } catch (err) {
124
+ const errorMessage = err instanceof Error ? err.message : String(err);
125
+ writeParityEvent(input.basePath, {
126
+ ts: new Date().toISOString(),
127
+ path: input.plan.pathLabel,
128
+ flags: input.plan.flags,
129
+ phase: "telemetry-error",
130
+ telemetry: "uok-kernel-enter",
131
+ error: errorMessage,
132
+ });
133
+ logWarning("db", `uok-kernel-enter audit emit failed (non-fatal): ${errorMessage}`);
134
+ return false;
85
135
  }
136
+ }
86
137
 
87
- const decoratedDeps: LoopDeps = flags.enabled
88
- ? {
89
- ...deps,
90
- uokObserver: createTurnObserver({
91
- basePath: s.basePath,
92
- gitAction: flags.gitopsTurnAction,
93
- gitPush: flags.gitopsTurnPush,
94
- enableAudit: flags.auditUnified,
95
- enableGitops: flags.gitops,
96
- }),
97
- }
98
- : deps;
138
+ function decorateLoopDepsForKernel(input: {
139
+ deps: LoopDeps;
140
+ basePath: string;
141
+ plan: UokKernelRunPlan;
142
+ auditHealthy: boolean;
143
+ }): LoopDeps {
144
+ if (!input.plan.useKernel) return input.deps;
145
+
146
+ return {
147
+ ...input.deps,
148
+ uokObserver: createTurnObserver({
149
+ basePath: input.basePath,
150
+ gitAction: input.plan.flags.gitopsTurnAction,
151
+ gitPush: input.plan.flags.gitopsTurnPush,
152
+ enableAudit: input.plan.flags.auditUnified && input.auditHealthy,
153
+ enableGitops: input.plan.flags.gitops,
154
+ }),
155
+ };
156
+ }
157
+
158
+ async function executeKernelRunPlan(input: {
159
+ plan: UokKernelRunPlan;
160
+ ctx: ExtensionContext;
161
+ pi: ExtensionAPI;
162
+ s: AutoSession;
163
+ deps: LoopDeps;
164
+ kernelDeps: LoopDeps;
165
+ runKernelLoop: RunAutoLoopWithUokArgs["runKernelLoop"];
166
+ runLegacyLoop: RunAutoLoopWithUokArgs["runLegacyLoop"];
167
+ }): Promise<void> {
168
+ if (input.plan.useKernel) {
169
+ await input.runKernelLoop(input.ctx, input.pi, input.s, input.kernelDeps);
170
+ return;
171
+ }
172
+ await input.runLegacyLoop(input.ctx, input.pi, input.s, input.deps);
173
+ }
174
+
175
+ export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<void> {
176
+ const { ctx, pi, s, deps, runKernelLoop, runLegacyLoop } = args;
177
+ const auditState = createKernelAuditStateController(s.basePath);
178
+ let plan: UokKernelRunPlan | null = null;
99
179
 
100
180
  try {
101
- if (flags.enabled) {
102
- await runKernelLoop(ctx, pi, s, decoratedDeps);
103
- } else {
104
- await runLegacyLoop(ctx, pi, s, deps);
181
+ plan = createUokKernelRunPlan({
182
+ preferences: deps.loadEffectiveGSDPreferences(),
183
+ autoStartTime: s.autoStartTime,
184
+ });
185
+ auditState.apply(plan.flags.auditUnified);
186
+
187
+ if (plan.pathLabel !== "uok-kernel") {
188
+ incrementLegacyTelemetry("legacy.uokFallbackUsed");
105
189
  }
190
+
106
191
  writeParityEvent(s.basePath, {
107
192
  ts: new Date().toISOString(),
108
- path: pathLabel,
109
- flags,
110
- phase: "exit",
111
- status: "ok",
193
+ path: plan.pathLabel,
194
+ flags: plan.flags,
195
+ phase: "enter",
112
196
  });
113
- } catch (err) {
197
+
198
+ const auditHealthy = emitKernelEnterAudit({
199
+ basePath: s.basePath,
200
+ plan,
201
+ sessionId: ctx.sessionManager?.getSessionId?.(),
202
+ });
203
+ auditState.apply(plan.flags.auditUnified && auditHealthy);
204
+
205
+ const kernelDeps = decorateLoopDepsForKernel({
206
+ deps,
207
+ basePath: s.basePath,
208
+ plan,
209
+ auditHealthy,
210
+ });
211
+
212
+ await executeKernelRunPlan({
213
+ plan,
214
+ ctx,
215
+ pi,
216
+ s,
217
+ deps,
218
+ kernelDeps,
219
+ runKernelLoop,
220
+ runLegacyLoop,
221
+ });
222
+
114
223
  writeParityEvent(s.basePath, {
115
224
  ts: new Date().toISOString(),
116
- path: pathLabel,
117
- flags,
225
+ path: plan.pathLabel,
226
+ flags: plan.flags,
118
227
  phase: "exit",
119
- status: "error",
120
- error: err instanceof Error ? err.message : String(err),
228
+ status: "ok",
121
229
  });
230
+ } catch (err) {
231
+ if (plan) {
232
+ writeParityEvent(s.basePath, {
233
+ ts: new Date().toISOString(),
234
+ path: plan.pathLabel,
235
+ flags: plan.flags,
236
+ phase: "exit",
237
+ status: "error",
238
+ error: err instanceof Error ? err.message : String(err),
239
+ });
240
+ }
122
241
  throw err;
242
+ } finally {
243
+ auditState.restore();
123
244
  }
124
245
  }
@@ -1,6 +1,7 @@
1
1
  import type { TaskMetadata } from "../complexity-classifier.js";
2
2
  import { computeTaskRequirements, filterToolsForProvider } from "../model-router.js";
3
3
  import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
4
+ import { isUnifiedAuditEnabled } from "./audit-toggle.js";
4
5
 
5
6
  export interface ModelCandidate {
6
7
  id: string;
@@ -83,23 +84,25 @@ export function applyModelPolicyFilter<T extends ModelCandidate>(
83
84
  };
84
85
  decisions.push(decision);
85
86
 
86
- emitUokAuditEvent(
87
- options.basePath,
88
- buildAuditEnvelope({
89
- traceId: options.traceId,
90
- turnId: options.turnId,
91
- category: "model-policy",
92
- type: allowed ? "model-policy-allow" : "model-policy-deny",
93
- payload: {
94
- modelId: model.id,
95
- provider: model.provider,
96
- api: model.api,
97
- reason,
98
- unitType: options.unitType,
99
- requirements,
100
- },
101
- }),
102
- );
87
+ if (isUnifiedAuditEnabled(options.basePath)) {
88
+ emitUokAuditEvent(
89
+ options.basePath,
90
+ buildAuditEnvelope({
91
+ traceId: options.traceId,
92
+ turnId: options.turnId,
93
+ category: "model-policy",
94
+ type: allowed ? "model-policy-allow" : "model-policy-deny",
95
+ payload: {
96
+ modelId: model.id,
97
+ provider: model.provider,
98
+ api: model.api,
99
+ reason,
100
+ unitType: options.unitType,
101
+ requirements,
102
+ },
103
+ }),
104
+ );
105
+ }
103
106
 
104
107
  if (allowed) eligible.push(model);
105
108
  }
@@ -105,7 +105,7 @@ export function writeWorktreeEventLog(
105
105
  }
106
106
 
107
107
  function emitWorkflowEventAudit(basePath: string, event: WorkflowEvent): void {
108
- if (!isUnifiedAuditEnabled()) return;
108
+ if (!isUnifiedAuditEnabled(basePath)) return;
109
109
  try {
110
110
  const normalized = normalizeWorkflowEventCommand(event.cmd) ?? "unknown";
111
111
  emitUokAuditEvent(
@@ -302,7 +302,7 @@ function _push(
302
302
  _buffer.shift();
303
303
  }
304
304
 
305
- if (_auditBasePath && isUnifiedAuditEnabled()) {
305
+ if (_auditBasePath && isUnifiedAuditEnabled(_auditBasePath)) {
306
306
  try {
307
307
  emitUokAuditEvent(
308
308
  _auditBasePath,
@@ -17,7 +17,7 @@ import type { MilestoneRow } from "./db-milestone-artifact-rows.js";
17
17
  import type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
18
18
  import type { VerificationEvidenceRow } from "./db-verification-evidence-rows.js";
19
19
  import { atomicWriteSync } from "./atomic-write.js";
20
- import { join } from "node:path";
20
+ import { dirname, join } from "node:path";
21
21
  import { mkdirSync, existsSync } from "node:fs";
22
22
  import { readFile } from "node:fs/promises";
23
23
  import { logWarning } from "./workflow-logger.js";
@@ -26,7 +26,7 @@ import { deriveState } from "./state.js";
26
26
  import type { GSDState } from "./types.js";
27
27
  import { renderPlanFromDb, renderRoadmapFromDb } from "./markdown-renderer.js";
28
28
  import { readManifest } from "./workflow-manifest.js";
29
- import { gsdRoot, resolveSliceFile, resolveMilestonePath, resolveSlicePath, gsdProjectionRoot } from "./paths.js";
29
+ import { gsdRoot, resolveMilestoneFile, resolveSliceFile, resolveTaskFile, relTaskFile } from "./paths.js";
30
30
 
31
31
  // ─── Helpers ─────────────────────────────────────────────────────────────
32
32
 
@@ -329,24 +329,10 @@ export function renderSummaryProjection(basePath: string, milestoneId: string, s
329
329
  const evidenceRows = getVerificationEvidence(milestoneId, sliceId, taskId);
330
330
  const content = renderSummaryContent(taskRow, sliceId, milestoneId, evidenceRows);
331
331
 
332
- // Layout-aware: avoid creating a milestones/ directory as a side effect for
333
- // flat-phase projects. If milestonesDir() sees a freshly-created milestones/
334
- // dir it treats the project as legacy, breaking all subsequent path resolution.
335
- const slicePath = resolveSlicePath(basePath, milestoneId, sliceId);
336
- const phaseDir = resolveMilestonePath(basePath, milestoneId);
337
- let dir: string;
338
- if (slicePath && phaseDir && slicePath !== phaseDir) {
339
- // Legacy layout: slice has its own slices/SID/ subdir → tasks/ subdir
340
- dir = join(slicePath, "tasks");
341
- } else if (phaseDir) {
342
- // Flat-phase: task summaries go in the phase dir (no tasks/ subdir needed)
343
- dir = phaseDir;
344
- } else {
345
- // Fallback: legacy hardcoded path (milestone dir not on disk yet)
346
- dir = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
347
- }
348
- mkdirSync(dir, { recursive: true });
349
- atomicWriteSync(join(dir, `${taskId}-SUMMARY.md`), content);
332
+ const summaryPath = resolveTaskFile(basePath, milestoneId, sliceId, taskId, "SUMMARY")
333
+ ?? join(basePath, relTaskFile(basePath, milestoneId, sliceId, taskId, "SUMMARY", getMilestone(milestoneId)?.title));
334
+ mkdirSync(dirname(summaryPath), { recursive: true });
335
+ atomicWriteSync(summaryPath, content);
350
336
  }
351
337
 
352
338
  // ─── STATE.md Projection ────────────────────────────────────────────────
@@ -536,7 +522,7 @@ export async function regenerateIfMissing(
536
522
  filePath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN") ?? "";
537
523
  break;
538
524
  case "ROADMAP":
539
- filePath = join(basePath, ".gsd", "milestones", milestoneId, `${milestoneId}-ROADMAP.md`);
525
+ filePath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP") ?? "";
540
526
  break;
541
527
  case "SUMMARY":
542
528
  // For SUMMARY, we regenerate all task summaries in the slice
@@ -553,8 +539,7 @@ export async function regenerateIfMissing(
553
539
  const doneTasks = taskRows.filter(t => t.status === "done" || t.status === "complete");
554
540
  let regenerated = 0;
555
541
  for (const task of doneTasks) {
556
- const summaryPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", sliceId, "tasks", `${task.id}-SUMMARY.md`);
557
- if (!existsSync(summaryPath)) {
542
+ if (!resolveTaskFile(basePath, milestoneId, sliceId, task.id, "SUMMARY")) {
558
543
  try {
559
544
  renderSummaryProjection(basePath, milestoneId, sliceId, task.id);
560
545
  regenerated++;
@@ -591,7 +576,7 @@ export async function regenerateIfMissing(
591
576
  return !!(resolveSliceFile(basePath, milestoneId, sliceId, "PLAN"));
592
577
  case "ROADMAP":
593
578
  await renderRoadmapFromDb(basePath, milestoneId);
594
- return existsSync(filePath);
579
+ return !!resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
595
580
  case "STATE":
596
581
  await renderStateProjection(basePath);
597
582
  return existsSync(filePath);
@@ -2,14 +2,11 @@
2
2
 
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import {
5
- buildMilestoneFileName,
6
- canonicalPhaseDirName,
7
- isLegacyMilestonesLayout,
8
- milestonesDir,
9
5
  type GsdPathContract,
10
6
  resolveGsdPathContract,
11
7
  resolveMilestoneFile,
12
8
  resolveMilestonePath,
9
+ targetMilestoneFile,
13
10
  normalizeRealPath,
14
11
  } from "./paths.js";
15
12
  import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "./worktree-root.js";
@@ -41,27 +38,14 @@ function tryRealpath(p: string): string {
41
38
  return normalizeRealPath(p);
42
39
  }
43
40
 
44
- function isLegacyMilestoneDirectory(dir: string): boolean {
45
- const parent = dirname(dir);
46
- return parent.endsWith("/milestones") || parent.endsWith("\\milestones");
47
- }
48
-
49
41
  function scopeMilestoneDir(basePath: string, milestoneId: string): string {
50
- return resolveMilestonePath(basePath, milestoneId) ?? join(
51
- milestonesDir(basePath),
52
- isLegacyMilestonesLayout(basePath) ? milestoneId : canonicalPhaseDirName(milestoneId),
53
- );
42
+ return resolveMilestonePath(basePath, milestoneId)
43
+ ?? dirname(targetMilestoneFile(basePath, milestoneId, "ROADMAP"));
54
44
  }
55
45
 
56
46
  function scopeMilestoneFile(basePath: string, milestoneId: string, suffix: string): string {
57
- const existing = resolveMilestoneFile(basePath, milestoneId, suffix);
58
- if (existing) return existing;
59
-
60
- const dir = scopeMilestoneDir(basePath, milestoneId);
61
- const filename = isLegacyMilestoneDirectory(dir)
62
- ? `${milestoneId}-${suffix}.md`
63
- : buildMilestoneFileName(milestoneId, suffix);
64
- return join(dir, filename);
47
+ return resolveMilestoneFile(basePath, milestoneId, suffix)
48
+ ?? targetMilestoneFile(basePath, milestoneId, suffix);
65
49
  }
66
50
 
67
51
  /**
@@ -66,6 +66,30 @@ function splitCommandLine(commandLine: string): string[] {
66
66
  });
67
67
  }
68
68
 
69
+ /**
70
+ * Extra gsd-browser CLI flags to forward verbatim to the launched daemon, from
71
+ * GSD_BROWSER_MCP_EXTRA_ARGS. Unlike GSD_BROWSER_MCP_ARGS (a full args override),
72
+ * these are *appended* to the default `mcp`/`daemon start` invocation so callers
73
+ * keep the managed session/identity flags. Accepts a JSON array (e.g.
74
+ * `["--stealth"]`) or a plain, optionally-quoted command-line string
75
+ * (e.g. `--stealth --browser-path /usr/bin/chromium`). This is the config-level
76
+ * knob for environments where Chrome needs extra launch flags — e.g. containers
77
+ * without unprivileged user namespaces, where `--stealth` supplies Chrome's
78
+ * `--no-sandbox`.
79
+ */
80
+ function resolveExtraGsdBrowserArgs(env: NodeJS.ProcessEnv): string[] {
81
+ const raw = env.GSD_BROWSER_MCP_EXTRA_ARGS?.trim();
82
+ if (!raw) return [];
83
+ if (raw.startsWith("[")) {
84
+ const parsed = parseJsonEnv<unknown>(env, "GSD_BROWSER_MCP_EXTRA_ARGS");
85
+ if (!Array.isArray(parsed)) {
86
+ throw new Error("GSD_BROWSER_MCP_EXTRA_ARGS JSON must be an array of strings");
87
+ }
88
+ return parsed.map(String);
89
+ }
90
+ return splitCommandLine(raw);
91
+ }
92
+
69
93
  function buildPathGsdBrowserVersionInvocation(platform: NodeJS.Platform): { command: string; args: string[] } {
70
94
  if (platform === "win32") {
71
95
  return { command: "cmd", args: ["/d", "/s", "/c", "gsd-browser", "--version"] };
@@ -297,6 +321,10 @@ export function resolveGsdBrowserMcpLaunchConfig(
297
321
  || (preferPathCli ? "gsd-browser" : undefined)
298
322
  || (bundledCliPath ? process.execPath : undefined)
299
323
  || "gsd-browser";
324
+ // Appended after the managed session/identity flags so they survive the
325
+ // daemon-start transformation (which forwards everything after `mcp`), letting
326
+ // callers add extra Chrome/gsd-browser launch flags via one config knob.
327
+ const extraArgs = resolveExtraGsdBrowserArgs(env);
300
328
  const args = Array.isArray(explicitArgs) && explicitArgs.length > 0
301
329
  ? explicitArgs.map(String)
302
330
  : [
@@ -311,6 +339,7 @@ export function resolveGsdBrowserMcpLaunchConfig(
311
339
  identityKey,
312
340
  "--identity-project",
313
341
  identityProject,
342
+ ...extraArgs,
314
343
  ];
315
344
  const cwd = env.GSD_BROWSER_MCP_CWD?.trim() || resolvedProjectRoot;
316
345
 
@@ -244,6 +244,14 @@ export async function showInterviewRound(
244
244
  let completed = false;
245
245
  let removeAbortListener: (() => void) | undefined;
246
246
 
247
+ // Preview-panel scrolling. previewScroll is the first visible line of the
248
+ // side-by-side preview column; the viewport/total are captured on each
249
+ // render so PgUp/PgDn input can clamp against real bounds.
250
+ let previewScroll = 0;
251
+ let previewViewport = 0;
252
+ let previewTotal = 0;
253
+ function resetPreviewScroll() { previewScroll = 0; }
254
+
247
255
  function finish(result: RoundResult) {
248
256
  if (completed) return;
249
257
  completed = true;
@@ -309,6 +317,7 @@ export async function showInterviewRound(
309
317
  if (newIdx === currentIdx) return;
310
318
  saveEditorToState();
311
319
  currentIdx = newIdx;
320
+ resetPreviewScroll();
312
321
  loadStateToEditor();
313
322
  focusNotes = states[currentIdx].notesVisible && states[currentIdx].notes.length > 0;
314
323
  refresh();
@@ -455,9 +464,20 @@ export async function showInterviewRound(
455
464
  if (matchesKey(data, Key.right)) { switchQuestion((currentIdx + 1) % questions.length); return; }
456
465
  }
457
466
 
467
+ // ── Preview scrolling ────────────────────────────────────────
468
+ if (matchesKey(data, Key.pageUp)) {
469
+ if (previewScroll > 0) { previewScroll = Math.max(0, previewScroll - previewViewport); refresh(); }
470
+ return;
471
+ }
472
+ if (matchesKey(data, Key.pageDown)) {
473
+ const maxScroll = Math.max(0, previewTotal - previewViewport);
474
+ if (previewScroll < maxScroll) { previewScroll = Math.min(maxScroll, previewScroll + previewViewport); refresh(); }
475
+ return;
476
+ }
477
+
458
478
  // ── Cursor navigation ────────────────────────────────────────
459
- if (matchesKey(data, Key.up)) { st.cursorIndex = (st.cursorIndex - 1 + optCount) % optCount; refresh(); return; }
460
- if (matchesKey(data, Key.down)) { st.cursorIndex = (st.cursorIndex + 1) % optCount; refresh(); return; }
479
+ if (matchesKey(data, Key.up)) { st.cursorIndex = (st.cursorIndex - 1 + optCount) % optCount; resetPreviewScroll(); refresh(); return; }
480
+ if (matchesKey(data, Key.down)) { st.cursorIndex = (st.cursorIndex + 1) % optCount; resetPreviewScroll(); refresh(); return; }
461
481
 
462
482
  if (multiSel) {
463
483
  const doneI = noneOrDoneIdx(currentIdx);
@@ -562,6 +582,12 @@ export async function showInterviewRound(
562
582
 
563
583
  // ── Preview helpers ──────────────────────────────────────────────
564
584
 
585
+ // Centered dim label between horizontal rules, used to mark hidden/scrollable rows.
586
+ function scrollIndicator(lbl: string, colWidth: number): string {
587
+ const d = "─".repeat(Math.max(0, Math.floor((colWidth - lbl.length - 2) / 2)));
588
+ return truncateToWidth(theme.fg("dim", ` ${d} ${lbl} ${d}`), colWidth);
589
+ }
590
+
565
591
  let mdThemeCache: ReturnType<typeof getMarkdownTheme> | null = null;
566
592
  let previewCache: { markdown: string; width: number; lines: string[] } | null = null;
567
593
 
@@ -666,6 +692,10 @@ export async function showInterviewRound(
666
692
  const useSideBySide = questionHasAnyPreview()
667
693
  && contentWidth >= (MIN_OPTIONS_WIDTH + MIN_PREVIEW_WIDTH + DIVIDER_WIDTH);
668
694
 
695
+ // No scrollable preview unless the side-by-side path sets these below.
696
+ previewTotal = 0;
697
+ previewViewport = 0;
698
+
669
699
  if (useSideBySide) {
670
700
  // ── Preview path ──────────────────────────────────────
671
701
  const ui = makeUI(theme, contentWidth);
@@ -707,19 +737,35 @@ export async function showInterviewRound(
707
737
  const leftLines = fullLeft.slice(0, maxBody);
708
738
  if (fullLeft.length > maxBody) {
709
739
  const n = fullLeft.length - maxBody + 1;
710
- const lbl = `+${n} lines hidden`;
711
- const d = "─".repeat(Math.max(0, Math.floor((leftWidth - lbl.length - 2) / 2)));
712
- leftLines[maxBody - 1] = truncateToWidth(theme.fg("dim", ` ${d} ${lbl} ${d}`), leftWidth);
740
+ leftLines[maxBody - 1] = scrollIndicator(`+${n} lines hidden`, leftWidth);
713
741
  }
714
742
 
715
743
  const preview = getCurrentPreview();
716
744
  const fullRight = preview ? renderPreviewColumn(preview, previewWidth) : [];
717
- const rightLines = fullRight.slice(0, maxBody);
718
- if (fullRight.length > maxBody) {
719
- const n = fullRight.length - maxBody + 1;
720
- const lbl = `+${n} lines hidden`;
721
- const d = "─".repeat(Math.max(0, Math.floor((previewWidth - lbl.length - 2) / 2)));
722
- rightLines[maxBody - 1] = truncateToWidth(theme.fg("dim", ` ${d} ${lbl} ${d}`), previewWidth);
745
+ // The first two lines are the pinned "Preview" header; the body
746
+ // below them scrolls via PgUp/PgDn so no content is unreachable.
747
+ const headerRows = fullRight.length > 0 ? 2 : 0;
748
+ const previewBody = fullRight.slice(headerRows);
749
+ const bodyViewport = Math.max(1, maxBody - headerRows);
750
+ previewTotal = previewBody.length;
751
+ previewViewport = bodyViewport;
752
+ const maxScroll = Math.max(0, previewBody.length - bodyViewport);
753
+ if (previewScroll > maxScroll) previewScroll = maxScroll;
754
+
755
+ const rightLines = fullRight.slice(0, headerRows);
756
+ if (previewBody.length <= bodyViewport) {
757
+ rightLines.push(...previewBody);
758
+ } else {
759
+ rightLines.push(...previewBody.slice(previewScroll, previewScroll + bodyViewport));
760
+ const hiddenAbove = previewScroll;
761
+ const hiddenBelow = previewBody.length - (previewScroll + bodyViewport);
762
+ // Overwrite the top/bottom visible rows with scroll indicators.
763
+ if (hiddenAbove > 0) {
764
+ rightLines[headerRows] = scrollIndicator(`▲ ${hiddenAbove} more`, previewWidth);
765
+ }
766
+ if (hiddenBelow > 0) {
767
+ rightLines[rightLines.length - 1] = scrollIndicator(`▼ ${hiddenBelow} more · PgUp/PgDn`, previewWidth);
768
+ }
723
769
  }
724
770
 
725
771
  while (leftLines.length < maxBody) leftLines.push("");
@@ -744,6 +790,7 @@ export async function showInterviewRound(
744
790
  if (isMultiQuestion) hints.push("←/→ navigate");
745
791
  hints.push(isLast && allAnswered() ? "enter to review" : "enter to next");
746
792
  }
793
+ if (previewTotal > previewViewport) hints.push("pgup/pgdn scroll preview");
747
794
  hints.push("esc to exit");
748
795
  const footer = ui.hints(hints)[0] ?? "";
749
796
 
@@ -16,6 +16,8 @@ import {
16
16
  const ANSI_PATTERN = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
17
17
  const ENTER = "\r";
18
18
  const ESC = "\x1b";
19
+ const PAGE_DOWN = "\x1b[6~";
20
+ const PAGE_UP = "\x1b[5~";
19
21
 
20
22
  before(() => { initTheme(); });
21
23
 
@@ -160,4 +162,34 @@ describe("interview-ui dialog borders", () => {
160
162
  const widget = await captureWrapUpWidget();
161
163
  assertFullOuterBorder(widget.render(80), 80);
162
164
  });
165
+
166
+ it("makes an overflowing preview scrollable with PgUp/PgDn", async () => {
167
+ const longPreview = Array.from({ length: 60 }, (_, i) => `line ${i + 1}`).join("\n");
168
+ const widget = await captureInterviewWidget([{
169
+ ...questions[0],
170
+ options: [
171
+ { label: "Web App", description: "Frontend or full-stack", preview: longPreview },
172
+ ],
173
+ }]);
174
+
175
+ // Initial render: content overflows, so a bottom "more" indicator and the
176
+ // scroll hint are shown, and the border stays intact.
177
+ const initial = stripAnsi(widget.render(100).join("\n"));
178
+ assertFullOuterBorder(widget.render(100), 100);
179
+ assert.match(initial, /▼ \d+ more/, "bottom scroll indicator should appear when preview overflows");
180
+ assert.match(initial, /pgup\/pgdn scroll preview/, "footer should hint at preview scrolling");
181
+ assert.doesNotMatch(initial, /▲ \d+ more/, "top indicator should be absent before scrolling");
182
+
183
+ // Scroll down: content below is now revealed and a top indicator appears.
184
+ widget.handleInput(PAGE_DOWN);
185
+ const scrolled = stripAnsi(widget.render(100).join("\n"));
186
+ assertFullOuterBorder(widget.render(100), 100);
187
+ assert.match(scrolled, /▲ \d+ more/, "top scroll indicator should appear after scrolling down");
188
+ assert.ok(scrolled !== initial, "scrolling should change the rendered preview");
189
+
190
+ // Scroll back up returns to the original view.
191
+ widget.handleInput(PAGE_UP);
192
+ const back = stripAnsi(widget.render(100).join("\n"));
193
+ assert.doesNotMatch(back, /▲ \d+ more/, "top indicator should disappear after scrolling back to top");
194
+ });
163
195
  });