gsd-pi 2.77.0-dev.1d17f366c → 2.77.0-dev.2daa994b6

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 (368) hide show
  1. package/dist/headless.js +25 -4
  2. package/dist/resource-loader.d.ts +40 -0
  3. package/dist/resource-loader.js +32 -13
  4. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  5. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  6. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  7. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  8. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  9. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  10. package/dist/resources/extensions/gsd/auto/phases.js +5 -18
  11. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +37 -8
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +79 -0
  14. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  15. package/dist/resources/extensions/gsd/auto-start.js +75 -24
  16. package/dist/resources/extensions/gsd/auto.js +34 -0
  17. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +7 -1
  19. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  20. package/dist/resources/extensions/gsd/component-types.js +69 -0
  21. package/dist/resources/extensions/gsd/context-store.js +23 -7
  22. package/dist/resources/extensions/gsd/detection.js +49 -1
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  24. package/dist/resources/extensions/gsd/forensics.js +106 -0
  25. package/dist/resources/extensions/gsd/gsd-db.js +1 -1
  26. package/dist/resources/extensions/gsd/guided-flow.js +2 -4
  27. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  28. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  29. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  30. package/dist/resources/extensions/gsd/model-router.js +6 -0
  31. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  32. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  33. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +5 -1
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  35. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  36. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  37. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  38. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  39. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  40. package/dist/resources/extensions/gsd/unit-context-manifest.js +334 -0
  41. package/dist/resources/extensions/gsd/worktree-manager.js +51 -0
  42. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  43. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  44. package/dist/resources/extensions/mcp-client/index.js +3 -1
  45. package/dist/resources/extensions/ollama/index.js +5 -1
  46. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  47. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  48. package/dist/web/standalone/.next/BUILD_ID +1 -1
  49. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  50. package/dist/web/standalone/.next/build-manifest.json +2 -2
  51. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.html +1 -1
  69. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  76. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  77. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  79. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  80. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  81. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  82. package/package.json +1 -3
  83. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  84. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  85. package/packages/mcp-server/src/workflow-tools.test.ts +80 -39
  86. package/packages/native/package.json +1 -1
  87. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  88. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  89. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  90. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  91. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  92. package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
  93. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  94. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  95. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  96. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  98. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  100. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  101. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  102. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  103. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  104. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  105. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  106. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  107. package/packages/pi-ai/dist/models.test.js +29 -11
  108. package/packages/pi-ai/dist/models.test.js.map +1 -1
  109. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  110. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  111. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  112. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  113. package/packages/pi-ai/src/models.test.ts +39 -11
  114. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  115. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  116. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  118. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  120. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
  122. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
  126. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
  128. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  130. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  133. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  135. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  137. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  139. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  141. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
  143. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  145. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/index.js +1 -0
  147. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  156. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  157. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  158. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  159. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  160. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  161. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  162. package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
  163. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
  164. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
  165. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  166. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  167. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  168. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  169. package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
  170. package/packages/pi-coding-agent/src/index.ts +1 -0
  171. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  172. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  173. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  174. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  175. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  176. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
  177. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  178. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  179. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  180. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +36 -12
  181. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  182. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  183. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  184. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  185. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  186. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  187. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  188. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  189. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  190. package/packages/pi-tui/dist/components/image.test.js +6 -5
  191. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  192. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
  193. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  194. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +41 -12
  195. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  196. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  197. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  198. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  199. package/packages/pi-tui/src/components/image.test.ts +10 -5
  200. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  201. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  202. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  203. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  204. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  205. package/scripts/install.js +15 -1
  206. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  207. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  208. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  209. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  210. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  211. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  212. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -72
  213. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  214. package/src/resources/extensions/github-sync/tests/templates.test.ts +33 -1
  215. package/src/resources/extensions/gsd/auto/phases.ts +6 -17
  216. package/src/resources/extensions/gsd/auto/session.ts +7 -0
  217. package/src/resources/extensions/gsd/auto-dispatch.ts +40 -8
  218. package/src/resources/extensions/gsd/auto-post-unit.ts +81 -0
  219. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  220. package/src/resources/extensions/gsd/auto-start.ts +97 -4
  221. package/src/resources/extensions/gsd/auto.ts +37 -0
  222. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  223. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -1
  224. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  225. package/src/resources/extensions/gsd/component-types.ts +362 -0
  226. package/src/resources/extensions/gsd/context-store.ts +25 -8
  227. package/src/resources/extensions/gsd/detection.ts +58 -1
  228. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  229. package/src/resources/extensions/gsd/forensics.ts +118 -1
  230. package/src/resources/extensions/gsd/git-service.ts +16 -0
  231. package/src/resources/extensions/gsd/gsd-db.ts +1 -1
  232. package/src/resources/extensions/gsd/guided-flow.ts +2 -4
  233. package/src/resources/extensions/gsd/journal.ts +11 -1
  234. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  235. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  236. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  237. package/src/resources/extensions/gsd/model-router.ts +6 -0
  238. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  239. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  240. package/src/resources/extensions/gsd/prompts/complete-milestone.md +5 -1
  241. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  242. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  243. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  244. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  245. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  246. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +25 -292
  247. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  248. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  249. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
  250. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  251. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  252. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  253. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  254. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  255. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  256. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  257. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  258. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  259. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  260. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  261. package/src/resources/extensions/gsd/tests/complete-task.test.ts +8 -4
  262. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  263. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  264. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  265. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  266. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  267. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  268. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  269. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  270. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  271. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  272. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  273. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  274. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  275. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  276. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1 -1
  277. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  278. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  279. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  280. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +8 -37
  281. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  282. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  283. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  284. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  285. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  287. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  288. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  289. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  290. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  291. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  292. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  293. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  294. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +22 -16
  295. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  296. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  297. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  298. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  299. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  300. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  301. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  302. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  303. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  304. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  305. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  306. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  307. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  308. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  309. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  310. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  311. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  312. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  313. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  314. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  315. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  316. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  317. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  318. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  319. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  320. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +203 -0
  321. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  322. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +1 -0
  323. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  324. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  325. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  326. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  327. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  328. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  329. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  330. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  331. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  332. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  333. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  334. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  335. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  336. package/src/resources/extensions/gsd/unit-context-manifest.ts +492 -0
  337. package/src/resources/extensions/gsd/worktree-manager.ts +53 -0
  338. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  339. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  340. package/src/resources/extensions/mcp-client/index.ts +3 -1
  341. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  342. package/src/resources/extensions/ollama/index.ts +5 -1
  343. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  344. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  345. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  346. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  347. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  348. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  349. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  350. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  351. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  352. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  353. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  354. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  355. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
  356. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  357. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  358. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  359. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  360. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  361. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  362. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  363. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  364. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  365. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  366. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  367. /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → gYYky7yfxW8txb9vU2TrJ}/_buildManifest.js +0 -0
  368. /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → gYYky7yfxW8txb9vU2TrJ}/_ssgManifest.js +0 -0
@@ -627,4 +627,83 @@ Integration tests mock external services.
627
627
 
628
628
  assert.strictEqual(result, '', 'empty content returns empty string');
629
629
  });
630
+
631
+ // ── Regression: issue #4719 — single-H2 with many H3 entries ──────────────
632
+ // A KNOWLEDGE.md structured as one top-level H2 with many H3 entries must
633
+ // filter at H3 granularity; otherwise one keyword match against the H2
634
+ // header or first paragraph returns the entire file.
635
+ test("single H2 with many H3 entries filters at H3 level (issue #4719)", async () => {
636
+ const singleH2Knowledge = `# Project Knowledge
637
+
638
+ ## Patterns
639
+
640
+ ### Database: prepared statements
641
+ Always use prepared statements with SQLite.
642
+
643
+ ### API: versioned paths
644
+ Use /v1/resource style versioning.
645
+
646
+ ### Testing: node:test
647
+ Prefer node:test over external frameworks.
648
+
649
+ ### Deployment: blue-green
650
+ Blue-green deployment for zero-downtime releases.
651
+ `;
652
+
653
+ const result = await queryKnowledge(singleH2Knowledge, ['database']);
654
+
655
+ // Should include only the matching H3 entry, not the whole file
656
+ assert.match(result, /Database: prepared statements/, 'includes matching H3 entry');
657
+ assert.ok(
658
+ !result.includes('API: versioned paths'),
659
+ 'does not include non-matching H3 entry',
660
+ );
661
+ assert.ok(
662
+ !result.includes('Testing: node:test'),
663
+ 'does not include non-matching H3 entry',
664
+ );
665
+ assert.ok(
666
+ !result.includes('Deployment: blue-green'),
667
+ 'does not include non-matching H3 entry',
668
+ );
669
+ // The returned payload must be dramatically smaller than the full content
670
+ assert.ok(
671
+ result.length < singleH2Knowledge.length / 2,
672
+ `scoped result (${result.length} chars) should be <50% of full content (${singleH2Knowledge.length} chars)`,
673
+ );
674
+ });
675
+
676
+ test("single H2 with H3 entries returns empty when no H3 matches (issue #4719)", async () => {
677
+ const singleH2Knowledge = `# Project Knowledge
678
+
679
+ ## Patterns
680
+
681
+ ### Database: prepared statements
682
+ Always use prepared statements with SQLite.
683
+
684
+ ### API: versioned paths
685
+ Use /v1/resource style versioning.
686
+ `;
687
+
688
+ const result = await queryKnowledge(singleH2Knowledge, ['nonexistent']);
689
+
690
+ assert.strictEqual(result, '', 'no H3 match returns empty string');
691
+ });
692
+
693
+ test("falls back to H2 when no H3 headings exist at all", async () => {
694
+ // Backwards-compat: files with only H2 topic headers must still filter.
695
+ const h2OnlyKnowledge = `# Project Knowledge
696
+
697
+ ## Database Patterns
698
+ Use prepared statements.
699
+
700
+ ## API Design
701
+ REST with OpenAPI.
702
+ `;
703
+
704
+ const result = await queryKnowledge(h2OnlyKnowledge, ['database']);
705
+
706
+ assert.match(result, /Database Patterns/, 'H2-only file falls back to H2 filtering');
707
+ assert.ok(!result.includes('API Design'), 'non-matching H2 section excluded');
708
+ });
630
709
  });
@@ -2,6 +2,7 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
+ import { extractSourceRegion } from "./test-helpers.ts";
5
6
 
6
7
  test("copyPlanningArtifacts skips when source and destination .gsd resolve to the same path", () => {
7
8
  const srcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
@@ -10,7 +11,7 @@ test("copyPlanningArtifacts skips when source and destination .gsd resolve to th
10
11
  const fnIdx = src.indexOf("function copyPlanningArtifacts");
11
12
  assert.ok(fnIdx !== -1, "copyPlanningArtifacts function exists");
12
13
 
13
- const fnBody = src.slice(fnIdx, fnIdx + 2400);
14
+ const fnBody = extractSourceRegion(src, "function copyPlanningArtifacts");
14
15
 
15
16
  const guardIdx = fnBody.indexOf("if (isSamePath(srcGsd, dstGsd)) return;");
16
17
  const copyIdx = fnBody.indexOf("safeCopyRecursive(join(srcGsd, \"milestones\")");
@@ -11,6 +11,7 @@ import { describe, it } from 'node:test'
11
11
  import assert from 'node:assert/strict'
12
12
  import { readFileSync } from 'node:fs'
13
13
  import { resolve } from 'node:path'
14
+ import { extractSourceRegion } from "./test-helpers.ts";
14
15
 
15
16
  const template = readFileSync(
16
17
  resolve(process.cwd(), 'src', 'resources', 'extensions', 'gsd', 'prompts', 'guided-discuss-slice.md'),
@@ -37,7 +38,7 @@ describe('discuss-slice structuredQuestionsAvailable template variable', () => {
37
38
  const falseIdx = template.indexOf('`{{structuredQuestionsAvailable}}` is `false`')
38
39
  assert.ok(falseIdx !== -1)
39
40
 
40
- const afterFalse = template.slice(falseIdx, falseIdx + 300)
41
+ const afterFalse = extractSourceRegion(template, '`{{structuredQuestionsAvailable}}` is `false`')
41
42
  assert.ok(
42
43
  afterFalse.includes('plain text'),
43
44
  'when structuredQuestionsAvailable is false, questions should be in plain text',
@@ -22,6 +22,7 @@ import { join, dirname } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
 
24
24
  import { DISCUSS_TOOLS_ALLOWLIST } from "../constants.ts";
25
+ import { extractSourceRegion } from "./test-helpers.ts";
25
26
 
26
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
27
28
  const guidedFlowSource = readFileSync(join(__dirname, "..", "guided-flow.ts"), "utf-8");
@@ -58,7 +59,7 @@ describe("#3616 — discuss tool scoping must not leak across sessions", () => {
58
59
  );
59
60
  const newSessionStart = agentSessionSource.indexOf("async newSession(options?:");
60
61
  assert.ok(newSessionStart >= 0, "should find newSession");
61
- const body = agentSessionSource.slice(newSessionStart, newSessionStart + 3000);
62
+ const body = extractSourceRegion(agentSessionSource, "async newSession(options?:");
62
63
 
63
64
  // Both branches (cwd-changed and cwd-unchanged) must include extension tools
64
65
  assert.ok(
@@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
4
4
  import { join, dirname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { AutoSession } from "../auto/session.ts";
7
+ import { extractSourceRegion } from "./test-helpers.ts";
7
8
 
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
10
 
@@ -20,7 +21,7 @@ describe("double mergeAndExit guard (#2645)", () => {
20
21
  const completeIdx = phasesSrc.indexOf('state.phase === "complete"');
21
22
  assert.ok(completeIdx > 0, "phases.ts should have a 'complete' phase check");
22
23
 
23
- const afterComplete = phasesSrc.slice(completeIdx, completeIdx + 600);
24
+ const afterComplete = extractSourceRegion(phasesSrc, 'state.phase === "complete"');
24
25
  const mergeIdx = afterComplete.indexOf("deps.resolver.mergeAndExit");
25
26
  const flagIdx = afterComplete.indexOf("s.milestoneMergedInPhases = true");
26
27
 
@@ -42,7 +43,7 @@ describe("double mergeAndExit guard (#2645)", () => {
42
43
  const allCompleteIdx = phasesSrc.indexOf("incomplete.length === 0");
43
44
  assert.ok(allCompleteIdx > 0, "phases.ts should have an all-milestones-complete check");
44
45
 
45
- const afterAllComplete = phasesSrc.slice(allCompleteIdx, allCompleteIdx + 800);
46
+ const afterAllComplete = extractSourceRegion(phasesSrc, "incomplete.length === 0");
46
47
  const mergeIdx = afterAllComplete.indexOf("deps.resolver.mergeAndExit");
47
48
  const flagIdx = afterAllComplete.indexOf("s.milestoneMergedInPhases = true");
48
49
 
@@ -64,7 +65,7 @@ describe("double mergeAndExit guard (#2645)", () => {
64
65
  const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
65
66
  assert.ok(step4Idx > 0, "auto.ts should have Step 4 worktree exit");
66
67
 
67
- const step4Block = autoSrc.slice(step4Idx, step4Idx + 600);
68
+ const step4Block = extractSourceRegion(autoSrc, "Step 4: Auto-worktree exit");
68
69
  assert.ok(
69
70
  step4Block.includes("milestoneMergedInPhases"),
70
71
  "stopAuto Step 4 must check milestoneMergedInPhases before merging",
@@ -13,6 +13,7 @@ import assert from "node:assert/strict";
13
13
  import { readFileSync } from "node:fs";
14
14
  import { join, dirname } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
+ import { extractSourceRegion } from "./test-helpers.ts";
16
17
 
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
19
  const RECOVERY_PATH = join(__dirname, "..", "bootstrap", "agent-end-recovery.ts");
@@ -30,7 +31,7 @@ test("agent-end-recovery.ts does not pause on aborted messages with empty conten
30
31
  assert.ok(abortIdx > -1, "abort handler must exist in agent-end-recovery.ts");
31
32
 
32
33
  // Extract the region around the abort handler (enough to see the guard logic)
33
- const abortRegion = source.slice(Math.max(0, abortIdx - 200), abortIdx + 600);
34
+ const abortRegion = extractSourceRegion(source, 'stopReason === "aborted"', { fromIdx: abortIdx });
34
35
 
35
36
  // Must check for empty content before pausing
36
37
  assert.ok(
@@ -48,7 +49,7 @@ test("agent-end-recovery.ts routes empty-content aborted messages to resolveAgen
48
49
  assert.ok(abortIdx > -1, "abort handler must exist");
49
50
 
50
51
  // Get the full abort handling block (from the if to the next stopReason check or success path)
51
- const afterAbort = source.slice(abortIdx, abortIdx + 800);
52
+ const afterAbort = extractSourceRegion(source, 'stopReason === "aborted"');
52
53
 
53
54
  // The abort block must have a code path that calls resolveAgentEnd (for empty-content case)
54
55
  assert.ok(
@@ -63,7 +64,7 @@ test("agent-end-recovery.ts checks for errorMessage presence in abort handler (#
63
64
  const abortIdx = source.indexOf('stopReason === "aborted"');
64
65
  assert.ok(abortIdx > -1, "abort handler must exist");
65
66
 
66
- const abortRegion = source.slice(abortIdx, abortIdx + 600);
67
+ const abortRegion = extractSourceRegion(source, 'stopReason === "aborted"');
67
68
 
68
69
  // Fatal aborts should have error context (errorMessage field).
69
70
  // The handler should check for this to distinguish fatal from non-fatal aborts.
@@ -1,154 +1,164 @@
1
- // Structural contracts for GSD extension bootstrap isolation.
1
+ // Behavioural contract for GSD extension bootstrap isolation (#4168, #4172).
2
2
  //
3
- // The /gsd command must survive failures in the full extension bootstrap
4
- // (register-extension.ts). This guards against the regression where a
5
- // Windows-specific import failure in register-shortcuts.ts silently
6
- // prevented /gsd from being registered at all (#4168, #4172).
3
+ // Guarantee: the `/gsd` slash command must be registered on pi even if the
4
+ // full bootstrap (shortcuts, tools, hooks, ecosystem) throws during import or
5
+ // execution. Prior regressions: a Windows-specific failure in register-
6
+ // shortcuts.ts silently prevented /gsd from being registered at all because
7
+ // registerGSDCommand was called inside the same try that loaded shortcuts.
8
+ //
9
+ // These tests exercise the real default export of index.ts (which calls
10
+ // registerGSDCommand via dynamic import, then attempts the full bootstrap)
11
+ // with a minimal mock ExtensionAPI and verify the observable behaviour
12
+ // directly: /gsd is registered in both the happy path and the degraded path.
13
+ //
14
+ // Anti-regression proof (documented in commit):
15
+ // neuter index.ts to register /gsd inside the same try{} as
16
+ // register-extension → the degraded-path test fails (no /gsd command
17
+ // registered when register-extension throws). Restore → passes.
7
18
 
8
19
  import { describe, test } from "node:test";
9
20
  import assert from "node:assert/strict";
10
- import { readFileSync } from "node:fs";
11
- import { join, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const indexSrc = readFileSync(join(__dirname, "../index.ts"), "utf-8");
16
- const registerExtSrc = readFileSync(
17
- join(__dirname, "../bootstrap/register-extension.ts"),
18
- "utf-8",
19
- );
20
-
21
- // ─── index.ts: core /gsd command must be registered before full bootstrap ─────
22
-
23
- describe("index.ts bootstrap isolation", () => {
24
- test("imports registerGSDCommand from commands/index.js separately", () => {
25
- assert.ok(
26
- indexSrc.includes('./commands/index.js"') || indexSrc.includes("./commands/index.js'"),
27
- "index.ts must import registerGSDCommand from ./commands/index.js",
28
- );
29
- });
30
-
31
- test("calls registerGSDCommand before importing register-extension.js", () => {
32
- const gsdCommandCallPos = indexSrc.indexOf("registerGSDCommand(pi)");
33
- const bootstrapImportPos = indexSrc.indexOf(
34
- './bootstrap/register-extension.js"',
35
- );
36
21
 
37
- assert.ok(gsdCommandCallPos >= 0, "must call registerGSDCommand(pi)");
38
- assert.ok(bootstrapImportPos >= 0, "must import register-extension.js");
22
+ import registerExtension from "../index.ts";
23
+
24
+ type RegisterFn = (name: string, def: unknown) => void;
25
+
26
+ function makePi(overrides: Partial<Record<string, unknown>> = {}) {
27
+ const registered: Array<[string, unknown]> = [];
28
+ const registerCommand: RegisterFn = (name, def) => {
29
+ registered.push([name, def]);
30
+ };
31
+ const events = {
32
+ on: () => {},
33
+ off: () => {},
34
+ emit: () => {},
35
+ };
36
+ const pi = {
37
+ registerCommand,
38
+ registerTool: () => {},
39
+ registerHook: () => {},
40
+ registerShortcut: () => {},
41
+ events,
42
+ ...overrides,
43
+ };
44
+ return { pi, registered };
45
+ }
46
+
47
+ describe("extension bootstrap isolation (#4168, #4172)", () => {
48
+ test("happy path: /gsd command is registered", async () => {
49
+ const { pi, registered } = makePi();
50
+ await registerExtension(pi as any);
51
+ const names = registered.map(([n]) => n);
39
52
  assert.ok(
40
- gsdCommandCallPos < bootstrapImportPos,
41
- "registerGSDCommand(pi) must be called BEFORE importing register-extension.js",
53
+ names.includes("gsd"),
54
+ `expected 'gsd' in registered commands, got ${JSON.stringify(names)}`,
42
55
  );
43
56
  });
44
57
 
45
- test("wraps register-extension.js import in try-catch", () => {
46
- // The dynamic import of register-extension.js must be inside a try block
47
- const tryPos = indexSrc.indexOf("try {");
48
- const bootstrapImportPos = indexSrc.indexOf(
49
- './bootstrap/register-extension.js"',
50
- );
51
- const catchPos = indexSrc.indexOf("catch (err)");
52
-
53
- assert.ok(tryPos >= 0, "must have try block");
54
- assert.ok(catchPos >= 0, "must have catch block");
58
+ test("degraded path: /gsd still registered when registerCommand throws for non-core commands", async () => {
59
+ // Simulate the Windows-style failure: pi.registerCommand throws for a
60
+ // specific non-core command ('kill' is a simple target registered by
61
+ // the full bootstrap) — the full bootstrap must fail but /gsd must
62
+ // already be registered before the failure occurs.
63
+ const registered: Array<[string, unknown]> = [];
64
+ const pi = {
65
+ registerCommand: (name: string, def: unknown) => {
66
+ if (name !== "gsd" && name !== "worktree" && name !== "exit") {
67
+ // Let /gsd, /worktree, /exit succeed (they precede the non-core
68
+ // loop); throw when the first non-core registration fires.
69
+ }
70
+ if (name === "kill") throw new Error("simulated windows failure");
71
+ registered.push([name, def]);
72
+ },
73
+ registerTool: () => {},
74
+ registerHook: () => {},
75
+ registerShortcut: () => {},
76
+ events: { on: () => {}, off: () => {}, emit: () => {} },
77
+ };
78
+
79
+ // registerExtension must not throw — the outer try/catch in index.ts
80
+ // swallows bootstrap failures after /gsd is already registered.
81
+ await registerExtension(pi as any);
82
+
83
+ const names = registered.map(([n]) => n);
55
84
  assert.ok(
56
- tryPos < bootstrapImportPos && bootstrapImportPos < catchPos,
57
- "register-extension.js import must be wrapped in try-catch",
85
+ names.includes("gsd"),
86
+ "expected 'gsd' to be registered even when a later command registration throws",
58
87
  );
59
88
  });
60
89
 
61
- test("logs warning on bootstrap failure via workflow-logger", () => {
62
- assert.ok(
63
- indexSrc.includes("logWarning"),
64
- "must use logWarning when bootstrap fails",
65
- );
66
- assert.ok(
67
- indexSrc.includes("Extension setup partially failed"),
68
- "warning message must indicate partial failure with /gsd still available",
90
+ test("degraded path: /gsd registered BEFORE any non-core command", async () => {
91
+ // Ordering guard: the first registerCommand call must be for 'gsd',
92
+ // because index.ts awaits registerGSDCommand(pi) before importing
93
+ // register-extension. Regression scenario: if a future refactor moves
94
+ // registerGSDCommand into the try block or after other registrations,
95
+ // a failure in those earlier registrations would take /gsd down too.
96
+ const calls: string[] = [];
97
+ const pi = {
98
+ registerCommand: (name: string) => {
99
+ calls.push(name);
100
+ },
101
+ registerTool: () => {},
102
+ registerHook: () => {},
103
+ registerShortcut: () => {},
104
+ events: { on: () => {}, off: () => {}, emit: () => {} },
105
+ };
106
+ await registerExtension(pi as any);
107
+ assert.ok(calls.length > 0, "expected at least one registerCommand call");
108
+ assert.equal(
109
+ calls[0],
110
+ "gsd",
111
+ `expected 'gsd' to be the first command registered, got ${JSON.stringify(calls)}`,
69
112
  );
70
113
  });
71
114
  });
72
115
 
73
- // ─── register-extension.ts: no double-registration + defensive wrapping ───────
74
-
75
- describe("register-extension.ts defensive registration", () => {
76
- test("does NOT import or call registerGSDCommand (avoids double-registration)", () => {
77
- // registerGSDCommand is now called by index.ts, not register-extension.ts
78
- assert.ok(
79
- !registerExtSrc.includes("import { registerGSDCommand }"),
80
- "register-extension.ts must NOT import registerGSDCommand",
81
- );
82
-
83
- // Check the function body of registerGsdExtension doesn't call it
84
- const funcBodyStart = registerExtSrc.indexOf(
85
- "export function registerGsdExtension",
86
- );
87
- const funcBody = registerExtSrc.slice(funcBodyStart);
88
- assert.ok(
89
- !funcBody.includes("registerGSDCommand(pi)"),
90
- "registerGsdExtension must NOT call registerGSDCommand(pi)",
91
- );
92
- });
93
-
94
- test("still registers worktree, exit, and kill commands", () => {
95
- const funcBodyStart = registerExtSrc.indexOf(
96
- "export function registerGsdExtension",
97
- );
98
- const funcBody = registerExtSrc.slice(funcBodyStart);
99
-
100
- assert.ok(
101
- funcBody.includes("registerWorktreeCommand(pi)"),
102
- "must register worktree command",
103
- );
104
- assert.ok(
105
- funcBody.includes("registerExitCommand(pi)"),
106
- "must register exit command",
107
- );
108
- assert.ok(
109
- funcBody.includes('"kill"'),
110
- "must register kill command",
111
- );
112
- });
113
-
114
- test("wraps non-critical registrations in individual try-catch blocks", () => {
115
- const funcBodyStart = registerExtSrc.indexOf(
116
- "export function registerGsdExtension",
117
- );
118
- const funcBody = registerExtSrc.slice(funcBodyStart);
119
-
120
- // Each non-critical registration should be wrapped with error handling
121
- const registrationNames = [
122
- "dynamic-tools",
123
- "db-tools",
124
- "journal-tools",
125
- "query-tools",
126
- "shortcuts",
127
- "hooks",
128
- ];
129
-
130
- for (const name of registrationNames) {
131
- assert.ok(
132
- funcBody.includes(`"${name}"`),
133
- `non-critical registration "${name}" must be present`,
134
- );
135
- }
136
-
137
- // Must have try-catch inside the registration loop
116
+ // Behavioural contract for registerGsdExtension itself: each non-core
117
+ // registration is wrapped in its own try/catch so one failure does not
118
+ // prevent siblings from loading.
119
+
120
+ import { registerGsdExtension } from "../bootstrap/register-extension.ts";
121
+
122
+ describe("registerGsdExtension defensive registration", () => {
123
+ test("a failing shortcut registration does not prevent kill command registration", async () => {
124
+ // `shortcuts` is registered via a non-critical slot that is wrapped in
125
+ // its own try/catch. `kill` is registered before the non-critical loop
126
+ // as a critical command. Simulate: registerShortcut throws. Expect:
127
+ // 'kill' is still registered, registerGsdExtension does not throw.
128
+ const registered: string[] = [];
129
+ const pi = {
130
+ registerCommand: (name: string) => {
131
+ registered.push(name);
132
+ },
133
+ registerTool: () => {},
134
+ registerHook: () => {},
135
+ registerShortcut: () => {
136
+ throw new Error("simulated platform-specific shortcut failure");
137
+ },
138
+ events: { on: () => {}, off: () => {}, emit: () => {} },
139
+ };
140
+ assert.doesNotThrow(() => registerGsdExtension(pi as any));
138
141
  assert.ok(
139
- funcBody.includes("try {") && funcBody.includes("catch (err)"),
140
- "must have try-catch for non-critical registrations",
142
+ registered.includes("kill"),
143
+ `expected 'kill' to be registered despite shortcut failure, got ${JSON.stringify(registered)}`,
141
144
  );
142
145
  });
143
146
 
144
- test("logs warning when a non-critical registration fails", () => {
145
- assert.ok(
146
- registerExtSrc.includes("Failed to register"),
147
- "must log descriptive warning for individual registration failures",
148
- );
147
+ test("does NOT register /gsd (caller's responsibility, avoids double-registration)", () => {
148
+ const registered: string[] = [];
149
+ const pi = {
150
+ registerCommand: (name: string) => {
151
+ registered.push(name);
152
+ },
153
+ registerTool: () => {},
154
+ registerHook: () => {},
155
+ registerShortcut: () => {},
156
+ events: { on: () => {}, off: () => {}, emit: () => {} },
157
+ };
158
+ registerGsdExtension(pi as any);
149
159
  assert.ok(
150
- registerExtSrc.includes("logWarning"),
151
- "must use logWarning from workflow-logger",
160
+ !registered.includes("gsd"),
161
+ `registerGsdExtension must NOT register 'gsd' (it is registered separately by index.ts), got ${JSON.stringify(registered)}`,
152
162
  );
153
163
  });
154
164
  });
@@ -26,14 +26,6 @@ import { MAX_FINALIZE_TIMEOUTS } from "../auto/types.ts";
26
26
 
27
27
  const { assertTrue, assertEq, report } = createTestContext();
28
28
 
29
- function getRunFinalizeBody(phasesSource: string): string {
30
- const fnIdx = phasesSource.indexOf("export async function runFinalize(");
31
- assertTrue(fnIdx > 0, "runFinalize function should exist in phases.ts");
32
-
33
- const nextExportIdx = phasesSource.indexOf("\nexport ", fnIdx + 1);
34
- return phasesSource.slice(fnIdx, nextExportIdx > fnIdx ? nextExportIdx : undefined);
35
- }
36
-
37
29
  // ═══ Test: withTimeout resolves when inner promise resolves promptly ══════════
38
30
 
39
31
  {
@@ -142,45 +134,6 @@ function getRunFinalizeBody(phasesSource: string): string {
142
134
  assertEq(result.timedOut, false, "should not time out");
143
135
  }
144
136
 
145
- // ═══ Test: runFinalize wraps BOTH pre and post verification with withTimeout ═
146
-
147
- {
148
- console.log("\n=== #3757: runFinalize wraps preVerification with timeout guard ===");
149
-
150
- const { readFileSync } = await import("node:fs");
151
- const phasesSource = readFileSync(
152
- new URL("../auto/phases.ts", import.meta.url),
153
- "utf-8",
154
- );
155
-
156
- const fnBody = getRunFinalizeBody(phasesSource);
157
-
158
- // postUnitPreVerification must be wrapped in withTimeout
159
- const preTimeoutIdx = fnBody.indexOf("withTimeout(");
160
- assertTrue(preTimeoutIdx > 0, "withTimeout should appear in runFinalize");
161
-
162
- const preVerIdx = fnBody.indexOf("postUnitPreVerification");
163
- assertTrue(preVerIdx > 0, "postUnitPreVerification should appear in runFinalize");
164
-
165
- // The first withTimeout should wrap postUnitPreVerification (not postUnitPostVerification)
166
- const firstWithTimeout = fnBody.slice(preTimeoutIdx, preTimeoutIdx + 200);
167
- assertTrue(
168
- firstWithTimeout.includes("postUnitPreVerification"),
169
- "first withTimeout in runFinalize should wrap postUnitPreVerification",
170
- );
171
-
172
- // postUnitPostVerification must also be wrapped
173
- const postVerIdx = fnBody.indexOf("postUnitPostVerification");
174
- assertTrue(postVerIdx > 0, "postUnitPostVerification should appear in runFinalize");
175
-
176
- // Count withTimeout occurrences — should be at least 2 (pre + post)
177
- const timeoutCount = (fnBody.match(/withTimeout\(/g) || []).length;
178
- assertTrue(
179
- timeoutCount >= 2,
180
- `runFinalize should have at least 2 withTimeout guards (found ${timeoutCount})`,
181
- );
182
- }
183
-
184
137
  // ═══ Test: MAX_FINALIZE_TIMEOUTS is defined and reasonable ═══════════════════
185
138
 
186
139
  {
@@ -200,62 +153,13 @@ function getRunFinalizeBody(phasesSource: string): string {
200
153
  );
201
154
  }
202
155
 
203
- // ═══ Test: timeout handlers escalate after consecutive timeouts ══════════════
204
-
205
- {
206
- console.log("\n=== #3757: timeout handlers escalate and detach currentUnit ===");
207
-
208
- const { readFileSync } = await import("node:fs");
209
- const phasesSource = readFileSync(
210
- new URL("../auto/phases.ts", import.meta.url),
211
- "utf-8",
212
- );
213
-
214
- const fnBody = getRunFinalizeBody(phasesSource);
215
-
216
- const helperCallCount = (fnBody.match(/failClosedOnFinalizeTimeout\(/g) || []).length;
217
- assertTrue(
218
- helperCallCount >= 2,
219
- `runFinalize should route both timeout branches through failClosedOnFinalizeTimeout (found ${helperCallCount})`,
220
- );
221
-
222
- const helperStart = phasesSource.indexOf("async function failClosedOnFinalizeTimeout");
223
- assertTrue(helperStart > 0, "failClosedOnFinalizeTimeout helper should exist");
224
- const helperEnd = phasesSource.indexOf("// ─── runPreDispatch", helperStart);
225
- const helperBody = phasesSource.slice(helperStart, helperEnd > helperStart ? helperEnd : undefined);
226
-
227
- const incrementCount = (helperBody.match(/consecutiveFinalizeTimeouts\+\+/g) || []).length;
228
- assertTrue(
229
- incrementCount >= 1,
230
- `timeout helper should increment consecutiveFinalizeTimeouts (found ${incrementCount})`,
231
- );
232
-
233
- const detachCount = (helperBody.match(/s\.currentUnit\s*=\s*null/g) || []).length;
234
- assertTrue(
235
- detachCount >= 1,
236
- `timeout helper should detach s.currentUnit (found ${detachCount})`,
237
- );
238
-
239
- const pauseCount = (helperBody.match(/pauseAuto\(/g) || []).length;
240
- assertTrue(
241
- pauseCount >= 1,
242
- `timeout helper should pause auto-mode (found ${pauseCount})`,
243
- );
244
-
245
- assertTrue(
246
- helperBody.includes('eventType: "unit-end"'),
247
- "timeout helper should emit a terminal unit-end event",
248
- );
249
- assertTrue(
250
- helperBody.includes('phase: "finalize-timeout"'),
251
- "timeout helper should persist finalize-timeout runtime state",
252
- );
253
-
254
- // Successful finalize should reset the counter
255
- assertTrue(
256
- fnBody.includes("consecutiveFinalizeTimeouts = 0"),
257
- "should reset consecutiveFinalizeTimeouts on successful finalize",
258
- );
259
- }
156
+ // Note: the two previous source-grep blocks that scanned phases.ts for
157
+ // `withTimeout(` / `failClosedOnFinalizeTimeout(` occurrences were removed
158
+ // under #4825 — they encoded implementation shape (Goodhart) and broke on
159
+ // any helper/loop refactor without catching a real regression. The intended
160
+ // behavioural invariant (pre+post verification hangs → pauseAuto called,
161
+ // unit-end emitted, escalation counter incremented) should be covered by a
162
+ // runFinalize integration test with mocked hanging verification — tracked
163
+ // separately. Refs #4825.
260
164
 
261
165
  report();