gsd-pi 2.77.0-dev.eaa4973bc → 2.78.0-dev.aeeb2ca00

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 (545) hide show
  1. package/README.md +53 -17
  2. package/dist/claude-cli-check.js +46 -10
  3. package/dist/headless.js +49 -4
  4. package/dist/resource-loader.d.ts +40 -0
  5. package/dist/resource-loader.js +32 -13
  6. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  7. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  8. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  9. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  10. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  11. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  12. package/dist/resources/extensions/claude-code-cli/readiness.js +72 -16
  13. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +481 -17
  14. package/dist/resources/extensions/github-sync/templates.js +103 -0
  15. package/dist/resources/extensions/google-search/index.js +3 -2
  16. package/dist/resources/extensions/gsd/auto/loop.js +124 -2
  17. package/dist/resources/extensions/gsd/auto/phases.js +57 -39
  18. package/dist/resources/extensions/gsd/auto/session.js +6 -2
  19. package/dist/resources/extensions/gsd/auto-dispatch.js +142 -29
  20. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  21. package/dist/resources/extensions/gsd/auto-post-unit.js +150 -64
  22. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  23. package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
  24. package/dist/resources/extensions/gsd/auto-start.js +107 -29
  25. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  26. package/dist/resources/extensions/gsd/auto-worktree.js +122 -26
  27. package/dist/resources/extensions/gsd/auto.js +76 -21
  28. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  30. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +3 -6
  31. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  32. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  33. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  34. package/dist/resources/extensions/gsd/component-types.js +69 -0
  35. package/dist/resources/extensions/gsd/context-store.js +23 -7
  36. package/dist/resources/extensions/gsd/detection.js +49 -1
  37. package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
  38. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  39. package/dist/resources/extensions/gsd/forensics.js +106 -0
  40. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  41. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  42. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  43. package/dist/resources/extensions/gsd/git-service.js +126 -2
  44. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  45. package/dist/resources/extensions/gsd/guided-flow.js +39 -13
  46. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  47. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  48. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  49. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  50. package/dist/resources/extensions/gsd/model-router.js +6 -0
  51. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  52. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  53. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  54. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  55. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  56. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  57. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  58. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  59. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  60. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  61. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  62. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  63. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  64. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  65. package/dist/resources/extensions/gsd/state.js +69 -58
  66. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  67. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  68. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  69. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  70. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  71. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  72. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  73. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  74. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  75. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  76. package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
  77. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  78. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  79. package/dist/resources/extensions/gsd/worktree-manager.js +85 -8
  80. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  81. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  82. package/dist/resources/extensions/mcp-client/index.js +3 -1
  83. package/dist/resources/extensions/ollama/index.js +5 -1
  84. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  85. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  86. package/dist/web/standalone/.next/BUILD_ID +1 -1
  87. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  88. package/dist/web/standalone/.next/build-manifest.json +2 -2
  89. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/index.html +1 -1
  108. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  114. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  115. package/dist/web/standalone/.next/server/chunks/1926.js +1 -1
  116. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  117. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  119. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  120. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  121. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  122. package/package.json +2 -3
  123. package/packages/daemon/package.json +2 -2
  124. package/packages/daemon/src/logger.ts +4 -3
  125. package/packages/mcp-server/dist/server.d.ts +24 -0
  126. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  127. package/packages/mcp-server/dist/server.js +88 -87
  128. package/packages/mcp-server/dist/server.js.map +1 -1
  129. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  130. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  131. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  132. package/packages/mcp-server/package.json +2 -2
  133. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  134. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  135. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  136. package/packages/mcp-server/src/server.ts +131 -105
  137. package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
  138. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  139. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  140. package/packages/native/package.json +2 -2
  141. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  142. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  143. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  144. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  145. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  146. package/packages/pi-agent-core/package.json +1 -1
  147. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  148. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  149. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  150. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  151. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  152. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  153. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  154. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  155. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  156. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  158. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  159. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  160. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  161. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  162. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  163. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  164. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  165. package/packages/pi-ai/dist/models.test.js +36 -11
  166. package/packages/pi-ai/dist/models.test.js.map +1 -1
  167. package/packages/pi-ai/package.json +1 -1
  168. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  169. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  170. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  171. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  172. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  173. package/packages/pi-ai/src/models.test.ts +48 -11
  174. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  175. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  176. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  178. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  180. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  181. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  182. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  184. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  186. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  188. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  190. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  193. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  195. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  197. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  198. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  199. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  201. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  202. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  203. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  204. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  205. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  206. package/packages/pi-coding-agent/dist/index.js +1 -0
  207. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  215. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  217. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  218. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
  219. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  224. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  226. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  227. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  228. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  229. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  230. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  231. package/packages/pi-coding-agent/package.json +1 -1
  232. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  233. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  234. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  235. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  236. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  237. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  238. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  239. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  240. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  241. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  242. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  243. package/packages/pi-coding-agent/src/index.ts +1 -0
  244. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  245. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  246. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  247. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
  248. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  249. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  250. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  251. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  252. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  253. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  254. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  255. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  256. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  257. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +37 -11
  258. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  259. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  260. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  261. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  262. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  263. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  264. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  265. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  266. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  267. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  268. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  269. package/packages/pi-tui/dist/components/editor.js +19 -0
  270. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  271. package/packages/pi-tui/dist/components/image.test.js +6 -5
  272. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  273. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  274. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  275. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  276. package/packages/pi-tui/package.json +1 -1
  277. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  278. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  279. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +42 -11
  280. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  281. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  282. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  283. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  284. package/packages/pi-tui/src/components/editor.ts +22 -0
  285. package/packages/pi-tui/src/components/image.test.ts +10 -5
  286. package/packages/pi-tui/src/editor-component.ts +3 -0
  287. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  288. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  289. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  290. package/packages/rpc-client/package.json +1 -1
  291. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  292. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  293. package/pkg/package.json +1 -1
  294. package/scripts/install.js +15 -1
  295. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  296. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  297. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  298. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  299. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  300. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  301. package/src/resources/extensions/claude-code-cli/readiness.ts +75 -16
  302. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +518 -19
  303. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +919 -75
  304. package/src/resources/extensions/github-sync/templates.ts +151 -0
  305. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  306. package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
  307. package/src/resources/extensions/google-search/index.ts +3 -2
  308. package/src/resources/extensions/gsd/auto/loop.ts +142 -2
  309. package/src/resources/extensions/gsd/auto/phases.ts +62 -38
  310. package/src/resources/extensions/gsd/auto/session.ts +7 -2
  311. package/src/resources/extensions/gsd/auto-dispatch.ts +156 -29
  312. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  313. package/src/resources/extensions/gsd/auto-post-unit.ts +163 -73
  314. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  315. package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
  316. package/src/resources/extensions/gsd/auto-start.ts +127 -9
  317. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  318. package/src/resources/extensions/gsd/auto-worktree.ts +130 -26
  319. package/src/resources/extensions/gsd/auto.ts +90 -23
  320. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
  321. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  322. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +3 -7
  323. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  324. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  325. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  326. package/src/resources/extensions/gsd/component-types.ts +362 -0
  327. package/src/resources/extensions/gsd/context-store.ts +25 -8
  328. package/src/resources/extensions/gsd/detection.ts +58 -1
  329. package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
  330. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  331. package/src/resources/extensions/gsd/forensics.ts +118 -1
  332. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  333. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  334. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  335. package/src/resources/extensions/gsd/git-service.ts +149 -2
  336. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  337. package/src/resources/extensions/gsd/guided-flow.ts +57 -14
  338. package/src/resources/extensions/gsd/journal.ts +11 -1
  339. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  340. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  341. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  342. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  343. package/src/resources/extensions/gsd/model-router.ts +6 -0
  344. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  345. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  346. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  347. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  348. package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  349. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  350. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  351. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  352. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  353. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  354. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  355. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  356. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  357. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  358. package/src/resources/extensions/gsd/state.ts +76 -66
  359. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  360. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  361. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  362. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  363. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  364. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +133 -292
  365. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  366. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  367. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  368. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
  369. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  370. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  371. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
  372. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  373. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  374. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  375. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  376. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  377. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  378. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  379. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  380. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  381. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  382. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  383. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  384. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  385. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  386. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  387. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  388. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  389. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  390. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
  391. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  392. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  393. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  394. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  395. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
  396. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  397. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  398. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
  399. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  400. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  401. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  402. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  403. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  404. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  405. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  406. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  407. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  408. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  409. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
  410. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  411. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  412. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  413. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  414. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  415. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  416. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  417. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  418. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  419. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  420. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  421. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
  422. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  423. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  424. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  425. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  426. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  427. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  428. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  429. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  430. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  431. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  432. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  433. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  434. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  435. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  436. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  437. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  438. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  439. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +23 -24
  440. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +32 -0
  441. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  442. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  443. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  444. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  445. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  446. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  447. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  448. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  449. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  450. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  451. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  452. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  453. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  454. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  455. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  456. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  457. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  458. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  459. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  460. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  461. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  462. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
  463. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  464. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  465. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  466. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  467. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  468. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  469. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  470. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  471. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  472. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  473. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  474. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  475. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  476. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  477. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  478. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  479. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  480. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  481. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  482. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  483. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
  484. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  485. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
  486. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  487. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  488. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  489. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  490. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  491. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  492. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  493. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  494. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  495. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  496. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  497. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  498. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  499. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  500. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  501. package/src/resources/extensions/gsd/types.ts +3 -3
  502. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  503. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  504. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  505. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  506. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  507. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  508. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  509. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  510. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  511. package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
  512. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  513. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  514. package/src/resources/extensions/gsd/worktree-manager.ts +108 -7
  515. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  516. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  517. package/src/resources/extensions/mcp-client/index.ts +3 -1
  518. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  519. package/src/resources/extensions/ollama/index.ts +5 -1
  520. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  521. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  522. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  523. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  524. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  525. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  526. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  527. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  528. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  529. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  530. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  531. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  532. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  533. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  534. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  535. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  536. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  537. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  538. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  539. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  540. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  541. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  542. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  543. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  544. /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_buildManifest.js +0 -0
  545. /package/dist/web/standalone/.next/static/{5wbu35_C2_MQ3Jj1lEVDx → cAJH99yNS1UPbeSEiNRrV}/_ssgManifest.js +0 -0
@@ -7,12 +7,13 @@ import assert from "node:assert/strict";
7
7
  import { Image } from "./image.js";
8
8
  describe("Image component (#3455)", () => {
9
9
  const theme = { fallbackColor: (s) => s };
10
- test("getDimensions returns undefined before resolution", () => {
11
- // Pass explicit dimensions to avoid async parsing
10
+ test("getDimensions returns undefined when constructed without explicit dims", () => {
11
+ // Previously this test was titled "returns undefined before resolution"
12
+ // but only asserted `typeof getDimensions === 'function'`. The title
13
+ // and the assertion had nothing to do with each other (#4794).
14
+ // Now actually assert the undefined return.
12
15
  const img = new Image("base64data", "image/png", theme, {});
13
- // Without explicit dims, getDimensions should be undefined until async resolve
14
- // But we can't easily test async here, so verify the method exists
15
- assert.equal(typeof img.getDimensions, "function");
16
+ assert.equal(img.getDimensions(), undefined, "without pre-resolved dims, getDimensions must return undefined until async resolve");
16
17
  });
17
18
  test("getDimensions returns dimensions when provided at construction", () => {
18
19
  const dims = { widthPx: 100, heightPx: 200 };
@@ -1 +1 @@
1
- {"version":3,"file":"image.test.js","sourceRoot":"","sources":["../../src/components/image.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACxC,MAAM,KAAK,GAAG,EAAE,aAAa,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;IAElD,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC9D,kDAAkD;QAClD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5D,+EAA+E;QAC/E,mEAAmE;QACnE,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;QAC3E,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC;QACnC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,mCAAmC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;QACjF,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAClE,GAAG,CAAC,uBAAuB,CAAC,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,6DAA6D;QAC7D,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,sDAAsD,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Regression test for #3455: Image component must not trigger infinite\n * re-render loop when dimensions resolve in cmux sessions.\n */\n\nimport { describe, test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { Image } from \"./image.js\";\n\ndescribe(\"Image component (#3455)\", () => {\n\tconst theme = { fallbackColor: (s: string) => s };\n\n\ttest(\"getDimensions returns undefined before resolution\", () => {\n\t\t// Pass explicit dimensions to avoid async parsing\n\t\tconst img = new Image(\"base64data\", \"image/png\", theme, {});\n\t\t// Without explicit dims, getDimensions should be undefined until async resolve\n\t\t// But we can't easily test async here, so verify the method exists\n\t\tassert.equal(typeof img.getDimensions, \"function\");\n\t});\n\n\ttest(\"getDimensions returns dimensions when provided at construction\", () => {\n\t\tconst dims = { widthPx: 100, heightPx: 200 };\n\t\tconst img = new Image(\"base64data\", \"image/png\", theme, {}, dims);\n\t\tconst result = img.getDimensions();\n\t\tassert.deepEqual(result, dims, \"Should return provided dimensions\");\n\t});\n\n\ttest(\"onDimensionsResolved callback is not called when dimensions provided\", () => {\n\t\tlet callCount = 0;\n\t\tconst dims = { widthPx: 100, heightPx: 200 };\n\t\tconst img = new Image(\"base64data\", \"image/png\", theme, {}, dims);\n\t\timg.setOnDimensionsResolved(() => { callCount++; });\n\t\t// With pre-resolved dims, the async path is skipped entirely\n\t\tassert.equal(callCount, 0, \"Callback should not fire for pre-resolved dimensions\");\n\t});\n});\n"]}
1
+ {"version":3,"file":"image.test.js","sourceRoot":"","sources":["../../src/components/image.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACxC,MAAM,KAAK,GAAG,EAAE,aAAa,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;IAElD,IAAI,CAAC,wEAAwE,EAAE,GAAG,EAAE;QACnF,wEAAwE;QACxE,qEAAqE;QACrE,+DAA+D;QAC/D,4CAA4C;QAC5C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CACX,GAAG,CAAC,aAAa,EAAE,EACnB,SAAS,EACT,oFAAoF,CACpF,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;QAC3E,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC;QACnC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,mCAAmC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;QACjF,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAClE,GAAG,CAAC,uBAAuB,CAAC,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,6DAA6D;QAC7D,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,sDAAsD,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Regression test for #3455: Image component must not trigger infinite\n * re-render loop when dimensions resolve in cmux sessions.\n */\n\nimport { describe, test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { Image } from \"./image.js\";\n\ndescribe(\"Image component (#3455)\", () => {\n\tconst theme = { fallbackColor: (s: string) => s };\n\n\ttest(\"getDimensions returns undefined when constructed without explicit dims\", () => {\n\t\t// Previously this test was titled \"returns undefined before resolution\"\n\t\t// but only asserted `typeof getDimensions === 'function'`. The title\n\t\t// and the assertion had nothing to do with each other (#4794).\n\t\t// Now actually assert the undefined return.\n\t\tconst img = new Image(\"base64data\", \"image/png\", theme, {});\n\t\tassert.equal(\n\t\t\timg.getDimensions(),\n\t\t\tundefined,\n\t\t\t\"without pre-resolved dims, getDimensions must return undefined until async resolve\",\n\t\t);\n\t});\n\n\ttest(\"getDimensions returns dimensions when provided at construction\", () => {\n\t\tconst dims = { widthPx: 100, heightPx: 200 };\n\t\tconst img = new Image(\"base64data\", \"image/png\", theme, {}, dims);\n\t\tconst result = img.getDimensions();\n\t\tassert.deepEqual(result, dims, \"Should return provided dimensions\");\n\t});\n\n\ttest(\"onDimensionsResolved callback is not called when dimensions provided\", () => {\n\t\tlet callCount = 0;\n\t\tconst dims = { widthPx: 100, heightPx: 200 };\n\t\tconst img = new Image(\"base64data\", \"image/png\", theme, {}, dims);\n\t\timg.setOnDimensionsResolved(() => { callCount++; });\n\t\t// With pre-resolved dims, the async path is skipped entirely\n\t\tassert.equal(callCount, 0, \"Callback should not fire for pre-resolved dimensions\");\n\t});\n});\n"]}
@@ -18,6 +18,8 @@ export interface EditorComponent extends Component {
18
18
  onSubmit?: (text: string) => void;
19
19
  /** Called when text changes */
20
20
  onChange?: (text: string) => void;
21
+ /** Called when an image file path is pasted via terminal emulator */
22
+ onPasteImagePath?: (filePath: string) => void;
21
23
  /** Add text to history for up/down navigation */
22
24
  addToHistory?(text: string): void;
23
25
  /** Insert text at current cursor position */
@@ -1 +1 @@
1
- {"version":3,"file":"editor-component.d.ts","sourceRoot":"","sources":["../src/editor-component.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAKjD,mCAAmC;IACnC,OAAO,IAAI,MAAM,CAAC;IAElB,2BAA2B;IAC3B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B,qEAAqE;IACrE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAMhC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAMlC,iDAAiD;IACjD,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAMlC,6CAA6C;IAC7C,kBAAkB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAExC;;;OAGG;IACH,eAAe,CAAC,IAAI,MAAM,CAAC;IAM3B,oCAAoC;IACpC,uBAAuB,CAAC,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAM/D,4BAA4B;IAC5B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtC,6BAA6B;IAC7B,WAAW,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpC,qDAAqD;IACrD,yBAAyB,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACrD"}
1
+ {"version":3,"file":"editor-component.d.ts","sourceRoot":"","sources":["../src/editor-component.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAKjD,mCAAmC;IACnC,OAAO,IAAI,MAAM,CAAC;IAElB,2BAA2B;IAC3B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B,qEAAqE;IACrE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAMhC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAM9C,iDAAiD;IACjD,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAMlC,6CAA6C;IAC7C,kBAAkB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAExC;;;OAGG;IACH,eAAe,CAAC,IAAI,MAAM,CAAC;IAM3B,oCAAoC;IACpC,uBAAuB,CAAC,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAM/D,4BAA4B;IAC5B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtC,6BAA6B;IAC7B,WAAW,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpC,qDAAqD;IACrD,yBAAyB,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"editor-component.js","sourceRoot":"","sources":["../src/editor-component.ts"],"names":[],"mappings":"","sourcesContent":["import type { AutocompleteProvider } from \"./autocomplete.js\";\nimport type { Component } from \"./tui.js\";\n\n/**\n * Interface for custom editor components.\n *\n * This allows extensions to provide their own editor implementation\n * (e.g., vim mode, emacs mode, custom keybindings) while maintaining\n * compatibility with the core application.\n */\nexport interface EditorComponent extends Component {\n\t// =========================================================================\n\t// Core text access (required)\n\t// =========================================================================\n\n\t/** Get the current text content */\n\tgetText(): string;\n\n\t/** Set the text content */\n\tsetText(text: string): void;\n\n\t/** Handle raw terminal input (key presses, paste sequences, etc.) */\n\thandleInput(data: string): void;\n\n\t// =========================================================================\n\t// Callbacks (required)\n\t// =========================================================================\n\n\t/** Called when user submits (e.g., Enter key) */\n\tonSubmit?: (text: string) => void;\n\n\t/** Called when text changes */\n\tonChange?: (text: string) => void;\n\n\t// =========================================================================\n\t// History support (optional)\n\t// =========================================================================\n\n\t/** Add text to history for up/down navigation */\n\taddToHistory?(text: string): void;\n\n\t// =========================================================================\n\t// Advanced text manipulation (optional)\n\t// =========================================================================\n\n\t/** Insert text at current cursor position */\n\tinsertTextAtCursor?(text: string): void;\n\n\t/**\n\t * Get text with any markers expanded (e.g., paste markers).\n\t * Falls back to getText() if not implemented.\n\t */\n\tgetExpandedText?(): string;\n\n\t// =========================================================================\n\t// Autocomplete support (optional)\n\t// =========================================================================\n\n\t/** Set the autocomplete provider */\n\tsetAutocompleteProvider?(provider: AutocompleteProvider): void;\n\n\t// =========================================================================\n\t// Appearance (optional)\n\t// =========================================================================\n\n\t/** Border color function */\n\tborderColor?: (str: string) => string;\n\n\t/** Set horizontal padding */\n\tsetPaddingX?(padding: number): void;\n\n\t/** Set max visible items in autocomplete dropdown */\n\tsetAutocompleteMaxVisible?(maxVisible: number): void;\n}\n"]}
1
+ {"version":3,"file":"editor-component.js","sourceRoot":"","sources":["../src/editor-component.ts"],"names":[],"mappings":"","sourcesContent":["import type { AutocompleteProvider } from \"./autocomplete.js\";\nimport type { Component } from \"./tui.js\";\n\n/**\n * Interface for custom editor components.\n *\n * This allows extensions to provide their own editor implementation\n * (e.g., vim mode, emacs mode, custom keybindings) while maintaining\n * compatibility with the core application.\n */\nexport interface EditorComponent extends Component {\n\t// =========================================================================\n\t// Core text access (required)\n\t// =========================================================================\n\n\t/** Get the current text content */\n\tgetText(): string;\n\n\t/** Set the text content */\n\tsetText(text: string): void;\n\n\t/** Handle raw terminal input (key presses, paste sequences, etc.) */\n\thandleInput(data: string): void;\n\n\t// =========================================================================\n\t// Callbacks (required)\n\t// =========================================================================\n\n\t/** Called when user submits (e.g., Enter key) */\n\tonSubmit?: (text: string) => void;\n\n\t/** Called when text changes */\n\tonChange?: (text: string) => void;\n\n\t/** Called when an image file path is pasted via terminal emulator */\n\tonPasteImagePath?: (filePath: string) => void;\n\n\t// =========================================================================\n\t// History support (optional)\n\t// =========================================================================\n\n\t/** Add text to history for up/down navigation */\n\taddToHistory?(text: string): void;\n\n\t// =========================================================================\n\t// Advanced text manipulation (optional)\n\t// =========================================================================\n\n\t/** Insert text at current cursor position */\n\tinsertTextAtCursor?(text: string): void;\n\n\t/**\n\t * Get text with any markers expanded (e.g., paste markers).\n\t * Falls back to getText() if not implemented.\n\t */\n\tgetExpandedText?(): string;\n\n\t// =========================================================================\n\t// Autocomplete support (optional)\n\t// =========================================================================\n\n\t/** Set the autocomplete provider */\n\tsetAutocompleteProvider?(provider: AutocompleteProvider): void;\n\n\t// =========================================================================\n\t// Appearance (optional)\n\t// =========================================================================\n\n\t/** Border color function */\n\tborderColor?: (str: string) => string;\n\n\t/** Set horizontal padding */\n\tsetPaddingX?(padding: number): void;\n\n\t/** Set max visible items in autocomplete dropdown */\n\tsetAutocompleteMaxVisible?(maxVisible: number): void;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gsd/pi-tui",
3
- "version": "2.77.0",
3
+ "version": "2.78.0",
4
4
  "description": "Terminal User Interface library (vendored from pi-mono)",
5
5
  "type": "module",
6
6
  "gsd": {
@@ -127,20 +127,36 @@ describe("CombinedAutocompleteProvider — argument completions", () => {
127
127
  });
128
128
 
129
129
  describe("CombinedAutocompleteProvider — @ file prefix extraction", () => {
130
- it("detects @ at start of line", () => {
130
+ it("detects @ at start of line and returns a valid suggestion shape", () => {
131
131
  const provider = makeProvider();
132
- // @ triggers fuzzy file search — we can't test the actual file results
133
- // but we can test that getSuggestions returns null (no files in /tmp matching)
134
- // rather than crashing
135
132
  const result = provider.getSuggestions(["@nonexistent_xyz"], 0, 16);
136
- // May return null or empty the key thing is it doesn't crash
137
- assert.ok(result === null || result.items.length >= 0);
133
+ // Either null (nothing matched) or a well-formed {items: Array, prefix: string}
134
+ // shape. Previous version's `result.items.length >= 0` was a tautology —
135
+ // array length is always ≥ 0; the whole expression could never fail.
136
+ if (result !== null) {
137
+ assert.ok(Array.isArray(result.items), "result.items must be an array");
138
+ // The @-prefix extraction strips the leading @ — prefix should be
139
+ // the raw text without the trigger character.
140
+ assert.equal(typeof result.prefix, "string", "prefix must be a string");
141
+ assert.ok(
142
+ !result.prefix.startsWith("@"),
143
+ `prefix must have the @ trigger stripped, got: ${JSON.stringify(result.prefix)}`,
144
+ );
145
+ }
138
146
  });
139
147
 
140
- it("detects @ after space", () => {
148
+ it("detects @ after space and returns a valid suggestion shape", () => {
141
149
  const provider = makeProvider();
142
150
  const result = provider.getSuggestions(["check @nonexistent_xyz"], 0, 22);
143
- assert.ok(result === null || result.items.length >= 0);
151
+ if (result !== null) {
152
+ assert.ok(Array.isArray(result.items), "result.items must be an array");
153
+ assert.equal(typeof result.prefix, "string", "prefix must be a string");
154
+ // The prefix must NOT include the word "check" that came before the @.
155
+ assert.ok(
156
+ !result.prefix.includes("check"),
157
+ `prefix must not include text before the @, got: ${JSON.stringify(result.prefix)}`,
158
+ );
159
+ }
144
160
  });
145
161
 
146
162
  it("returns null for bare @ with no query to avoid full tree walk (#1824)", () => {
@@ -1,4 +1,16 @@
1
1
  // pi-tui — Overlay Layout Tests (backdrop dimming)
2
+ //
3
+ // These tests previously coupled to literal ANSI escape bytes
4
+ // (`\x1b[2m`, `\x1b[38;5;240m`) and would break if the palette index or
5
+ // SGR spelling changed despite identical rendered output — Goodhart's law:
6
+ // the test measures escape codes, not dimming.
7
+ //
8
+ // We now parse the SGR escape codes into a semantic style state and assert
9
+ // on the visible contract: the covered-but-outside-overlay region is dim,
10
+ // has a non-default foreground (so the eye can distinguish foreground from
11
+ // background), and does not paint the terminal background (so user themes
12
+ // are preserved). The overlay content itself is reachable via plain-text
13
+ // lookup after stripping ANSI.
2
14
 
3
15
  import { describe, it } from "node:test";
4
16
  import assert from "node:assert/strict";
@@ -16,8 +28,107 @@ function makeEntry(
16
28
  };
17
29
  }
18
30
 
31
+ /**
32
+ * Parse a line's ANSI SGR state immediately before the first occurrence of a
33
+ * target substring in the rendered (ANSI-stripped) text. Walks `\x1b[...m`
34
+ * sequences left-to-right, maintaining a running state so we can ask what
35
+ * the terminal is doing when it reaches the target glyphs.
36
+ *
37
+ * Semantic fields:
38
+ * - dim: SGR 2 active and not reset by SGR 22 / 0
39
+ * - fg: foreground: "default" | "set" | the raw numeric parameters
40
+ * - bg: background: "default" | "set"
41
+ */
42
+ type SgrState = { dim: boolean; fg: "default" | "set"; bg: "default" | "set" };
43
+
44
+ function sgrStateAtGlyph(line: string, targetGlyph: string): SgrState {
45
+ const state: SgrState = { dim: false, fg: "default", bg: "default" };
46
+ // Walk codes and visible chars, tracking visible-glyph position.
47
+ let visibleSeen = "";
48
+ let i = 0;
49
+ while (i < line.length) {
50
+ if (line[i] === "\x1b" && line[i + 1] === "[") {
51
+ // Read until final byte in 0x40-0x7E
52
+ let j = i + 2;
53
+ while (j < line.length) {
54
+ const c = line.charCodeAt(j);
55
+ if (c >= 0x40 && c <= 0x7e) break;
56
+ j++;
57
+ }
58
+ const final = line[j];
59
+ if (final === "m") {
60
+ const paramString = line.slice(i + 2, j);
61
+ applySgr(state, paramString);
62
+ }
63
+ i = j + 1;
64
+ continue;
65
+ }
66
+ // Skip other escape sequences (OSC hyperlinks etc) conservatively:
67
+ // if we ever hit a non-SGR escape, just step past the ESC.
68
+ if (line[i] === "\x1b") {
69
+ i++;
70
+ continue;
71
+ }
72
+ visibleSeen += line[i];
73
+ if (visibleSeen.endsWith(targetGlyph)) {
74
+ // We've just consumed the last char of targetGlyph — return the
75
+ // state that was in effect for the whole match.
76
+ return state;
77
+ }
78
+ i++;
79
+ }
80
+ throw new Error(`Target glyph ${JSON.stringify(targetGlyph)} not found in line`);
81
+ }
82
+
83
+ function applySgr(state: SgrState, paramString: string): void {
84
+ // Empty params == reset
85
+ const parts = paramString === "" ? ["0"] : paramString.split(";");
86
+ let k = 0;
87
+ while (k < parts.length) {
88
+ const n = parts[k] === "" ? 0 : Number(parts[k]);
89
+ if (n === 0) {
90
+ state.dim = false;
91
+ state.fg = "default";
92
+ state.bg = "default";
93
+ } else if (n === 2) {
94
+ state.dim = true;
95
+ } else if (n === 22) {
96
+ state.dim = false;
97
+ } else if (n === 39) {
98
+ state.fg = "default";
99
+ } else if (n === 49) {
100
+ state.bg = "default";
101
+ } else if ((n >= 30 && n <= 37) || (n >= 90 && n <= 97)) {
102
+ state.fg = "set";
103
+ } else if ((n >= 40 && n <= 47) || (n >= 100 && n <= 107)) {
104
+ state.bg = "set";
105
+ } else if (n === 38) {
106
+ state.fg = "set";
107
+ // Skip colour-model parameters: 38;5;N or 38;2;R;G;B
108
+ if (parts[k + 1] === "5") {
109
+ k += 2;
110
+ } else if (parts[k + 1] === "2") {
111
+ k += 4;
112
+ }
113
+ } else if (n === 48) {
114
+ state.bg = "set";
115
+ if (parts[k + 1] === "5") {
116
+ k += 2;
117
+ } else if (parts[k + 1] === "2") {
118
+ k += 4;
119
+ }
120
+ }
121
+ k++;
122
+ }
123
+ }
124
+
125
+ function stripAnsi(line: string): string {
126
+ // Remove CSI sequences. Good enough for these tests.
127
+ return line.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
128
+ }
129
+
19
130
  describe("compositeOverlays — backdrop", () => {
20
- it("dims base lines when backdrop is true", () => {
131
+ it("dims base lines outside the overlay when backdrop is true", () => {
21
132
  const base = ["hello world", "second line"];
22
133
  const overlay = makeEntry(["OVERLAY"], {
23
134
  width: 7,
@@ -27,14 +138,17 @@ describe("compositeOverlays — backdrop", () => {
27
138
 
28
139
  const result = compositeOverlays(base, [overlay], 20, 20, 2);
29
140
 
30
- // All base lines in viewport should contain dim escape (\x1b[2m)
31
- // The overlay line itself is composited on top, but underlying lines get dimmed
32
- const dimmedLine = result.find((l) => l.includes("second line"));
33
- assert.ok(dimmedLine, "should have a line containing 'second line'");
34
- assert.ok(dimmedLine.includes("\x1b[2m"), "base line should be dimmed");
141
+ // "second line" is below the overlay (which is anchored top-left with
142
+ // a single visible row), so every glyph of that text should be
143
+ // rendered with the dim attribute active.
144
+ const line = result.find((l) => stripAnsi(l).includes("second line"));
145
+ assert.ok(line, "should have a line containing 'second line'");
146
+
147
+ const state = sgrStateAtGlyph(line, "second line");
148
+ assert.equal(state.dim, true, "base line should be dimmed (SGR 2)");
35
149
  });
36
150
 
37
- it("backdrop uses gray foreground for dimming", () => {
151
+ it("backdrop applies a non-default foreground colour and leaves background untouched", () => {
38
152
  const base = ["hello world", "second line"];
39
153
  const overlay = makeEntry(["OV"], {
40
154
  width: 2,
@@ -44,11 +158,16 @@ describe("compositeOverlays — backdrop", () => {
44
158
 
45
159
  const result = compositeOverlays(base, [overlay], 20, 20, 2);
46
160
 
47
- // Check a non-overlay line for backdrop codes (dim + gray fg, no bg)
48
- const line = result.find((l) => l.includes("second line"));
161
+ const line = result.find((l) => stripAnsi(l).includes("second line"));
49
162
  assert.ok(line, "should have a line containing 'second line'");
50
- assert.ok(line.includes("\x1b[38;5;240m"), "backdrop should set gray foreground");
51
- assert.ok(!line.includes("\x1b[48;"), "backdrop should not set background color");
163
+
164
+ const state = sgrStateAtGlyph(line, "second line");
165
+ assert.equal(state.fg, "set", "backdrop must set a foreground colour");
166
+ assert.equal(
167
+ state.bg,
168
+ "default",
169
+ "backdrop must not paint a background (preserves user's terminal theme)",
170
+ );
52
171
  });
53
172
 
54
173
  it("does not dim when backdrop is false/absent", () => {
@@ -60,10 +179,11 @@ describe("compositeOverlays — backdrop", () => {
60
179
 
61
180
  const result = compositeOverlays(base, [overlay], 20, 20, 2);
62
181
 
63
- // Lines not covered by overlay should remain undimmed
64
- const secondLine = result.find((l) => l.includes("second line"));
65
- assert.ok(secondLine, "should have a line containing 'second line'");
66
- assert.ok(!secondLine.includes("\x1b[2m"), "base line should not be dimmed");
182
+ const line = result.find((l) => stripAnsi(l).includes("second line"));
183
+ assert.ok(line, "should have a line containing 'second line'");
184
+
185
+ const state = sgrStateAtGlyph(line, "second line");
186
+ assert.equal(state.dim, false, "base line should not be dimmed when no backdrop");
67
187
  });
68
188
 
69
189
  it("overlay content renders on top of dimmed background", () => {
@@ -76,7 +196,10 @@ describe("compositeOverlays — backdrop", () => {
76
196
 
77
197
  const result = compositeOverlays(base, [overlay], 10, 10, 1);
78
198
 
79
- // The first line should contain the overlay text
80
- assert.ok(result[0].includes("XX"), "overlay text should be composited");
199
+ // Find the row that (after stripping styling) contains the overlay
200
+ // text. We don't use positional `result[0]` so the test survives if
201
+ // the row ordering changes.
202
+ const overlayRow = result.find((l) => stripAnsi(l).includes("XX"));
203
+ assert.ok(overlayRow, "overlay text should be composited into some rendered row");
81
204
  });
82
205
  });
@@ -1,29 +1,49 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { setTimeout as delay } from "node:timers/promises";
4
3
 
5
4
  import { StdinBuffer } from "../stdin-buffer.js";
6
5
 
6
+ // These tests use node:test's mock.timers to advance virtual time.
7
+ // They previously relied on wall-clock delays (delay(20), delay(150))
8
+ // racing the OS scheduler. On Windows the default setTimeout resolution is
9
+ // ~15.6ms, so a real-time delay of 20ms sometimes fired only one of two
10
+ // pending timers — hence the flake referenced in issue #4795.
11
+ //
12
+ // By mocking setTimeout/clearTimeout we control exactly when the buffer's
13
+ // sequence timeout and stale timeout fire, eliminating scheduler-induced
14
+ // flake and making the tests deterministic on every platform.
15
+
7
16
  describe("StdinBuffer", () => {
8
- it("flushes a lone Escape keypress", async () => {
17
+ it("flushes a lone Escape keypress after the sequence timeout", (t) => {
18
+ t.mock.timers.enable({ apis: ["setTimeout"] });
9
19
  const buffer = new StdinBuffer({ timeout: 5 });
10
20
  const received: string[] = [];
11
21
  buffer.on("data", (sequence) => received.push(sequence));
12
22
 
13
23
  buffer.process("\x1b");
14
- await delay(20);
24
+
25
+ // Before the timeout fires the lone ESC is still buffered.
26
+ assert.deepEqual(received, []);
27
+ assert.equal(buffer.getBuffer(), "\x1b");
28
+
29
+ // Advance past the sequence timeout.
30
+ t.mock.timers.tick(5);
15
31
 
16
32
  assert.deepEqual(received, ["\x1b"]);
17
33
  assert.equal(buffer.getBuffer(), "");
18
34
  });
19
35
 
20
- it("keeps split CSI focus and mouse sequences buffered until completion", async () => {
36
+ it("keeps split CSI focus and mouse sequences buffered until completion", (t) => {
37
+ t.mock.timers.enable({ apis: ["setTimeout"] });
21
38
  const buffer = new StdinBuffer({ timeout: 5 });
22
39
  const received: string[] = [];
23
40
  buffer.on("data", (sequence) => received.push(sequence));
24
41
 
25
42
  buffer.process("\x1b[");
26
- await delay(20);
43
+ // Even after the normal timeout elapses, an incomplete CSI prefix must
44
+ // remain buffered (not emitted as literal text) so split escape
45
+ // sequences stay intact.
46
+ t.mock.timers.tick(5);
27
47
  assert.deepEqual(received, []);
28
48
  assert.equal(buffer.getBuffer(), "\x1b[");
29
49
 
@@ -32,7 +52,7 @@ describe("StdinBuffer", () => {
32
52
  assert.equal(buffer.getBuffer(), "");
33
53
 
34
54
  buffer.process("\x1b[<35;20;");
35
- await delay(20);
55
+ t.mock.timers.tick(5);
36
56
  assert.deepEqual(received, ["\x1b[I"]);
37
57
  assert.equal(buffer.getBuffer(), "\x1b[<35;20;");
38
58
 
@@ -41,25 +61,36 @@ describe("StdinBuffer", () => {
41
61
  assert.equal(buffer.getBuffer(), "");
42
62
  });
43
63
 
44
- it("flushes a stale incomplete escape prefix after the stale timeout", async () => {
45
- const buffer = new StdinBuffer({ timeout: 5, staleTimeout: 10 });
64
+ it("flushes a stale incomplete escape prefix after the stale timeout", (t) => {
65
+ t.mock.timers.enable({ apis: ["setTimeout"] });
66
+ const buffer = new StdinBuffer({ timeout: 20, staleTimeout: 40 });
46
67
  const received: string[] = [];
47
68
  buffer.on("data", (sequence) => received.push(sequence));
48
69
 
49
70
  buffer.process("\x1b[");
50
- await delay(50);
71
+
72
+ // Sequence timeout: keeps the incomplete prefix buffered and starts
73
+ // the stale timer.
74
+ t.mock.timers.tick(20);
75
+ assert.deepEqual(received, []);
76
+ assert.equal(buffer.getBuffer(), "\x1b[");
77
+
78
+ // Stale timer fires — prefix is emitted as-is.
79
+ t.mock.timers.tick(40);
51
80
 
52
81
  assert.deepEqual(received, ["\x1b["]);
53
82
  assert.equal(buffer.getBuffer(), "");
54
83
  });
55
84
 
56
- it("still allows an incomplete escape prefix to complete before the stale timeout", async () => {
85
+ it("still allows an incomplete escape prefix to complete before the stale timeout", (t) => {
86
+ t.mock.timers.enable({ apis: ["setTimeout"] });
57
87
  const buffer = new StdinBuffer({ timeout: 5, staleTimeout: 30 });
58
88
  const received: string[] = [];
59
89
  buffer.on("data", (sequence) => received.push(sequence));
60
90
 
61
91
  buffer.process("\x1b[");
62
- await delay(10);
92
+ // Advance past the sequence timeout (but not the stale timeout).
93
+ t.mock.timers.tick(10);
63
94
  buffer.process("I");
64
95
 
65
96
  assert.deepEqual(received, ["\x1b[I"]);
@@ -25,43 +25,24 @@ function makeTerminal(): Terminal {
25
25
  };
26
26
  }
27
27
 
28
- describe("TUI clearOnShrink debounce", () => {
29
- it("defers full redraw on first shrink and commits on second", () => {
30
- const tui = new TUI(makeTerminal());
31
- const anyTui = tui as any;
32
-
33
- // Enable clearOnShrink and simulate prior rendering state
34
- anyTui.clearOnShrink = true;
35
- anyTui.maxLinesRendered = 10;
36
- anyTui._shrinkDebounceActive = false;
37
-
38
- // Simulate a shrink: newLines has fewer lines than maxLinesRendered
39
- // First shrink should set debounce flag but NOT reset maxLinesRendered
40
- anyTui._shrinkDebounceActive = false;
41
-
42
- // Verify the flag exists and is initially false
43
- assert.equal(anyTui._shrinkDebounceActive, false);
44
-
45
- // After setting it to true (simulating first shrink detection),
46
- // maxLinesRendered should remain at the old value so the condition
47
- // triggers again on the next render
48
- anyTui._shrinkDebounceActive = true;
49
- assert.equal(anyTui.maxLinesRendered, 10, "maxLinesRendered must not change during deferred shrink");
50
- });
51
-
52
- it("resets debounce flag when content grows back", () => {
53
- const tui = new TUI(makeTerminal());
54
- const anyTui = tui as any;
55
-
56
- anyTui.clearOnShrink = true;
57
- anyTui._shrinkDebounceActive = true;
58
-
59
- // Simulating the else branch: content grew back or no shrink
60
- // The code sets _shrinkDebounceActive = false in the else branch
61
- anyTui._shrinkDebounceActive = false;
62
- assert.equal(anyTui._shrinkDebounceActive, false);
63
- });
64
- });
28
+ // TUI clearOnShrink debounce — tests removed in #4794 (ref #4784).
29
+ //
30
+ // The previous tests mutated private fields (`_shrinkDebounceActive`,
31
+ // `maxLinesRendered`) and then asserted the values they just wrote —
32
+ // pure tautologies that never exercised the real debounce path in
33
+ // `renderNow()` (tui.ts:734-754). A regression that narrowed the
34
+ // condition, reversed the flag flip, or dropped the "keep
35
+ // maxLinesRendered" rule would have passed all of them.
36
+ //
37
+ // A proper test would (a) render a component that produces N lines to
38
+ // establish `maxLinesRendered`, (b) swap in a component that produces
39
+ // N-k lines to trigger the shrink branch, and (c) observe terminal
40
+ // writes to confirm the debounce defers/commits the full redraw on the
41
+ // expected render call.
42
+ //
43
+ // That test setup requires exposing enough of the render path (or
44
+ // extracting the debounce decision into a pure helper) — deferred to a
45
+ // separate refactor PR rather than shipping a tautology. See #4794.
65
46
 
66
47
  describe("TUI", () => {
67
48
  it("does not swallow a bare Escape keypress while waiting for the cell-size response", () => {
@@ -37,11 +37,27 @@ describe("Input", () => {
37
37
  const input = new Input();
38
38
  input.secure = true;
39
39
  input.focused = true;
40
- input.handleInput("secret123");
40
+ const SECRET = "secret123";
41
+ input.handleInput(SECRET);
41
42
 
42
43
  const line = input.render(40)[0] ?? "";
43
- assert.ok(!line.includes("secret123"), "rendered line must not expose raw secret text");
44
- assert.ok(line.includes("*********"), "rendered line should include masked characters");
44
+ // Previous assertion was `line.includes("*********")` a literal
45
+ // 9-star string that silently goes stale if SECRET is renamed to
46
+ // a different length (#4796). Match any run of asterisks and
47
+ // assert its length covers the secret.
48
+ assert.ok(
49
+ !line.includes(SECRET),
50
+ "rendered line must not expose raw secret text",
51
+ );
52
+ const maskMatch = line.match(/\*+/);
53
+ assert.ok(
54
+ maskMatch,
55
+ `rendered line must include masked characters, got: ${JSON.stringify(line)}`,
56
+ );
57
+ assert.ok(
58
+ maskMatch[0].length >= SECRET.length,
59
+ `mask must cover at least the secret length (${SECRET.length}), got ${maskMatch[0].length} asterisks`,
60
+ );
45
61
  });
46
62
 
47
63
  it("maps kitty keypad digits to text instead of inserting private-use glyphs", () => {