@opengsd/gsd-pi 1.0.2-dev.e9a1b49 → 1.0.2-dev.fb7ddf1

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 (490) hide show
  1. package/README.md +63 -12
  2. package/dist/headless-answers.js +2 -1
  3. package/dist/headless-events.d.ts +1 -0
  4. package/dist/headless-events.js +8 -1
  5. package/dist/onboarding.js +22 -3
  6. package/dist/resource-loader.d.ts +7 -0
  7. package/dist/resource-loader.js +44 -9
  8. package/dist/resources/.managed-resources-content-hash +1 -1
  9. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +34 -11
  10. package/dist/resources/extensions/context7/index.js +12 -2
  11. package/dist/resources/extensions/get-secrets-from-user.js +16 -16
  12. package/dist/resources/extensions/google-cli/index.js +30 -0
  13. package/dist/resources/extensions/google-cli/models.js +55 -0
  14. package/dist/resources/extensions/google-cli/package.json +11 -0
  15. package/dist/resources/extensions/google-cli/readiness.js +12 -0
  16. package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
  17. package/dist/resources/extensions/gsd/auto/loop.js +81 -1
  18. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -2
  19. package/dist/resources/extensions/gsd/auto/phases.js +38 -1
  20. package/dist/resources/extensions/gsd/auto/run-unit.js +8 -0
  21. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  22. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -7
  23. package/dist/resources/extensions/gsd/auto-post-unit.js +65 -16
  24. package/dist/resources/extensions/gsd/auto-prompts.js +5 -236
  25. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  26. package/dist/resources/extensions/gsd/auto-start.js +232 -49
  27. package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -1
  28. package/dist/resources/extensions/gsd/auto-verification.js +14 -2
  29. package/dist/resources/extensions/gsd/auto-worktree.js +36 -55
  30. package/dist/resources/extensions/gsd/auto.js +40 -2
  31. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
  32. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -2
  33. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +39 -5
  34. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +107 -27
  35. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -27
  36. package/dist/resources/extensions/gsd/bootstrap/tool-search-shim.js +4 -4
  37. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
  38. package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
  39. package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
  40. package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
  41. package/dist/resources/extensions/gsd/commands-usage.js +105 -1
  42. package/dist/resources/extensions/gsd/config-overlay.js +20 -14
  43. package/dist/resources/extensions/gsd/context-overlay.js +22 -16
  44. package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
  45. package/dist/resources/extensions/gsd/doctor-engine-checks.js +87 -0
  46. package/dist/resources/extensions/gsd/doctor-git-checks.js +70 -5
  47. package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
  48. package/dist/resources/extensions/gsd/doctor.js +7 -2
  49. package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
  50. package/dist/resources/extensions/gsd/guided-flow.js +5 -6
  51. package/dist/resources/extensions/gsd/key-manager.js +45 -13
  52. package/dist/resources/extensions/gsd/mcp-filter.js +57 -18
  53. package/dist/resources/extensions/gsd/mcp-project-config.js +15 -9
  54. package/dist/resources/extensions/gsd/migration-auto-check.js +5 -1
  55. package/dist/resources/extensions/gsd/milestone-actions.js +3 -0
  56. package/dist/resources/extensions/gsd/milestone-reopen-events.js +28 -0
  57. package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
  58. package/dist/resources/extensions/gsd/parallel-merge.js +6 -4
  59. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
  60. package/dist/resources/extensions/gsd/post-execution-checks.js +5 -4
  61. package/dist/resources/extensions/gsd/preferences-skills.js +11 -4
  62. package/dist/resources/extensions/gsd/preferences.js +14 -2
  63. package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
  64. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
  65. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  66. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  67. package/dist/resources/extensions/gsd/prompts/system.md +3 -20
  68. package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
  69. package/dist/resources/extensions/gsd/repo-identity.js +36 -6
  70. package/dist/resources/extensions/gsd/repository-registry.js +3 -1
  71. package/dist/resources/extensions/gsd/safety/evidence-collector.js +13 -6
  72. package/dist/resources/extensions/gsd/skill-activation.js +233 -0
  73. package/dist/resources/extensions/gsd/skill-catalog.data.js +820 -0
  74. package/dist/resources/extensions/gsd/skill-catalog.install.js +179 -0
  75. package/dist/resources/extensions/gsd/skill-catalog.js +5 -1028
  76. package/dist/resources/extensions/gsd/skill-discovery.js +121 -79
  77. package/dist/resources/extensions/gsd/skill-scope.js +52 -0
  78. package/dist/resources/extensions/gsd/skill-telemetry.js +6 -39
  79. package/dist/resources/extensions/gsd/skills/gsd-headless/SKILL.md +1 -1
  80. package/dist/resources/extensions/gsd/skills.js +60 -0
  81. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +351 -0
  82. package/dist/resources/extensions/gsd/state-reconciliation/index.js +41 -0
  83. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +4 -0
  84. package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
  85. package/dist/resources/extensions/gsd/tools/exec-tool.js +42 -8
  86. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +63 -2
  87. package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
  88. package/dist/resources/extensions/gsd/unit-context-manifest.js +35 -26
  89. package/dist/resources/extensions/gsd/user-input-boundary.js +1 -1
  90. package/dist/resources/extensions/gsd/vision-ask.js +22 -0
  91. package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
  92. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
  93. package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
  94. package/dist/resources/extensions/gsd/worktree-state-projection.js +29 -0
  95. package/dist/resources/extensions/search-the-web/native-search.js +57 -8
  96. package/dist/resources/extensions/shared/confirm-ui.js +9 -6
  97. package/dist/resources/extensions/shared/dialog-frame.js +42 -0
  98. package/dist/resources/extensions/shared/interview-ui.js +42 -30
  99. package/dist/resources/extensions/shared/next-action-ui.js +6 -6
  100. package/dist/resources/extensions/subagent/index.js +8 -15
  101. package/dist/resources/shared/package-manager-detection.js +36 -0
  102. package/dist/resources/skills/agent-browser/SKILL.md +1 -1
  103. package/dist/resources/skills/api-design/SKILL.md +1 -1
  104. package/dist/resources/skills/code-optimizer/SKILL.md +6 -11
  105. package/dist/resources/skills/create-gsd-extension/SKILL.md +1 -1
  106. package/dist/resources/skills/create-mcp-server/SKILL.md +1 -1
  107. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  108. package/dist/resources/skills/create-skill/workflows/verify-skill.md +2 -10
  109. package/dist/resources/skills/debug-like-expert/references/when-to-research.md +1 -5
  110. package/dist/resources/skills/decompose-into-slices/SKILL.md +3 -3
  111. package/dist/resources/skills/dependency-upgrade/SKILL.md +1 -1
  112. package/dist/resources/skills/forensics/SKILL.md +2 -2
  113. package/dist/resources/skills/grill-me/SKILL.md +1 -1
  114. package/dist/resources/skills/handoff/SKILL.md +1 -1
  115. package/dist/resources/skills/make-interfaces-feel-better/SKILL.md +1 -1
  116. package/dist/resources/skills/observability/SKILL.md +1 -1
  117. package/dist/resources/skills/security-review/SKILL.md +1 -1
  118. package/dist/resources/skills/spike-wrap-up/SKILL.md +1 -1
  119. package/dist/resources/skills/tdd/SKILL.md +1 -1
  120. package/dist/resources/skills/write-docs/SKILL.md +1 -1
  121. package/dist/resources/skills/write-milestone-brief/SKILL.md +1 -1
  122. package/dist/update-check.d.ts +6 -2
  123. package/dist/update-check.js +7 -3
  124. package/dist/web/standalone/.next/BUILD_ID +1 -1
  125. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  126. package/dist/web/standalone/.next/build-manifest.json +2 -2
  127. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  128. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/index.html +1 -1
  146. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  147. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  153. package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
  154. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  156. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  157. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  158. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  159. package/dist/worktree-cli.d.ts +0 -2
  160. package/dist/worktree-cli.js +21 -9
  161. package/package.json +5 -2
  162. package/packages/cloud-mcp-gateway/bin/gsd-cloud-mcp-gateway.js +14 -0
  163. package/packages/cloud-mcp-gateway/package.json +4 -3
  164. package/packages/contracts/dist/rpc.test.js +5 -0
  165. package/packages/contracts/dist/rpc.test.js.map +1 -1
  166. package/packages/contracts/dist/workflow.d.ts +15 -0
  167. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  168. package/packages/contracts/dist/workflow.js +16 -0
  169. package/packages/contracts/dist/workflow.js.map +1 -1
  170. package/packages/contracts/dist/workflow.test.js +1 -0
  171. package/packages/contracts/dist/workflow.test.js.map +1 -1
  172. package/packages/contracts/package.json +1 -1
  173. package/packages/daemon/package.json +4 -4
  174. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +1 -0
  175. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
  176. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +22 -8
  177. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
  178. package/packages/gsd-agent-core/package.json +5 -5
  179. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
  180. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
  181. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
  182. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
  183. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  184. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  185. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
  186. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
  187. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
  188. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  189. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
  190. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
  191. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
  192. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  193. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
  194. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
  195. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
  196. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
  197. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
  198. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
  199. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  200. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  201. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
  202. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
  203. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
  204. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  205. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
  206. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  207. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  208. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
  209. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  210. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  211. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -2
  212. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  213. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
  214. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  215. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
  216. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  217. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  218. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
  219. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  220. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
  221. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  222. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
  223. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  224. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
  225. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
  226. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
  227. package/packages/gsd-agent-modes/package.json +7 -7
  228. package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
  229. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  230. package/packages/mcp-server/dist/server.js +7 -1
  231. package/packages/mcp-server/dist/server.js.map +1 -1
  232. package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
  233. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  234. package/packages/mcp-server/dist/workflow-tools.js +47 -8
  235. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  236. package/packages/mcp-server/package.json +5 -4
  237. package/packages/native/package.json +1 -1
  238. package/packages/pi-agent-core/dist/agent-loop.js +16 -14
  239. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  240. package/packages/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
  241. package/packages/pi-agent-core/dist/harness/skills.js +6 -0
  242. package/packages/pi-agent-core/dist/harness/skills.js.map +1 -1
  243. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts +7 -0
  244. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts.map +1 -1
  245. package/packages/pi-agent-core/dist/harness/system-prompt.js +7 -0
  246. package/packages/pi-agent-core/dist/harness/system-prompt.js.map +1 -1
  247. package/packages/pi-agent-core/package.json +1 -1
  248. package/packages/pi-ai/bin/pi-ai.js +14 -0
  249. package/packages/pi-ai/dist/models.generated.d.ts +48 -206
  250. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  251. package/packages/pi-ai/dist/models.generated.js +73 -226
  252. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  253. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  254. package/packages/pi-ai/dist/providers/anthropic.js +50 -0
  255. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  256. package/packages/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  257. package/packages/pi-ai/dist/providers/openai-responses-shared.js +28 -4
  258. package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  259. package/packages/pi-ai/dist/types.d.ts +2 -0
  260. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  261. package/packages/pi-ai/dist/types.js.map +1 -1
  262. package/packages/pi-ai/dist/utils/tests/tool-search-shim.test.js +29 -1
  263. package/packages/pi-ai/dist/utils/tests/tool-search-shim.test.js.map +1 -1
  264. package/packages/pi-ai/dist/utils/tool-search-shim.d.ts +4 -1
  265. package/packages/pi-ai/dist/utils/tool-search-shim.d.ts.map +1 -1
  266. package/packages/pi-ai/dist/utils/tool-search-shim.js +58 -10
  267. package/packages/pi-ai/dist/utils/tool-search-shim.js.map +1 -1
  268. package/packages/pi-ai/dist/utils/tool-shims.d.ts +1 -1
  269. package/packages/pi-ai/dist/utils/tool-shims.d.ts.map +1 -1
  270. package/packages/pi-ai/dist/utils/tool-shims.js.map +1 -1
  271. package/packages/pi-ai/package.json +3 -2
  272. package/packages/pi-coding-agent/README.md +1 -1
  273. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +2 -2
  274. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  275. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  276. package/packages/pi-coding-agent/dist/core/extensions/loader.js +1 -1
  277. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  278. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/extensions/runner.js +8 -2
  280. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/skills.d.ts +3 -0
  282. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/skills.js +3 -0
  284. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  286. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -7
  287. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  288. package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
  289. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/tools/read.js +5 -4
  291. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  293. package/packages/pi-coding-agent/dist/core/tools/write.js +0 -1
  294. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  295. package/packages/pi-coding-agent/package.json +8 -8
  296. package/packages/pi-tui/package.json +1 -1
  297. package/packages/rpc-client/package.json +2 -2
  298. package/pkg/package.json +1 -1
  299. package/scripts/install/deps.js +10 -0
  300. package/scripts/install/detect-existing.js +17 -3
  301. package/scripts/install/npm-global.js +103 -33
  302. package/scripts/install.js +1 -0
  303. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +36 -11
  304. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +86 -19
  305. package/src/resources/extensions/context7/index.ts +15 -2
  306. package/src/resources/extensions/get-secrets-from-user.ts +17 -16
  307. package/src/resources/extensions/google-cli/index.ts +34 -0
  308. package/src/resources/extensions/google-cli/models.ts +57 -0
  309. package/src/resources/extensions/google-cli/package.json +11 -0
  310. package/src/resources/extensions/google-cli/readiness.ts +15 -0
  311. package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
  312. package/src/resources/extensions/gsd/auto/loop.ts +96 -1
  313. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -2
  314. package/src/resources/extensions/gsd/auto/phases.ts +47 -1
  315. package/src/resources/extensions/gsd/auto/run-unit.ts +10 -0
  316. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  317. package/src/resources/extensions/gsd/auto-dispatch.ts +31 -11
  318. package/src/resources/extensions/gsd/auto-post-unit.ts +101 -18
  319. package/src/resources/extensions/gsd/auto-prompts.ts +4 -284
  320. package/src/resources/extensions/gsd/auto-recovery.ts +10 -7
  321. package/src/resources/extensions/gsd/auto-start.ts +307 -56
  322. package/src/resources/extensions/gsd/auto-tool-tracking.ts +3 -1
  323. package/src/resources/extensions/gsd/auto-verification.ts +18 -2
  324. package/src/resources/extensions/gsd/auto-worktree.ts +47 -57
  325. package/src/resources/extensions/gsd/auto.ts +50 -2
  326. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
  327. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +9 -4
  328. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +42 -5
  329. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +124 -25
  330. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -28
  331. package/src/resources/extensions/gsd/bootstrap/tool-search-shim.ts +4 -4
  332. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
  333. package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
  334. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
  335. package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
  336. package/src/resources/extensions/gsd/commands-usage.ts +110 -5
  337. package/src/resources/extensions/gsd/config-overlay.ts +19 -16
  338. package/src/resources/extensions/gsd/context-overlay.ts +24 -19
  339. package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
  340. package/src/resources/extensions/gsd/doctor-engine-checks.ts +99 -0
  341. package/src/resources/extensions/gsd/doctor-git-checks.ts +72 -5
  342. package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
  343. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  344. package/src/resources/extensions/gsd/doctor.ts +7 -2
  345. package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
  346. package/src/resources/extensions/gsd/guided-flow.ts +5 -6
  347. package/src/resources/extensions/gsd/key-manager.ts +57 -14
  348. package/src/resources/extensions/gsd/mcp-filter.ts +64 -17
  349. package/src/resources/extensions/gsd/mcp-project-config.ts +24 -9
  350. package/src/resources/extensions/gsd/migration-auto-check.ts +6 -0
  351. package/src/resources/extensions/gsd/milestone-actions.ts +2 -0
  352. package/src/resources/extensions/gsd/milestone-reopen-events.ts +28 -0
  353. package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
  354. package/src/resources/extensions/gsd/parallel-merge.ts +6 -4
  355. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
  356. package/src/resources/extensions/gsd/post-execution-checks.ts +7 -4
  357. package/src/resources/extensions/gsd/preferences-skills.ts +11 -4
  358. package/src/resources/extensions/gsd/preferences.ts +17 -2
  359. package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
  360. package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
  361. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  362. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  363. package/src/resources/extensions/gsd/prompts/system.md +3 -20
  364. package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
  365. package/src/resources/extensions/gsd/repo-identity.ts +35 -7
  366. package/src/resources/extensions/gsd/repository-registry.ts +3 -1
  367. package/src/resources/extensions/gsd/safety/evidence-collector.ts +13 -6
  368. package/src/resources/extensions/gsd/skill-activation.ts +292 -0
  369. package/src/resources/extensions/gsd/skill-catalog.data.ts +858 -0
  370. package/src/resources/extensions/gsd/skill-catalog.install.ts +205 -0
  371. package/src/resources/extensions/gsd/skill-catalog.ts +16 -1087
  372. package/src/resources/extensions/gsd/skill-discovery.ts +134 -78
  373. package/src/resources/extensions/gsd/skill-scope.ts +63 -0
  374. package/src/resources/extensions/gsd/skill-telemetry.ts +6 -40
  375. package/src/resources/extensions/gsd/skills/gsd-headless/SKILL.md +1 -1
  376. package/src/resources/extensions/gsd/skills.ts +75 -0
  377. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +499 -0
  378. package/src/resources/extensions/gsd/state-reconciliation/index.ts +40 -0
  379. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +8 -0
  380. package/src/resources/extensions/gsd/state-reconciliation/types.ts +30 -0
  381. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +328 -2
  382. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +75 -0
  383. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +28 -2
  384. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +30 -0
  385. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +41 -0
  386. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +24 -0
  387. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
  388. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +53 -0
  389. package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
  390. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
  391. package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
  392. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
  393. package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
  394. package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +40 -3
  395. package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
  396. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
  397. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
  398. package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +1 -0
  399. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
  400. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +1 -1
  401. package/src/resources/extensions/gsd/tests/doctor-empty-worktree.test.ts +71 -1
  402. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
  403. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +101 -1
  404. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +30 -0
  405. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
  406. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +22 -3
  407. package/src/resources/extensions/gsd/tests/integration/parallel-merge.test.ts +43 -0
  408. package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +8 -0
  409. package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
  410. package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +19 -1
  411. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +24 -0
  412. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +56 -1
  413. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
  414. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
  415. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
  416. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +9 -0
  417. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +127 -10
  418. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +44 -0
  419. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +93 -0
  420. package/src/resources/extensions/gsd/tests/project-relocation-recovery.test.ts +101 -0
  421. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
  422. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +116 -11
  423. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +27 -0
  424. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +30 -1
  425. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
  426. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +111 -0
  427. package/src/resources/extensions/gsd/tests/skill-scope-auto.test.ts +67 -0
  428. package/src/resources/extensions/gsd/tests/skills.test.ts +55 -0
  429. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
  430. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +303 -0
  431. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +67 -1
  432. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
  433. package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
  434. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
  435. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +18 -0
  436. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +26 -0
  437. package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
  438. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
  439. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +133 -0
  440. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +82 -0
  441. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
  442. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
  443. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
  444. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +38 -1
  445. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +10 -0
  446. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
  447. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
  448. package/src/resources/extensions/gsd/tools/exec-tool.ts +42 -10
  449. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +82 -5
  450. package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
  451. package/src/resources/extensions/gsd/unit-context-manifest.ts +37 -26
  452. package/src/resources/extensions/gsd/user-input-boundary.ts +1 -1
  453. package/src/resources/extensions/gsd/vision-ask.ts +28 -0
  454. package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
  455. package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
  456. package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
  457. package/src/resources/extensions/gsd/worktree-state-projection.ts +33 -0
  458. package/src/resources/extensions/search-the-web/native-search.ts +60 -8
  459. package/src/resources/extensions/shared/confirm-ui.ts +8 -12
  460. package/src/resources/extensions/shared/dialog-frame.ts +71 -0
  461. package/src/resources/extensions/shared/interview-ui.ts +43 -42
  462. package/src/resources/extensions/shared/next-action-ui.ts +6 -6
  463. package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
  464. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
  465. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
  466. package/src/resources/extensions/subagent/index.ts +8 -15
  467. package/src/resources/shared/package-manager-detection.ts +39 -0
  468. package/src/resources/skills/agent-browser/SKILL.md +1 -1
  469. package/src/resources/skills/api-design/SKILL.md +1 -1
  470. package/src/resources/skills/code-optimizer/SKILL.md +6 -11
  471. package/src/resources/skills/create-gsd-extension/SKILL.md +1 -1
  472. package/src/resources/skills/create-mcp-server/SKILL.md +1 -1
  473. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  474. package/src/resources/skills/create-skill/workflows/verify-skill.md +2 -10
  475. package/src/resources/skills/debug-like-expert/references/when-to-research.md +1 -5
  476. package/src/resources/skills/decompose-into-slices/SKILL.md +3 -3
  477. package/src/resources/skills/dependency-upgrade/SKILL.md +1 -1
  478. package/src/resources/skills/forensics/SKILL.md +2 -2
  479. package/src/resources/skills/grill-me/SKILL.md +1 -1
  480. package/src/resources/skills/handoff/SKILL.md +1 -1
  481. package/src/resources/skills/make-interfaces-feel-better/SKILL.md +1 -1
  482. package/src/resources/skills/observability/SKILL.md +1 -1
  483. package/src/resources/skills/security-review/SKILL.md +1 -1
  484. package/src/resources/skills/spike-wrap-up/SKILL.md +1 -1
  485. package/src/resources/skills/tdd/SKILL.md +1 -1
  486. package/src/resources/skills/write-docs/SKILL.md +1 -1
  487. package/src/resources/skills/write-milestone-brief/SKILL.md +1 -1
  488. package/dist/tsconfig.extensions.tsbuildinfo +0 -1
  489. /package/dist/web/standalone/.next/static/{BEjZM0MLHLibeMFbjtMol → tH1tnDYt1E0hK9Ien73Z0}/_buildManifest.js +0 -0
  490. /package/dist/web/standalone/.next/static/{BEjZM0MLHLibeMFbjtMol → tH1tnDYt1E0hK9Ien73Z0}/_ssgManifest.js +0 -0
@@ -0,0 +1,351 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Fail-closed reconciliation guards for DB/artifact and slice-id drift.
3
+ import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, } from "node:fs";
4
+ import { basename, join } from "node:path";
5
+ import { _getAdapter, getAllMilestones, getMilestoneSlices, getSliceTasks, isDbAvailable, } from "../../gsd-db.js";
6
+ import { clearParseCache } from "../../files.js";
7
+ import { clearPathCache, gsdProjectionRoot, resolveMilestonePath, resolveSliceFile, resolveTaskFile, } from "../../paths.js";
8
+ import { isClosedStatus } from "../../status-guards.js";
9
+ import { invalidateStateCache } from "../../state.js";
10
+ import { isAfter, latestExplicitReopenAt } from "../../milestone-reopen-events.js";
11
+ function safeListArtifactRows(milestoneId) {
12
+ const adapter = _getAdapter();
13
+ if (!adapter)
14
+ return [];
15
+ try {
16
+ return adapter
17
+ .prepare(`SELECT path, artifact_type, milestone_id, slice_id, task_id, imported_at
18
+ FROM artifacts
19
+ WHERE milestone_id = :mid
20
+ ORDER BY imported_at, path`)
21
+ .all({ ":mid": milestoneId });
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ function latestCompletedMilestoneDispatch(milestoneId) {
28
+ const adapter = _getAdapter();
29
+ if (!adapter)
30
+ return null;
31
+ try {
32
+ const row = adapter
33
+ .prepare(`SELECT started_at, ended_at
34
+ FROM unit_dispatches
35
+ WHERE milestone_id = :mid
36
+ AND unit_type = 'complete-milestone'
37
+ AND unit_id = :mid
38
+ AND status = 'completed'
39
+ ORDER BY COALESCE(ended_at, started_at) DESC, id DESC
40
+ LIMIT 1`)
41
+ .get({ ":mid": milestoneId });
42
+ return row ?? null;
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ function hasExplicitReopenAfter(basePath, milestoneId, completedDispatchAt) {
49
+ const reopenAt = latestExplicitReopenAt(basePath, milestoneId);
50
+ if (!reopenAt)
51
+ return false;
52
+ if (!completedDispatchAt)
53
+ return true;
54
+ return Date.parse(reopenAt) > Date.parse(completedDispatchAt);
55
+ }
56
+ function addUniqueDrift(drifts, seen, drift) {
57
+ const key = [
58
+ drift.milestoneId,
59
+ drift.sliceId ?? "",
60
+ drift.taskId ?? "",
61
+ drift.artifactType,
62
+ drift.artifactPath ?? "",
63
+ drift.reason,
64
+ ].join("|");
65
+ if (seen.has(key))
66
+ return;
67
+ seen.add(key);
68
+ drifts.push(drift);
69
+ }
70
+ function detectArtifactDbStatusDriftForMilestone(basePath, milestoneId) {
71
+ const milestone = getAllMilestones().find((m) => m.id === milestoneId);
72
+ if (!milestone || isClosedStatus(milestone.status))
73
+ return [];
74
+ const latestReopen = latestExplicitReopenAt(basePath, milestoneId);
75
+ const artifacts = safeListArtifactRows(milestoneId).filter((row) => isAfter(row.imported_at, latestReopen));
76
+ const bySlice = new Map(getMilestoneSlices(milestoneId).map((slice) => [slice.id, slice]));
77
+ const drifts = [];
78
+ const seen = new Set();
79
+ for (const slice of bySlice.values()) {
80
+ if (!isClosedStatus(slice.status)) {
81
+ const diskSummary = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
82
+ if (diskSummary && existsSync(diskSummary)) {
83
+ addUniqueDrift(drifts, seen, {
84
+ kind: "artifact-db-status-divergence",
85
+ milestoneId,
86
+ sliceId: slice.id,
87
+ artifactType: "SUMMARY",
88
+ artifactPath: diskSummary,
89
+ dbStatus: slice.status,
90
+ reason: `slice ${slice.id} has SUMMARY on disk while DB status is ${slice.status}`,
91
+ });
92
+ }
93
+ }
94
+ const tasks = getSliceTasks(milestoneId, slice.id);
95
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
96
+ const summaryRows = artifacts.filter((row) => row.artifact_type === "SUMMARY" &&
97
+ row.slice_id === slice.id &&
98
+ row.task_id);
99
+ if (tasks.length === 0 && summaryRows.length > 0) {
100
+ addUniqueDrift(drifts, seen, {
101
+ kind: "artifact-db-status-divergence",
102
+ milestoneId,
103
+ sliceId: slice.id,
104
+ artifactType: "SUMMARY",
105
+ artifactPath: summaryRows[0]?.path,
106
+ dbStatus: "no-db-tasks",
107
+ reason: `slice ${slice.id} has task SUMMARY artifacts but no DB tasks`,
108
+ });
109
+ }
110
+ for (const row of summaryRows) {
111
+ const task = row.task_id ? taskById.get(row.task_id) : null;
112
+ if (!task) {
113
+ if (tasks.length > 0) {
114
+ addUniqueDrift(drifts, seen, {
115
+ kind: "artifact-db-status-divergence",
116
+ milestoneId,
117
+ sliceId: slice.id,
118
+ taskId: row.task_id ?? undefined,
119
+ artifactType: "SUMMARY",
120
+ artifactPath: row.path,
121
+ dbStatus: "missing-db-task",
122
+ reason: `task ${slice.id}/${row.task_id} has SUMMARY artifact but no DB task`,
123
+ });
124
+ }
125
+ continue;
126
+ }
127
+ if (isClosedStatus(task.status))
128
+ continue;
129
+ addUniqueDrift(drifts, seen, {
130
+ kind: "artifact-db-status-divergence",
131
+ milestoneId,
132
+ sliceId: slice.id,
133
+ taskId: row.task_id ?? undefined,
134
+ artifactType: "SUMMARY",
135
+ artifactPath: row.path,
136
+ dbStatus: task.status,
137
+ reason: `task ${slice.id}/${row.task_id} has SUMMARY artifact while DB status is ${task.status}`,
138
+ });
139
+ }
140
+ for (const task of tasks) {
141
+ if (isClosedStatus(task.status))
142
+ continue;
143
+ const diskTaskSummary = resolveTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY");
144
+ if (!diskTaskSummary || !existsSync(diskTaskSummary))
145
+ continue;
146
+ addUniqueDrift(drifts, seen, {
147
+ kind: "artifact-db-status-divergence",
148
+ milestoneId,
149
+ sliceId: slice.id,
150
+ taskId: task.id,
151
+ artifactType: "SUMMARY",
152
+ artifactPath: diskTaskSummary,
153
+ dbStatus: task.status,
154
+ reason: `task ${slice.id}/${task.id} has SUMMARY on disk while DB status is ${task.status}`,
155
+ });
156
+ }
157
+ }
158
+ for (const row of artifacts) {
159
+ if (row.artifact_type !== "SUMMARY" || !row.slice_id || row.task_id)
160
+ continue;
161
+ const slice = bySlice.get(row.slice_id);
162
+ if (!slice || isClosedStatus(slice.status))
163
+ continue;
164
+ addUniqueDrift(drifts, seen, {
165
+ kind: "artifact-db-status-divergence",
166
+ milestoneId,
167
+ sliceId: row.slice_id,
168
+ artifactType: "SUMMARY",
169
+ artifactPath: row.path,
170
+ dbStatus: slice.status,
171
+ reason: `slice ${row.slice_id} has SUMMARY artifact while DB status is ${slice.status}`,
172
+ });
173
+ }
174
+ return drifts;
175
+ }
176
+ function classifyDiskOnlySliceDir(sliceDir) {
177
+ let sawScaffold = false;
178
+ const stack = [sliceDir];
179
+ while (stack.length > 0) {
180
+ const dir = stack.pop();
181
+ let entries;
182
+ try {
183
+ entries = readdirSync(dir);
184
+ }
185
+ catch {
186
+ return "block-meaningful";
187
+ }
188
+ if (entries.length === 0 && dir !== sliceDir) {
189
+ sawScaffold = true;
190
+ continue;
191
+ }
192
+ for (const entry of entries) {
193
+ if (entry === ".DS_Store")
194
+ continue;
195
+ const full = join(dir, entry);
196
+ let stat;
197
+ try {
198
+ stat = lstatSync(full);
199
+ }
200
+ catch {
201
+ return "block-meaningful";
202
+ }
203
+ if (stat.isDirectory()) {
204
+ sawScaffold = true;
205
+ stack.push(full);
206
+ continue;
207
+ }
208
+ if (stat.isFile() && stat.size === 0) {
209
+ sawScaffold = true;
210
+ continue;
211
+ }
212
+ return "block-meaningful";
213
+ }
214
+ }
215
+ return sawScaffold ? "quarantine-scaffold" : "delete-empty";
216
+ }
217
+ function detectDiskSliceIdDivergenceForMilestone(basePath, milestoneId) {
218
+ const milestonePath = resolveMilestonePath(basePath, milestoneId);
219
+ if (!milestonePath)
220
+ return [];
221
+ const slicesDir = join(milestonePath, "slices");
222
+ if (!existsSync(slicesDir))
223
+ return [];
224
+ const knownSliceIds = new Set(getMilestoneSlices(milestoneId).map((slice) => slice.id));
225
+ const drifts = [];
226
+ for (const entry of readdirSync(slicesDir)) {
227
+ const sliceDir = join(slicesDir, entry);
228
+ try {
229
+ if (!lstatSync(sliceDir).isDirectory())
230
+ continue;
231
+ }
232
+ catch {
233
+ continue;
234
+ }
235
+ if (entry === "parallel-research")
236
+ continue;
237
+ if (knownSliceIds.has(entry))
238
+ continue;
239
+ const disposition = classifyDiskOnlySliceDir(sliceDir);
240
+ drifts.push({
241
+ kind: "disk-slice-id-divergence",
242
+ milestoneId,
243
+ sliceId: entry,
244
+ sliceDir,
245
+ disposition,
246
+ reason: disposition === "block-meaningful"
247
+ ? `disk-only slice directory ${entry} contains meaningful files and is not in the DB`
248
+ : `disk-only slice directory ${entry} is not in the DB`,
249
+ });
250
+ }
251
+ return drifts;
252
+ }
253
+ export function detectArtifactDbDrift(_state, ctx) {
254
+ if (!isDbAvailable())
255
+ return [];
256
+ const drifts = [];
257
+ for (const milestone of getAllMilestones()) {
258
+ if (isClosedStatus(milestone.status))
259
+ continue;
260
+ const completedDispatch = latestCompletedMilestoneDispatch(milestone.id);
261
+ const completedAt = completedDispatch?.ended_at ?? completedDispatch?.started_at ?? null;
262
+ if (completedDispatch &&
263
+ !hasExplicitReopenAfter(ctx.basePath, milestone.id, completedAt)) {
264
+ drifts.push({
265
+ kind: "completed-milestone-reopened",
266
+ milestoneId: milestone.id,
267
+ dbStatus: milestone.status,
268
+ completedDispatchAt: completedAt,
269
+ });
270
+ }
271
+ drifts.push(...detectArtifactDbStatusDriftForMilestone(ctx.basePath, milestone.id));
272
+ drifts.push(...detectDiskSliceIdDivergenceForMilestone(ctx.basePath, milestone.id));
273
+ }
274
+ return drifts;
275
+ }
276
+ function quarantineSliceDir(record, basePath) {
277
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
278
+ const quarantineDir = join(gsdProjectionRoot(basePath), "quarantine", "milestones", record.milestoneId, "slices");
279
+ mkdirSync(quarantineDir, { recursive: true });
280
+ const target = join(quarantineDir, `${basename(record.sliceDir)}-${stamp}`);
281
+ renameSync(record.sliceDir, target);
282
+ }
283
+ function diskSliceIdDivergenceGuidance(record) {
284
+ const quarantineExample = `.gsd/quarantine/milestones/${record.milestoneId}/slices/${record.sliceId}-manual-review`;
285
+ return (`Slice ID drift in ${record.milestoneId}: ${record.reason}. ` +
286
+ "Runtime will not import disk-only slice IDs into the DB. " +
287
+ `Review ${record.sliceDir}. ` +
288
+ `If ${record.sliceId} is stale, move or delete that directory; to preserve it, move it under ${quarantineExample}. ` +
289
+ "If it contains work to keep, copy or merge that content into a DB-backed slice, or explicitly recreate the slice through GSD planning, then remove the disk-only directory. " +
290
+ `After repair, run /gsd doctor ${record.milestoneId}, then resume with /gsd next or /gsd auto.`);
291
+ }
292
+ export function repairArtifactDbDrift(record, ctx) {
293
+ if (record.kind === "disk-slice-id-divergence") {
294
+ if (record.disposition === "delete-empty") {
295
+ rmSync(record.sliceDir, { recursive: true, force: true });
296
+ }
297
+ else if (record.disposition === "quarantine-scaffold") {
298
+ quarantineSliceDir(record, ctx.basePath);
299
+ }
300
+ else {
301
+ throw new Error(diskSliceIdDivergenceGuidance(record));
302
+ }
303
+ clearPathCache();
304
+ clearParseCache();
305
+ invalidateStateCache();
306
+ return;
307
+ }
308
+ if (record.kind === "completed-milestone-reopened") {
309
+ throw new Error(`Milestone ${record.milestoneId} has completed complete-milestone dispatch history` +
310
+ ` (${record.completedDispatchAt ?? "time unknown"}) but the DB status is ${record.dbStatus}. ` +
311
+ "Refusing to plan it again without an explicit reopen or recovery.");
312
+ }
313
+ throw new Error(`Artifact/DB status drift in ${record.milestoneId}` +
314
+ `${record.sliceId ? `/${record.sliceId}` : ""}` +
315
+ `${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
316
+ "Runtime will not silently import completion artifacts into DB state; run explicit recovery/repair after review.");
317
+ }
318
+ export function describeArtifactDbDriftBlocker(record) {
319
+ if (record.kind === "disk-slice-id-divergence") {
320
+ if (record.disposition !== "block-meaningful")
321
+ return null;
322
+ return diskSliceIdDivergenceGuidance(record);
323
+ }
324
+ if (record.kind === "completed-milestone-reopened") {
325
+ return (`Milestone ${record.milestoneId} has completed complete-milestone dispatch history` +
326
+ ` (${record.completedDispatchAt ?? "time unknown"}) but the DB status is ${record.dbStatus}. ` +
327
+ "Refusing to plan it again without an explicit reopen or recovery.");
328
+ }
329
+ return (`Artifact/DB status drift in ${record.milestoneId}` +
330
+ `${record.sliceId ? `/${record.sliceId}` : ""}` +
331
+ `${record.taskId ? `/${record.taskId}` : ""}: ${record.reason}. ` +
332
+ "Runtime will not silently import completion artifacts into DB state; run explicit recovery/repair after review.");
333
+ }
334
+ export const diskSliceIdDivergenceHandler = {
335
+ kind: "disk-slice-id-divergence",
336
+ detect: (state, ctx) => detectArtifactDbDrift(state, ctx).filter((record) => record.kind === "disk-slice-id-divergence"),
337
+ blocker: describeArtifactDbDriftBlocker,
338
+ repair: repairArtifactDbDrift,
339
+ };
340
+ export const artifactDbStatusDivergenceHandler = {
341
+ kind: "artifact-db-status-divergence",
342
+ detect: (state, ctx) => detectArtifactDbDrift(state, ctx).filter((record) => record.kind === "artifact-db-status-divergence"),
343
+ blocker: describeArtifactDbDriftBlocker,
344
+ repair: repairArtifactDbDrift,
345
+ };
346
+ export const completedMilestoneReopenedHandler = {
347
+ kind: "completed-milestone-reopened",
348
+ detect: (state, ctx) => detectArtifactDbDrift(state, ctx).filter((record) => record.kind === "completed-milestone-reopened"),
349
+ blocker: describeArtifactDbDriftBlocker,
350
+ repair: repairArtifactDbDrift,
351
+ };
@@ -42,6 +42,8 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
42
42
  };
43
43
  }
44
44
  const failures = [];
45
+ const blockers = [];
46
+ let repairedThisPass = false;
45
47
  for (const record of drift) {
46
48
  const handler = registry.find((h) => h.kind === record.kind);
47
49
  if (!handler) {
@@ -51,14 +53,33 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
51
53
  });
52
54
  continue;
53
55
  }
56
+ const blocker = handler.blocker ? await handler.blocker(record, ctx) : null;
57
+ if (blocker) {
58
+ blockers.push(blocker);
59
+ continue;
60
+ }
54
61
  try {
55
62
  await handler.repair(record, ctx);
56
63
  repaired.push(record);
64
+ repairedThisPass = true;
57
65
  }
58
66
  catch (cause) {
59
67
  failures.push({ drift: record, cause });
60
68
  }
61
69
  }
70
+ if (blockers.length > 0) {
71
+ let blockerState = stateSnapshot;
72
+ if (repairedThisPass) {
73
+ deps.invalidateStateCache();
74
+ blockerState = await deps.deriveState(basePath, deps.deriveStateOptions);
75
+ }
76
+ return {
77
+ ok: true,
78
+ stateSnapshot: blockerState,
79
+ repaired,
80
+ blockers: [...new Set([...(blockerState.blockers ?? []), ...blockers])],
81
+ };
82
+ }
62
83
  if (failures.length > 0) {
63
84
  throw new ReconciliationFailedError({ failures, pass });
64
85
  }
@@ -70,6 +91,26 @@ export async function reconcileBeforeDispatch(basePath, deps = defaultDeps) {
70
91
  const finalCtx = { basePath, state: finalState };
71
92
  const persistent = await detectAllDrift(finalState, finalCtx, registry);
72
93
  if (persistent.length > 0) {
94
+ const blockers = [];
95
+ const unblockedPersistent = [];
96
+ for (const record of persistent) {
97
+ const handler = registry.find((h) => h.kind === record.kind);
98
+ const blocker = handler?.blocker ? await handler.blocker(record, finalCtx) : null;
99
+ if (blocker) {
100
+ blockers.push(blocker);
101
+ }
102
+ else {
103
+ unblockedPersistent.push(record);
104
+ }
105
+ }
106
+ if (blockers.length > 0 && unblockedPersistent.length === 0) {
107
+ return {
108
+ ok: true,
109
+ stateSnapshot: finalState,
110
+ repaired,
111
+ blockers: [...new Set([...(finalState.blockers ?? []), ...blockers])],
112
+ };
113
+ }
73
114
  throw new ReconciliationFailedError({ persistentDrift: persistent });
74
115
  }
75
116
  return {
@@ -2,6 +2,7 @@
2
2
  // File Purpose: ADR-017 drift handler registry. Single source of truth for
3
3
  // the catalog. Tests can override per-call via ReconciliationDeps.registry.
4
4
  import { completionTimestampHandler } from "./drift/completion.js";
5
+ import { artifactDbStatusDivergenceHandler, completedMilestoneReopenedHandler, diskSliceIdDivergenceHandler, } from "./drift/artifact-db.js";
5
6
  import { mergeStateHandler } from "./drift/merge-state.js";
6
7
  import { unregisteredMilestoneHandler } from "./drift/project-md.js";
7
8
  import { roadmapDivergenceHandler } from "./drift/roadmap.js";
@@ -19,6 +20,9 @@ export const DRIFT_REGISTRY = [
19
20
  staleRenderHandler,
20
21
  staleWorkerHandler,
21
22
  unregisteredMilestoneHandler,
23
+ diskSliceIdDivergenceHandler,
22
24
  roadmapDivergenceHandler,
25
+ completedMilestoneReopenedHandler,
26
+ artifactDbStatusDivergenceHandler,
23
27
  completionTimestampHandler,
24
28
  ];
@@ -116,6 +116,15 @@ export async function handleCompleteTask(params, basePath) {
116
116
  if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
117
117
  return { error: "milestoneId is required and must be a non-empty string" };
118
118
  }
119
+ if (!params.oneLiner || typeof params.oneLiner !== "string" || params.oneLiner.trim() === "") {
120
+ return { error: "oneLiner is required and must be a non-empty string" };
121
+ }
122
+ if (!params.narrative || typeof params.narrative !== "string" || params.narrative.trim() === "") {
123
+ return { error: "narrative is required and must be a non-empty string" };
124
+ }
125
+ if (!params.verification || typeof params.verification !== "string" || params.verification.trim() === "") {
126
+ return { error: "verification is required and must be a non-empty string" };
127
+ }
119
128
  const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, params.milestoneId);
120
129
  // ── Ownership check (opt-in: only enforced when claim file exists) ──────
121
130
  const ownershipErr = checkOwnership(artifactBasePath, taskUnitKey(params.milestoneId, params.sliceId, params.taskId), params.actorName);
@@ -40,6 +40,42 @@ function paramError(message) {
40
40
  isError: true,
41
41
  };
42
42
  }
43
+ function normalizeRuntime(value) {
44
+ if (value === undefined || value === null || value === "")
45
+ return "bash";
46
+ if (typeof value !== "string") {
47
+ return paramError(`invalid runtime "${String(value)}" — must be bash | node | python`);
48
+ }
49
+ const normalized = value.trim().toLowerCase();
50
+ if (normalized === "" || normalized === "bash" || normalized === "sh" || normalized === "shell")
51
+ return "bash";
52
+ if (normalized === "node" || normalized === "nodejs" || normalized === "js" || normalized === "javascript")
53
+ return "node";
54
+ if (normalized === "python" || normalized === "python3" || normalized === "py")
55
+ return "python";
56
+ return paramError(`invalid runtime "${value}" — must be bash | node | python`);
57
+ }
58
+ function normalizeScript(params) {
59
+ const candidates = [params.script, params.command, params.cmd, params.code];
60
+ let sawNonString = false;
61
+ for (const candidate of candidates) {
62
+ if (candidate === undefined || candidate === null)
63
+ continue;
64
+ if (typeof candidate !== "string") {
65
+ sawNonString = true;
66
+ continue;
67
+ }
68
+ if (candidate.trim().length > 0)
69
+ return candidate;
70
+ }
71
+ if (sawNonString) {
72
+ return paramError("script/command must be a non-empty string");
73
+ }
74
+ return paramError("script is required and must be a non-empty string");
75
+ }
76
+ function isToolExecutionResult(value) {
77
+ return typeof value === "object" && value !== null && Array.isArray(value.content);
78
+ }
43
79
  function escapeRegExp(value) {
44
80
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45
81
  }
@@ -151,14 +187,12 @@ function scriptReferencesOriginalRootFromWorktree(script, baseDir) {
151
187
  export async function executeGsdExec(params, deps) {
152
188
  if (!isEnabled(deps.preferences))
153
189
  return contextModeDisabledResult("gsd_exec");
154
- const runtime = params.runtime;
155
- if (runtime !== "bash" && runtime !== "node" && runtime !== "python") {
156
- return paramError(`invalid runtime "${String(runtime)}" — must be bash | node | python`);
157
- }
158
- const script = typeof params.script === "string" ? params.script : "";
159
- if (script.trim().length === 0) {
160
- return paramError("script is required and must be a non-empty string");
161
- }
190
+ const runtime = normalizeRuntime(params.runtime);
191
+ if (isToolExecutionResult(runtime))
192
+ return runtime;
193
+ const script = normalizeScript(params);
194
+ if (isToolExecutionResult(script))
195
+ return script;
162
196
  if (Buffer.byteLength(script, "utf8") > 200_000) {
163
197
  return paramError("script exceeds the 200 KB length limit");
164
198
  }
@@ -6,8 +6,10 @@ import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, should
6
6
  import { getActiveRequirements, insertMilestone, getMilestone, getSliceStatusSummary, getSliceTaskCounts, readTransaction, saveGateResult, } from "../gsd-db.js";
7
7
  import { GATE_REGISTRY } from "../gate-registry.js";
8
8
  import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
9
- import { resolveMilestoneFile, resolveSliceFile } from "../paths.js";
9
+ import { clearPathCache, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
10
+ import { saveFile, clearParseCache } from "../files.js";
10
11
  import { unlinkSync } from "node:fs";
12
+ import { join } from "node:path";
11
13
  import { handleCompleteMilestone } from "./complete-milestone.js";
12
14
  import { handleCompleteTask } from "./complete-task.js";
13
15
  import { handleCompleteSlice } from "./complete-slice.js";
@@ -56,6 +58,25 @@ function registerProjectMilestoneSequence(content) {
56
58
  }
57
59
  return registered;
58
60
  }
61
+ async function mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, content) {
62
+ const contract = resolveGsdPathContract(basePath);
63
+ if (!contract.worktreeGsd)
64
+ return;
65
+ if (contract.worktreeGsd === contract.projectGsd)
66
+ return;
67
+ const fullPath = join(contract.worktreeGsd, relativePath);
68
+ try {
69
+ await saveFile(fullPath, content);
70
+ clearPathCache();
71
+ clearParseCache();
72
+ invalidateStateCache();
73
+ }
74
+ catch (err) {
75
+ logWarning("tool", `gsd_summary_save worktree projection mirror failed: ${err.message}`, {
76
+ path: relativePath,
77
+ });
78
+ }
79
+ }
59
80
  export async function executeSummarySave(params, basePath = process.cwd()) {
60
81
  const dbAvailable = await ensureDbOpen(basePath);
61
82
  if (!dbAvailable) {
@@ -145,6 +166,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
145
166
  slice_id: isRootArtifact ? undefined : params.slice_id,
146
167
  task_id: isRootArtifact ? undefined : params.task_id,
147
168
  }, basePath);
169
+ await mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, contentToSave);
148
170
  let registeredMilestones = [];
149
171
  if (params.artifact_type === "PROJECT") {
150
172
  try {
@@ -238,6 +260,24 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
238
260
  };
239
261
  }
240
262
  }
263
+ function normalizeVerificationEvidence(evidence) {
264
+ return (evidence ?? []).map((entry) => typeof entry === "string"
265
+ ? { command: entry, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 }
266
+ : entry);
267
+ }
268
+ function deriveVerificationSummary(evidence) {
269
+ if (evidence.length === 0)
270
+ return null;
271
+ const rendered = evidence.slice(0, 3).map((entry) => {
272
+ const command = entry.command.trim() || "(unspecified command)";
273
+ const verdict = entry.verdict.trim() || "recorded";
274
+ return `\`${command}\` exited ${entry.exitCode} (${verdict})`;
275
+ });
276
+ const suffix = evidence.length > rendered.length
277
+ ? `; ${evidence.length - rendered.length} more check(s) recorded`
278
+ : "";
279
+ return `Verification evidence recorded: ${rendered.join("; ")}${suffix}.`;
280
+ }
241
281
  export async function executeTaskComplete(params, basePath = process.cwd()) {
242
282
  const dbAvailable = await ensureDbOpen(basePath);
243
283
  if (!dbAvailable) {
@@ -249,7 +289,28 @@ export async function executeTaskComplete(params, basePath = process.cwd()) {
249
289
  }
250
290
  try {
251
291
  const coerced = { ...params };
252
- coerced.verificationEvidence = (params.verificationEvidence ?? []).map((v) => typeof v === "string" ? { command: v, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 } : v);
292
+ const verificationEvidence = normalizeVerificationEvidence(params.verificationEvidence);
293
+ coerced.verificationEvidence = verificationEvidence;
294
+ const verification = typeof params.verification === "string" ? params.verification.trim() : "";
295
+ if (verification.length === 0) {
296
+ const derived = deriveVerificationSummary(verificationEvidence);
297
+ if (derived) {
298
+ coerced.verification = derived;
299
+ }
300
+ else if (params.blockerDiscovered === true) {
301
+ coerced.verification = "Not run: blocker discovered before verification.";
302
+ }
303
+ else {
304
+ return {
305
+ content: [{
306
+ type: "text",
307
+ text: "Error completing task: verification is required unless verificationEvidence is provided or blockerDiscovered is true.",
308
+ }],
309
+ details: { operation: "complete_task", error: "verification_required" },
310
+ isError: true,
311
+ };
312
+ }
313
+ }
253
314
  const result = await handleCompleteTask(coerced, basePath);
254
315
  if ("error" in result) {
255
316
  return {
@@ -105,3 +105,54 @@ export function renderFrame(theme, inner, width, options = {}) {
105
105
  lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
106
106
  return lines.map((line) => safeLine(line, width, ""));
107
107
  }
108
+ function renderTitledTopBorder(theme, title, width, border) {
109
+ const trimmedTitle = title.trim();
110
+ if (!trimmedTitle || width < 10) {
111
+ return border("╭" + "─".repeat(width - 2) + "╮");
112
+ }
113
+ const maxTitleWidth = Math.max(0, width - 7);
114
+ const safeTitle = safeLine(trimmedTitle, maxTitleWidth);
115
+ const fill = Math.max(0, width - visibleWidth(safeTitle) - 5);
116
+ return border("╭─ ") + theme.bold(theme.fg("accent", safeTitle)) + border(" " + "─".repeat(fill) + "╮");
117
+ }
118
+ export function renderDialogFrame(theme, title, inner, width, options = {}) {
119
+ if (width < 4)
120
+ return inner.map((line) => safeLine(line, width));
121
+ const borderColor = options.borderColor ?? "borderAccent";
122
+ const paddingX = Math.max(0, options.paddingX ?? 1);
123
+ const contentWidth = Math.max(0, width - 2 - paddingX * 2);
124
+ const border = (text) => theme.fg(borderColor, text);
125
+ const pad = " ".repeat(paddingX);
126
+ const lines = [renderTitledTopBorder(theme, title, width, border)];
127
+ const scroll = options.scroll;
128
+ const bodyRows = inner.length;
129
+ const trackOffset = Math.max(0, Math.min(scroll?.trackOffset ?? 0, bodyRows));
130
+ const trackRows = Math.max(0, Math.min(scroll?.trackRows ?? bodyRows, bodyRows - trackOffset));
131
+ const scrollable = !!scroll && scroll.totalRows > scroll.visibleRows && trackRows > 0;
132
+ const thumbLen = scrollable
133
+ ? Math.max(1, Math.round((scroll.visibleRows / scroll.totalRows) * trackRows))
134
+ : 0;
135
+ const maxThumbStart = Math.max(0, trackRows - thumbLen);
136
+ const maxScrollOffset = scrollable ? Math.max(1, scroll.totalRows - scroll.visibleRows) : 1;
137
+ const thumbStart = scrollable
138
+ ? trackOffset + Math.min(maxThumbStart, Math.round((scroll.offset / maxScrollOffset) * maxThumbStart))
139
+ : -1;
140
+ for (let i = 0; i < inner.length; i++) {
141
+ const line = inner[i] ?? "";
142
+ const rightBorder = scrollable && i >= thumbStart && i < thumbStart + thumbLen ? "┃" : "│";
143
+ lines.push(border("│") + pad + padRightVisible(line, contentWidth) + pad + border(rightBorder));
144
+ }
145
+ const footer = Array.isArray(options.footer)
146
+ ? options.footer
147
+ : options.footer
148
+ ? [options.footer]
149
+ : [];
150
+ if (footer.length > 0) {
151
+ lines.push(border("├" + "─".repeat(width - 2) + "┤"));
152
+ for (const line of footer) {
153
+ lines.push(border("│") + pad + padRightVisible(line, contentWidth) + pad + border("│"));
154
+ }
155
+ }
156
+ lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
157
+ return lines.map((line) => safeLine(line, width, ""));
158
+ }