@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.4fb2d460

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 (334) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/ask-user-questions.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
  6. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  7. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  8. package/dist/resources/extensions/gsd/auto-artifact-paths.js +12 -3
  9. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -2
  11. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  12. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  13. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  14. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  15. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  16. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  17. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  18. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  19. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  20. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  32. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  33. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  34. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  35. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  36. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  37. package/dist/resources/extensions/gsd/auto.js +9 -3
  38. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
  40. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  41. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
  42. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  43. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  44. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  45. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  46. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  47. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  48. package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  51. package/dist/resources/extensions/gsd/git-service.js +45 -0
  52. package/dist/resources/extensions/gsd/gsd-db.js +23 -11
  53. package/dist/resources/extensions/gsd/guided-flow.js +22 -17
  54. package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
  55. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  56. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  57. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  58. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  59. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  60. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  61. package/dist/resources/extensions/gsd/paths.js +17 -3
  62. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  63. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  64. package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
  65. package/dist/resources/extensions/gsd/preferences.js +20 -0
  66. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  67. package/dist/resources/extensions/gsd/quick.js +12 -3
  68. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  69. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  70. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  71. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  72. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  73. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  74. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  75. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  76. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  77. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  78. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  79. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  80. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  81. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  85. package/dist/web/standalone/.next/build-manifest.json +2 -2
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.html +1 -1
  104. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  111. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  113. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  114. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  115. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  116. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  117. package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
  118. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  119. package/package.json +6 -6
  120. package/packages/cloud-mcp-gateway/package.json +2 -2
  121. package/packages/contracts/package.json +1 -1
  122. package/packages/daemon/package.json +4 -4
  123. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  124. package/packages/gsd-agent-core/package.json +5 -5
  125. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  126. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  127. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  128. package/packages/gsd-agent-modes/package.json +7 -7
  129. package/packages/mcp-server/package.json +4 -4
  130. package/packages/native/package.json +1 -1
  131. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  132. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  133. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  134. package/packages/pi-agent-core/package.json +1 -1
  135. package/packages/pi-ai/dist/index.d.ts +1 -0
  136. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/index.js +1 -0
  138. package/packages/pi-ai/dist/index.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  141. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  142. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  143. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  144. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  145. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  146. package/packages/pi-ai/dist/types.d.ts +8 -20
  147. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/types.js.map +1 -1
  149. package/packages/pi-ai/package.json +1 -1
  150. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  151. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  153. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  155. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  157. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  159. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  160. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  161. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  162. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  163. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  165. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  167. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/index.js +1 -0
  169. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  170. package/packages/pi-coding-agent/package.json +7 -7
  171. package/packages/pi-tui/package.json +2 -2
  172. package/packages/rpc-client/package.json +2 -2
  173. package/pkg/dist/core/export-html/template.js +53 -4
  174. package/pkg/package.json +1 -1
  175. package/src/resources/extensions/ask-user-questions.ts +1 -1
  176. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  177. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
  178. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  179. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  180. package/src/resources/extensions/gsd/auto-artifact-paths.ts +12 -3
  181. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
  183. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  184. package/src/resources/extensions/gsd/auto-start.ts +5 -3
  185. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  186. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  187. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  188. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  189. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  190. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  191. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  192. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  193. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  194. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  195. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  196. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  197. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  198. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  199. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  200. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  201. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  202. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  203. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  204. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  205. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  206. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  207. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  208. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  209. package/src/resources/extensions/gsd/auto.ts +11 -17
  210. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
  212. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  213. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
  214. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  215. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  216. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  217. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  218. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  219. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
  221. package/src/resources/extensions/gsd/forensics.ts +1 -1
  222. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  223. package/src/resources/extensions/gsd/git-service.ts +53 -0
  224. package/src/resources/extensions/gsd/gsd-db.ts +33 -11
  225. package/src/resources/extensions/gsd/guided-flow.ts +26 -18
  226. package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
  227. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  228. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  229. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  230. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  231. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  232. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  233. package/src/resources/extensions/gsd/paths.ts +21 -3
  234. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  235. package/src/resources/extensions/gsd/preferences-types.ts +30 -0
  236. package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
  237. package/src/resources/extensions/gsd/preferences.ts +20 -0
  238. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  239. package/src/resources/extensions/gsd/quick.ts +11 -3
  240. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  241. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  242. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  243. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  244. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  245. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  246. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  248. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  249. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  250. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  251. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  252. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  253. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  254. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  255. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  256. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  257. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  258. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  259. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  260. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  261. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  262. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  263. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  265. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  266. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
  268. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
  269. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  270. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  271. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  272. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  273. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  274. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  275. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  276. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  277. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  278. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  279. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  280. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  281. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  282. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  283. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  284. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  285. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  287. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  289. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  291. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  292. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  293. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  294. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  295. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  296. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  297. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  298. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  299. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  300. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  301. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  302. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  303. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  304. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  305. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  306. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  307. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
  308. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  309. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  310. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  311. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  312. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  313. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  314. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  315. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  316. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  317. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  319. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  320. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  321. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  322. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  323. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  324. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  325. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  326. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  327. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  328. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  329. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  330. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  331. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  332. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  333. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_buildManifest.js +0 -0
  334. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_ssgManifest.js +0 -0
@@ -85,6 +85,18 @@ export interface UnitWorkflowDispatchReadinessInput {
85
85
  activeTools?: string[];
86
86
  }
87
87
 
88
+ export interface WorkflowDispatchModelContext {
89
+ provider?: string;
90
+ baseUrl?: string;
91
+ }
92
+
93
+ export interface UnitWorkflowDispatchReadinessForModelInput
94
+ extends Omit<UnitWorkflowDispatchReadinessInput, "provider" | "authMode" | "baseUrl"> {
95
+ model?: WorkflowDispatchModelContext | null;
96
+ fallbackModel?: WorkflowDispatchModelContext | null;
97
+ getProviderAuthMode?: (provider: string) => WorkflowCapabilityOptions["authMode"] | undefined;
98
+ }
99
+
88
100
  export function compileUnitContextContract(unitType: string): UnitContextContractResult {
89
101
  const manifest = resolveManifest(unitType);
90
102
  if (!manifest) {
@@ -115,6 +127,23 @@ export function getUnitWorkflowDispatchReadinessError(
115
127
  );
116
128
  }
117
129
 
130
+ export function getUnitWorkflowDispatchReadinessErrorForModel(
131
+ input: UnitWorkflowDispatchReadinessForModelInput,
132
+ ): string | null {
133
+ const provider = input.model?.provider ?? input.fallbackModel?.provider;
134
+ const baseUrl = input.model?.baseUrl ?? input.fallbackModel?.baseUrl;
135
+ return getUnitWorkflowDispatchReadinessError({
136
+ provider,
137
+ unitType: input.unitType,
138
+ projectRoot: input.projectRoot,
139
+ env: input.env,
140
+ surface: input.surface,
141
+ authMode: provider ? input.getProviderAuthMode?.(provider) : undefined,
142
+ baseUrl,
143
+ activeTools: input.activeTools,
144
+ });
145
+ }
146
+
118
147
  export function compileUnitToolContract(unitType: string): ToolContractResult {
119
148
  const manifest = resolveManifest(unitType);
120
149
  const surfaceContract = getUnitToolSurfaceContract(unitType);
@@ -5,6 +5,11 @@
5
5
  * tasks back to "pending". This is intentional — if you're reopening a
6
6
  * slice, you're re-doing the work. Partial resets create ambiguous state.
7
7
  *
8
+ * Also recovers a desynced slice (#1205): when a UAT→planning fallback leaves
9
+ * the slice open (e.g. "pending") but its tasks still "complete", this clears
10
+ * that state so the planner can re-plan without hitting "already complete"
11
+ * rejections.
12
+ *
8
13
  * The parent milestone must still be open (not complete).
9
14
  */
10
15
 
@@ -24,8 +24,8 @@ import { writeManifest } from "../workflow-manifest.js";
24
24
  import { appendEvent } from "../workflow-events.js";
25
25
  import { logWarning } from "../workflow-logger.js";
26
26
  import { existsSync, unlinkSync } from "node:fs";
27
- import { join } from "node:path";
28
- import { resolveTasksDir, clearPathCache } from "../paths.js";
27
+ import { join, basename, dirname } from "node:path";
28
+ import { resolveTasksDir, resolveSlicePath, clearPathCache } from "../paths.js";
29
29
 
30
30
  export interface ReopenTaskParams {
31
31
  milestoneId: string;
@@ -106,10 +106,18 @@ export async function handleReopenTask(
106
106
  // ── Clean up stale filesystem artifacts (M12 fix) ────────────────────────
107
107
  // Without this, the DB-filesystem reconciler sees the SUMMARY.md and
108
108
  // auto-corrects the task back to "complete", making reopen a no-op (#3161).
109
+ // Legacy layout keeps the summary under a tasks/ subdir; flat-phase writes
110
+ // TID-SUMMARY.md directly in the phase dir. A tasks/ subdir may still exist
111
+ // in flat-phase for auxiliary artifacts — its mere existence must NOT redirect
112
+ // summary cleanup into tasks/ (#1208).
109
113
  try {
110
- const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
111
- if (tasksDir) {
112
- const summaryPath = join(tasksDir, `${params.taskId}-SUMMARY.md`);
114
+ const slicePath = resolveSlicePath(basePath, params.milestoneId, params.sliceId);
115
+ if (slicePath) {
116
+ const isLegacySlice = basename(dirname(slicePath)) === "slices";
117
+ const summaryDir = isLegacySlice
118
+ ? (resolveTasksDir(basePath, params.milestoneId, params.sliceId) ?? slicePath)
119
+ : slicePath;
120
+ const summaryPath = join(summaryDir, `${params.taskId}-SUMMARY.md`);
113
121
  if (existsSync(summaryPath)) unlinkSync(summaryPath);
114
122
  }
115
123
  } catch (cleanupErr) {
@@ -21,8 +21,8 @@
21
21
  // Not yet declared here (remaining ADR-033 steps): the manifest data
22
22
  // (`UNIT_MANIFESTS` stays in unit-context-manifest.ts, already type-enforced
23
23
  // against the registry's `UnitType`). Prompt-template association is declared
24
- // for direct one-template units; conditional/composite prompt selection stays
25
- // with the prompt builders until it has a richer registry shape.
24
+ // for direct one-template units and verified conditional template sets;
25
+ // composition logic stays with prompt builders.
26
26
 
27
27
  import type { CanonicalWorkflowToolName } from "@opengsd/contracts";
28
28
  import { BROWSER_CONTRACT_TOOL_NAMES } from "../shared/browser-contract.js";
@@ -68,6 +68,11 @@ export interface UnitDescriptor {
68
68
  * verified prompt associations.
69
69
  */
70
70
  readonly promptTemplate?: string;
71
+ /**
72
+ * Prompt template ids a conditional or composite builder may choose from.
73
+ * Omitted unless every listed association is verified against the builder.
74
+ */
75
+ readonly promptTemplates?: readonly string[];
71
76
  /** `null` = the unit has no scoped gsd-tool contract. */
72
77
  readonly toolContract: UnitToolSurfaceContract | null;
73
78
  }
@@ -137,6 +142,7 @@ export const UNIT_REGISTRY = {
137
142
  kind: "primary",
138
143
  scopeClass: "standard",
139
144
  phaseChain: ["discuss", "planning"],
145
+ promptTemplates: ["discuss", "discuss-headless", "guided-discuss-milestone"],
140
146
  toolContract: {
141
147
  allowedGsdTools: [
142
148
  "gsd_summary_save",
@@ -526,3 +532,11 @@ export function getUnitPhaseChain(unitType: string): readonly GSDModelPhaseKey[]
526
532
  export function getUnitPromptTemplate(unitType: string): string | undefined {
527
533
  return getUnitDescriptor(unitType)?.promptTemplate;
528
534
  }
535
+
536
+ /** Prompt template ids for any verified direct or conditional prompt builder. */
537
+ export function getUnitPromptTemplates(unitType: string): readonly string[] {
538
+ const descriptor = getUnitDescriptor(unitType);
539
+ if (!descriptor) return [];
540
+ if (descriptor.promptTemplates) return descriptor.promptTemplates;
541
+ return descriptor.promptTemplate ? [descriptor.promptTemplate] : [];
542
+ }
@@ -9,7 +9,7 @@ import {
9
9
  nativeDiffNumstat,
10
10
  nativeIsAncestor,
11
11
  } from "./native-git-bridge.js";
12
- import { autoWorktreeBranch } from "./auto-worktree.js";
12
+ import { autoWorktreeBranch } from "./auto-worktree-branch-lifecycle.js";
13
13
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
14
14
  import { getAllMilestones } from "./gsd-db.js";
15
15
  import { resolveMilestoneIntegrationBranch } from "./git-service.js";
@@ -3,7 +3,8 @@
3
3
 
4
4
  import { existsSync } from "node:fs";
5
5
 
6
- import { getAutoWorktreePath, isInAutoWorktree } from "./auto-worktree.js";
6
+ import { isInAutoWorktree } from "./auto-worktree-entry.js";
7
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
7
8
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
8
9
  import { refreshWorkflowDatabaseFromDisk } from "./db-workspace.js";
9
10
  import { getIsolationMode } from "./preferences.js";
@@ -3,8 +3,8 @@
3
3
 
4
4
  import { existsSync, realpathSync } from "node:fs";
5
5
 
6
- import { getAutoWorktreePath } from "./auto-worktree.js";
7
- import { isSafeToAutoResolve } from "./auto-worktree.js";
6
+ import { isSafeToAutoResolve } from "./auto-worktree-conflict-auto-resolve.js";
7
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
8
8
  import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
9
9
  import {
10
10
  listMergeStateBlockers,
@@ -13,7 +13,7 @@
13
13
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
14
14
  import { loadPrompt } from "./prompt-loader.js";
15
15
  import { autoCommitCurrentBranch, getMainBranch, resolveGitHeadPath, nudgeGitBranchCache } from "./worktree.js";
16
- import { runWorktreePostCreateHook } from "./auto-worktree.js";
16
+ import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
17
17
  import { showConfirm } from "../shared/tui.js";
18
18
  import { gsdRoot, milestonesDir, resolveGsdPathContract } from "./paths.js";
19
19
  import {
@@ -68,19 +68,21 @@ import {
68
68
  getCurrentBranch,
69
69
  } from "./worktree.js";
70
70
  import { nativeCheckoutBranch } from "./native-git-bridge.js";
71
- // ADR-016 phase 2 / C2 (#5625): worktree-manager helpers inlined from
72
- // `./auto-worktree.js`. These seven functions are not real seams — Lifecycle
73
- // is the only Module that calls them, and they live alongside the Module's
74
- // other primitives in `auto-worktree.ts`.
71
+ // ADR-016 phase 2 / C2 (#5625): lifecycle uses focused auto-worktree
72
+ // modules instead of the legacy compatibility barrel. Branch, entry/path,
73
+ // creation, and teardown primitives are still implementation-level seams for
74
+ // Lifecycle; tests vary them through `WorktreeLifecycleTestOverrides` below.
75
75
  import {
76
76
  autoWorktreeBranch,
77
- createAutoWorktree,
78
- enterAutoWorktree,
79
77
  enterBranchModeForMilestone,
80
- getAutoWorktreePath,
78
+ } from "./auto-worktree-branch-lifecycle.js";
79
+ import { createAutoWorktree } from "./auto-worktree-creation.js";
80
+ import {
81
+ enterAutoWorktree,
81
82
  isInAutoWorktree,
82
- teardownAutoWorktree,
83
- } from "./auto-worktree.js";
83
+ } from "./auto-worktree-entry.js";
84
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
85
+ import { teardownAutoWorktree } from "./auto-worktree-teardown.js";
84
86
  import { resolveRoadmapForMilestoneMerge } from "./milestone-merge-roadmap.js";
85
87
  import type { MilestoneMergeTransactionRunner } from "./milestone-merge-transaction.js";
86
88
 
@@ -353,7 +355,7 @@ export interface MergeContext {
353
355
  originalBasePath: string;
354
356
  /**
355
357
  * Current worktree path or project root when in branch mode. Used as the
356
- * cwd anchor for `mergeMilestoneToMain` and the source for
358
+ * cwd anchor for the milestone merge transaction and the source for
357
359
  * `Projection.finalizeProjectionForMerge`.
358
360
  */
359
361
  worktreeBasePath: string;
@@ -381,7 +383,8 @@ export interface MergeStandaloneResult {
381
383
  pushed: boolean;
382
384
  /**
383
385
  * Commit message produced by the squash merge, if available. Forwarded
384
- * from `mergeMilestoneToMain`. Only populated when `merged === true`.
386
+ * from the milestone merge transaction. Only populated when
387
+ * `merged === true`.
385
388
  */
386
389
  commitMessage?: string;
387
390
  }
@@ -1373,8 +1376,9 @@ function _mergeBranchModeImpl(
1373
1376
  * **CWD anchor**: anchors `process.cwd()` at `originalBasePath` before
1374
1377
  * non-worktree merge paths to mirror the single-loop guard against ENOENT
1375
1378
  * after teardown (de73fb43d). Worktree-mode merge paths keep the real
1376
- * worktree as cwd because `mergeMilestoneToMain()` infers source worktree
1377
- * state from `process.cwd()`. Best-effort; silent on failure.
1379
+ * worktree as cwd because the default milestone merge transaction delegates
1380
+ * to the legacy merge primitive, which infers source worktree state from
1381
+ * `process.cwd()`. Best-effort; silent on failure.
1378
1382
  *
1379
1383
  * **Failure handling**: `MergeConflictError` and other unrecoverable errors
1380
1384
  * propagate to the caller. The caller is responsible for any state restore
@@ -1475,8 +1479,8 @@ export function mergeMilestoneStandalone(
1475
1479
 
1476
1480
  // Set cwd to the correct anchor before dispatching to mode implementations.
1477
1481
  // Worktree mode / in-worktree override must run from the live worktree so
1478
- // mergeMilestoneToMain can find worktree-local state; branch mode runs from
1479
- // the original project root. Best-effort for synthetic test paths.
1482
+ // The default merge transaction can find worktree-local state; branch mode
1483
+ // runs from the original project root. Best-effort for synthetic test paths.
1480
1484
  const targetCwd = mode === "worktree" || inWorktree
1481
1485
  ? worktreeBasePath
1482
1486
  : originalBasePath;
@@ -8,7 +8,8 @@
8
8
 
9
9
  import { readdirSync } from "node:fs";
10
10
 
11
- import { enterAutoWorktree, getAutoWorktreePath } from "./auto-worktree.js";
11
+ import { enterAutoWorktree } from "./auto-worktree-entry.js";
12
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
12
13
  import { getIsolationMode } from "./preferences.js";
13
14
  import { allWorktreesDirs } from "./worktree-manager.js";
14
15
  import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "./worktree-root.js";
@@ -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
  });