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
@@ -20,6 +20,9 @@ import type { AutoSession } from "./auto/session.js";
20
20
  import { debugLog } from "./debug-logger.js";
21
21
  import { MergeConflictError } from "./git-service.js";
22
22
  import { emitJournalEvent } from "./journal.js";
23
+ import { emitWorktreeCreated, emitWorktreeMerged } from "./worktree-telemetry.js";
24
+ import { getCollapseCadence, getMilestoneResquash, resquashMilestoneOnMain } from "./slice-cadence.js";
25
+ import { loadEffectiveGSDPreferences } from "./preferences.js";
23
26
 
24
27
  // ─── Dependency Interface ──────────────────────────────────────────────────
25
28
 
@@ -290,6 +293,19 @@ export class WorktreeResolver {
290
293
  eventType: "worktree-enter",
291
294
  data: { milestoneId, wtPath, created: !existingPath },
292
295
  });
296
+ // #4764 — record creation/enter as a lifecycle event so the telemetry
297
+ // aggregator can pair it with the eventual worktree-merged event.
298
+ try {
299
+ emitWorktreeCreated(this.s.originalBasePath || this.s.basePath, milestoneId, {
300
+ reason: existingPath ? "enter-milestone" : "create-milestone",
301
+ });
302
+ } catch (telemetryErr) {
303
+ debugLog("WorktreeResolver", {
304
+ action: "enterMilestone",
305
+ phase: "telemetry-emit",
306
+ error: telemetryErr instanceof Error ? telemetryErr.message : String(telemetryErr),
307
+ });
308
+ }
293
309
  ctx.notify(`Entered worktree for ${milestoneId} at ${wtPath}`, "info");
294
310
  } catch (err) {
295
311
  const msg = err instanceof Error ? err.message : String(err);
@@ -398,6 +414,10 @@ export class WorktreeResolver {
398
414
  mergeAndExit(milestoneId: string, ctx: NotifyCtx): void {
399
415
  this.validateMilestoneId(milestoneId);
400
416
 
417
+ // #4764 — telemetry: record start timestamp so we can emit merge duration.
418
+ const mergeStartedAt = new Date().toISOString();
419
+ const mergeStartMs = Date.now();
420
+
401
421
  // If worktree creation failed earlier, skip merge — work is on current branch (#2483)
402
422
  if (this.s.isolationDegraded) {
403
423
  debugLog("WorktreeResolver", {
@@ -444,17 +464,79 @@ export class WorktreeResolver {
444
464
  return;
445
465
  }
446
466
 
467
+ let actuallyMerged = false;
447
468
  if (
448
469
  mode === "worktree" || inWorktree
449
470
  ) {
450
- this._mergeWorktreeMode(milestoneId, ctx);
471
+ actuallyMerged = this._mergeWorktreeMode(milestoneId, ctx);
451
472
  } else if (mode === "branch") {
452
- this._mergeBranchMode(milestoneId, ctx);
473
+ actuallyMerged = this._mergeBranchMode(milestoneId, ctx);
474
+ }
475
+
476
+ // The remainder of this function emits telemetry and runs re-squash.
477
+ // Both are gated on actuallyMerged — if the _merge* helper took a
478
+ // no-merge path (missing originalBase, no roadmap, wrong branch) the
479
+ // milestone branch was intentionally left unmerged and we must not
480
+ // emit a worktree-merged event or collapse commits on main.
481
+ if (!actuallyMerged) {
482
+ // Always clear the start-SHA tracker to avoid leaking across sessions.
483
+ this.s.milestoneStartShas.delete(milestoneId);
484
+ return;
485
+ }
486
+
487
+ // #4765 — when collapse_cadence=slice AND milestone_resquash=true, the
488
+ // N per-slice commits on main should be collapsed into one milestone
489
+ // commit. Done AFTER the primary merge-and-teardown so the branch and
490
+ // worktree are already cleaned up; we operate on main directly.
491
+ try {
492
+ const startSha = this.s.milestoneStartShas.get(milestoneId);
493
+ if (startSha) {
494
+ const prefs = loadEffectiveGSDPreferences(this.s.originalBasePath || this.s.basePath)?.preferences;
495
+ if (getCollapseCadence(prefs) === "slice" && getMilestoneResquash(prefs)) {
496
+ const result = resquashMilestoneOnMain(
497
+ this.s.originalBasePath || this.s.basePath,
498
+ milestoneId,
499
+ startSha,
500
+ );
501
+ if (result.resquashed) {
502
+ ctx.notify(
503
+ `slice-cadence: re-squashed slice commits for ${milestoneId} into a single milestone commit.`,
504
+ "info",
505
+ );
506
+ }
507
+ }
508
+ this.s.milestoneStartShas.delete(milestoneId);
509
+ }
510
+ } catch (err) {
511
+ debugLog("WorktreeResolver", {
512
+ action: "mergeAndExit",
513
+ milestoneId,
514
+ phase: "resquash",
515
+ error: err instanceof Error ? err.message : String(err),
516
+ });
517
+ }
518
+
519
+ // #4764 — record merge completion. Only reaches here when an actual
520
+ // merge ran; failure paths throw out of _merge* before this point and
521
+ // no-merge paths returned above.
522
+ try {
523
+ emitWorktreeMerged(this.s.originalBasePath || this.s.basePath, milestoneId, {
524
+ reason: "milestone-complete",
525
+ startedAt: mergeStartedAt,
526
+ durationMs: Date.now() - mergeStartMs,
527
+ });
528
+ } catch (telemetryErr) {
529
+ debugLog("WorktreeResolver", {
530
+ action: "mergeAndExit",
531
+ phase: "telemetry-emit",
532
+ error: telemetryErr instanceof Error ? telemetryErr.message : String(telemetryErr),
533
+ });
453
534
  }
454
535
  }
455
536
 
456
- /** Worktree-mode merge: read roadmap, merge, teardown, reset paths. */
457
- private _mergeWorktreeMode(milestoneId: string, ctx: NotifyCtx): void {
537
+ /** Worktree-mode merge: read roadmap, merge, teardown, reset paths.
538
+ * Returns true when a squash-merge actually ran (false on skip paths). */
539
+ private _mergeWorktreeMode(milestoneId: string, ctx: NotifyCtx): boolean {
458
540
  const originalBase = this.s.originalBasePath;
459
541
  if (!originalBase) {
460
542
  debugLog("WorktreeResolver", {
@@ -464,9 +546,10 @@ export class WorktreeResolver {
464
546
  skipped: true,
465
547
  reason: "missing-original-base",
466
548
  });
467
- return;
549
+ return false;
468
550
  }
469
551
 
552
+ let merged = false;
470
553
  try {
471
554
  const { synced } = this.deps.syncWorktreeStateBack(
472
555
  originalBase,
@@ -515,6 +598,7 @@ export class WorktreeResolver {
515
598
  milestoneId,
516
599
  roadmapContent,
517
600
  );
601
+ merged = true;
518
602
 
519
603
  // #2945 Bug 3: mergeMilestoneToMain performs best-effort worktree
520
604
  // cleanup internally (step 12), but it can silently fail on Windows
@@ -618,10 +702,12 @@ export class WorktreeResolver {
618
702
  result: "done",
619
703
  basePath: this.s.basePath,
620
704
  });
705
+ return merged;
621
706
  }
622
707
 
623
- /** Branch-mode merge: check current branch, merge if on milestone branch. */
624
- private _mergeBranchMode(milestoneId: string, ctx: NotifyCtx): void {
708
+ /** Branch-mode merge: check current branch, merge if on milestone branch.
709
+ * Returns true when a merge actually ran (false on skip paths). */
710
+ private _mergeBranchMode(milestoneId: string, ctx: NotifyCtx): boolean {
625
711
  try {
626
712
  const currentBranch = this.deps.getCurrentBranch(this.s.basePath);
627
713
  const milestoneBranch = this.deps.autoWorktreeBranch(milestoneId);
@@ -636,7 +722,7 @@ export class WorktreeResolver {
636
722
  currentBranch,
637
723
  milestoneBranch,
638
724
  });
639
- return;
725
+ return false;
640
726
  }
641
727
 
642
728
  const roadmapPath = this.deps.resolveMilestoneFile(
@@ -652,7 +738,7 @@ export class WorktreeResolver {
652
738
  skipped: true,
653
739
  reason: "no-roadmap",
654
740
  });
655
- return;
741
+ return false;
656
742
  }
657
743
 
658
744
  const roadmapContent = this.deps.readFileSync(roadmapPath, "utf-8");
@@ -683,6 +769,7 @@ export class WorktreeResolver {
683
769
  mode: "branch",
684
770
  result: "success",
685
771
  });
772
+ return true;
686
773
  } catch (err) {
687
774
  const msg = err instanceof Error ? err.message : String(err);
688
775
  debugLog("WorktreeResolver", {
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Worktree telemetry — #4764
3
+ *
4
+ * Thin emit helpers + aggregator on top of the existing journal. Separate
5
+ * module so callers import a tiny surface and don't have to assemble
6
+ * JournalEntry records by hand. Kernighan: the underlying emit path
7
+ * (emitJournalEvent) is already battle-tested; this module is just
8
+ * structured call sites + a summarizer.
9
+ *
10
+ * Emitted event types (see journal.ts):
11
+ * - worktree-created worktree entered/created for a milestone
12
+ * - worktree-merged worktree merge back to main completed
13
+ * - worktree-orphaned audit detected an orphaned branch/worktree
14
+ * - auto-exit auto-mode exited (pause/stop/blocked/error)
15
+ * - worktree-sync syncStateToProjectRoot snapshot
16
+ * - canonical-root-redirect resolveCanonicalMilestoneRoot redirected
17
+ *
18
+ * These events are purely observational. They never block, never throw,
19
+ * and never carry code content — only IDs, counts, durations, and reasons.
20
+ */
21
+
22
+ import { randomUUID } from "node:crypto";
23
+ import { emitJournalEvent, queryJournal } from "./journal.js";
24
+ import type { JournalEntry } from "./journal.js";
25
+
26
+ function now(): string {
27
+ return new Date().toISOString();
28
+ }
29
+
30
+ function baseEntry(eventType: JournalEntry["eventType"], data: Record<string, unknown>): JournalEntry {
31
+ return {
32
+ ts: now(),
33
+ flowId: (typeof data.flowId === "string" ? data.flowId : undefined) ?? randomUUID(),
34
+ seq: typeof data.seq === "number" ? data.seq : 0,
35
+ eventType,
36
+ data,
37
+ };
38
+ }
39
+
40
+ // ─── Reason literal unions ───────────────────────────────────────────────
41
+ // Closed sets so typos at call sites are rejected at compile time and can't
42
+ // silently fragment the telemetry buckets produced by summarizeWorktreeTelemetry.
43
+
44
+ export type WorktreeCreatedReason = "create-milestone" | "enter-milestone";
45
+ export type AutoExitReason =
46
+ | "pause"
47
+ | "stop"
48
+ | "blocked"
49
+ | "merge-conflict"
50
+ | "merge-failed"
51
+ | "slice-merge-conflict"
52
+ | "all-complete"
53
+ | "no-active-milestone"
54
+ | "other";
55
+
56
+ // ─── Emitters ────────────────────────────────────────────────────────────
57
+
58
+ export function emitWorktreeCreated(
59
+ projectRoot: string,
60
+ milestoneId: string,
61
+ meta: { flowId?: string; reason?: WorktreeCreatedReason } = {},
62
+ ): void {
63
+ emitJournalEvent(projectRoot, baseEntry("worktree-created", {
64
+ milestoneId,
65
+ startedAt: now(),
66
+ flowId: meta.flowId,
67
+ reason: meta.reason ?? "enter-milestone",
68
+ }));
69
+ }
70
+
71
+ export function emitWorktreeMerged(
72
+ projectRoot: string,
73
+ milestoneId: string,
74
+ meta: {
75
+ flowId?: string;
76
+ reason?: "milestone-complete" | "all-complete" | "stop-fallback" | "transition" | "other";
77
+ startedAt?: string;
78
+ durationMs?: number;
79
+ sliceCount?: number;
80
+ taskCount?: number;
81
+ conflict?: boolean;
82
+ conflictedFiles?: number;
83
+ } = {},
84
+ ): void {
85
+ emitJournalEvent(projectRoot, baseEntry("worktree-merged", {
86
+ milestoneId,
87
+ endedAt: now(),
88
+ flowId: meta.flowId,
89
+ reason: meta.reason ?? "other",
90
+ startedAt: meta.startedAt,
91
+ durationMs: meta.durationMs,
92
+ sliceCount: meta.sliceCount,
93
+ taskCount: meta.taskCount,
94
+ conflict: meta.conflict ?? false,
95
+ conflictedFiles: meta.conflictedFiles ?? 0,
96
+ }));
97
+ }
98
+
99
+ export function emitWorktreeOrphaned(
100
+ projectRoot: string,
101
+ milestoneId: string,
102
+ meta: {
103
+ flowId?: string;
104
+ reason: "in-progress-unmerged" | "complete-unmerged" | "stale-branch";
105
+ commitsAhead?: number;
106
+ worktreeDirExists?: boolean;
107
+ },
108
+ ): void {
109
+ emitJournalEvent(projectRoot, baseEntry("worktree-orphaned", {
110
+ milestoneId,
111
+ flowId: meta.flowId,
112
+ reason: meta.reason,
113
+ commitsAhead: meta.commitsAhead,
114
+ worktreeDirExists: meta.worktreeDirExists ?? false,
115
+ detectedAt: now(),
116
+ }));
117
+ }
118
+
119
+ export function emitAutoExit(
120
+ projectRoot: string,
121
+ meta: {
122
+ flowId?: string;
123
+ /** Must come from the closed AutoExitReason set. Callers with free-form
124
+ * reasons (e.g. stopAuto's `reason?: string` parameter) should map to
125
+ * the closed set before emitting. */
126
+ reason: AutoExitReason;
127
+ milestoneId?: string;
128
+ milestoneMerged: boolean;
129
+ },
130
+ ): void {
131
+ emitJournalEvent(projectRoot, baseEntry("auto-exit", {
132
+ reason: meta.reason,
133
+ flowId: meta.flowId,
134
+ milestoneId: meta.milestoneId,
135
+ milestoneMerged: meta.milestoneMerged,
136
+ exitedAt: now(),
137
+ }));
138
+ }
139
+
140
+ export function emitWorktreeSync(
141
+ projectRoot: string,
142
+ milestoneId: string,
143
+ meta: {
144
+ flowId?: string;
145
+ filesCopied?: number;
146
+ bytesCopied?: number;
147
+ commitsAhead?: number;
148
+ worktreeAgeMs?: number;
149
+ },
150
+ ): void {
151
+ emitJournalEvent(projectRoot, baseEntry("worktree-sync", {
152
+ milestoneId,
153
+ flowId: meta.flowId,
154
+ filesCopied: meta.filesCopied,
155
+ bytesCopied: meta.bytesCopied,
156
+ commitsAhead: meta.commitsAhead,
157
+ worktreeAgeMs: meta.worktreeAgeMs,
158
+ }));
159
+ }
160
+
161
+ export function emitCanonicalRootRedirect(
162
+ projectRoot: string,
163
+ milestoneId: string,
164
+ redirectedTo: string,
165
+ meta: { flowId?: string } = {},
166
+ ): void {
167
+ emitJournalEvent(projectRoot, baseEntry("canonical-root-redirect", {
168
+ milestoneId,
169
+ redirectedTo,
170
+ flowId: meta.flowId,
171
+ }));
172
+ }
173
+
174
+ // #4765 — slice-cadence collapse events
175
+
176
+ export function emitSliceMerged(
177
+ projectRoot: string,
178
+ milestoneId: string,
179
+ sliceId: string,
180
+ meta: { durationMs?: number; conflict?: boolean; commitSha?: string; flowId?: string } = {},
181
+ ): void {
182
+ emitJournalEvent(projectRoot, baseEntry("slice-merged", {
183
+ milestoneId,
184
+ sliceId,
185
+ mergedAt: now(),
186
+ durationMs: meta.durationMs,
187
+ conflict: meta.conflict ?? false,
188
+ commitSha: meta.commitSha,
189
+ flowId: meta.flowId,
190
+ }));
191
+ }
192
+
193
+ export function emitMilestoneResquash(
194
+ projectRoot: string,
195
+ milestoneId: string,
196
+ meta: { sliceCount: number; startSha?: string; endSha?: string; flowId?: string } = { sliceCount: 0 },
197
+ ): void {
198
+ emitJournalEvent(projectRoot, baseEntry("milestone-resquash", {
199
+ milestoneId,
200
+ sliceCount: meta.sliceCount,
201
+ startSha: meta.startSha,
202
+ endSha: meta.endSha,
203
+ resquashedAt: now(),
204
+ flowId: meta.flowId,
205
+ }));
206
+ }
207
+
208
+ // ─── Aggregator ──────────────────────────────────────────────────────────
209
+
210
+ export interface WorktreeTelemetrySummary {
211
+ /** Count of worktrees created within the window */
212
+ worktreesCreated: number;
213
+ /** Count of worktrees merged within the window */
214
+ worktreesMerged: number;
215
+ /** Count of orphan detections within the window */
216
+ orphansDetected: number;
217
+ /** Breakdown by orphan reason */
218
+ orphansByReason: Record<string, number>;
219
+ /** Merge durations in milliseconds, sorted ascending */
220
+ mergeDurationsMs: number[];
221
+ /** Number of merges that hit a conflict */
222
+ mergeConflicts: number;
223
+ /** Auto-exit reasons and their counts */
224
+ exitsByReason: Record<string, number>;
225
+ /** Auto-exits where the milestone was NOT merged before exit — the #4761 producer metric */
226
+ exitsWithUnmergedWork: number;
227
+ /** Count of canonical-root-redirects (how often #4761 validation would have read stale state) */
228
+ canonicalRedirects: number;
229
+ /** #4765 — count of successful slice-level merges (slice-cadence feature) */
230
+ slicesMerged: number;
231
+ /** #4765 — count of slice-level merge conflicts */
232
+ sliceMergeConflicts: number;
233
+ /** #4765 — count of milestone-level re-squash operations */
234
+ milestoneResquashes: number;
235
+ }
236
+
237
+ /**
238
+ * Summarize worktree telemetry across the journal. Optional time window
239
+ * via filters.after / filters.before (ISO-8601).
240
+ */
241
+ export function summarizeWorktreeTelemetry(
242
+ projectRoot: string,
243
+ filters?: { after?: string; before?: string },
244
+ ): WorktreeTelemetrySummary {
245
+ const entries = queryJournal(projectRoot, filters);
246
+
247
+ const summary: WorktreeTelemetrySummary = {
248
+ worktreesCreated: 0,
249
+ worktreesMerged: 0,
250
+ orphansDetected: 0,
251
+ orphansByReason: {},
252
+ mergeDurationsMs: [],
253
+ mergeConflicts: 0,
254
+ exitsByReason: {},
255
+ exitsWithUnmergedWork: 0,
256
+ canonicalRedirects: 0,
257
+ slicesMerged: 0,
258
+ sliceMergeConflicts: 0,
259
+ milestoneResquashes: 0,
260
+ };
261
+
262
+ for (const e of entries) {
263
+ const d = e.data ?? {};
264
+ switch (e.eventType) {
265
+ case "worktree-created":
266
+ summary.worktreesCreated++;
267
+ break;
268
+ case "worktree-merged":
269
+ summary.worktreesMerged++;
270
+ if (typeof d.durationMs === "number") summary.mergeDurationsMs.push(d.durationMs);
271
+ if (d.conflict === true) summary.mergeConflicts++;
272
+ break;
273
+ case "worktree-orphaned": {
274
+ summary.orphansDetected++;
275
+ const reason = typeof d.reason === "string" ? d.reason : "unknown";
276
+ summary.orphansByReason[reason] = (summary.orphansByReason[reason] ?? 0) + 1;
277
+ break;
278
+ }
279
+ case "auto-exit": {
280
+ const reason = typeof d.reason === "string" ? d.reason : "unknown";
281
+ summary.exitsByReason[reason] = (summary.exitsByReason[reason] ?? 0) + 1;
282
+ if (d.milestoneMerged === false) summary.exitsWithUnmergedWork++;
283
+ break;
284
+ }
285
+ case "canonical-root-redirect":
286
+ summary.canonicalRedirects++;
287
+ break;
288
+ case "slice-merged":
289
+ summary.slicesMerged++;
290
+ if (d.conflict === true) summary.sliceMergeConflicts++;
291
+ break;
292
+ case "milestone-resquash":
293
+ summary.milestoneResquashes++;
294
+ break;
295
+ default:
296
+ break;
297
+ }
298
+ }
299
+
300
+ summary.mergeDurationsMs.sort((a, b) => a - b);
301
+ return summary;
302
+ }
303
+
304
+ /**
305
+ * Return the p{quantile} of a sorted array using the nearest-rank method.
306
+ * Quantile in [0,1].
307
+ *
308
+ * Prior implementation used Math.floor(q*n), which overstates exact-rank
309
+ * quantiles by one sample (e.g. p95 of 20 values returned the max instead
310
+ * of the 19th value). The nearest-rank index is ceil(q*n) - 1, clamped to
311
+ * [0, n-1].
312
+ */
313
+ export function percentile(sortedValues: number[], q: number): number | null {
314
+ if (sortedValues.length === 0) return null;
315
+ if (q <= 0) return sortedValues[0];
316
+ if (q >= 1) return sortedValues[sortedValues.length - 1];
317
+ const idx = Math.min(
318
+ sortedValues.length - 1,
319
+ Math.max(0, Math.ceil(q * sortedValues.length) - 1),
320
+ );
321
+ return sortedValues[idx];
322
+ }
@@ -203,7 +203,9 @@ async function assertTrustedStdioServer(
203
203
  return trustKey;
204
204
  }
205
205
 
206
- function getServerConfig(name: string): McpServerConfig | undefined {
206
+ // Exported for tests (see tests/server-name-spaces.test.ts).
207
+ // Production call sites treat this as module-private.
208
+ export function getServerConfig(name: string): McpServerConfig | undefined {
207
209
  const trimmed = name.trim();
208
210
  return readConfigs().find((s) =>
209
211
  s.name === trimmed ||
@@ -1,55 +1,89 @@
1
1
  /**
2
2
  * Regression test for #3029 — mcp_discover fails for server names with spaces.
3
3
  *
4
- * The getServerConfig lookup must handle:
5
- * 1. Exact match (already works)
4
+ * getServerConfig must handle:
5
+ * 1. Exact match
6
6
  * 2. Names with leading/trailing whitespace (trimming)
7
7
  * 3. Case-insensitive matching (e.g. "Langgraph code" vs "langgraph Code")
8
8
  *
9
- * We test at the source level since getServerConfig is not exported.
9
+ * getOrConnect must use the canonical (config.name) as the cache key so that
10
+ * subsequent lookups with variant casing/whitespace hit the same connection.
11
+ *
12
+ * These are behaviour tests against the real exported getServerConfig — no
13
+ * source grep.
10
14
  */
11
15
 
12
- import test from "node:test";
16
+ import test, { before, after } from "node:test";
13
17
  import assert from "node:assert/strict";
14
- import { readFileSync } from "node:fs";
15
- import { dirname, join } from "node:path";
16
- import { fileURLToPath } from "node:url";
18
+ import { writeFileSync, unlinkSync, existsSync, mkdirSync, rmSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { mkdtempSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+
23
+ import { getServerConfig } from "../index.js";
24
+
25
+ // readConfigs() anchors to process.cwd() — run each test in a sandbox dir
26
+ // with a purpose-built .mcp.json so the extension reads our fixture, not
27
+ // whatever .mcp.json happens to live in the current working directory.
28
+ let sandboxDir: string;
29
+ let originalCwd: string;
30
+
31
+ before(() => {
32
+ originalCwd = process.cwd();
33
+ sandboxDir = mkdtempSync(join(tmpdir(), "mcp-name-spaces-"));
34
+ const mcpConfig = {
35
+ mcpServers: {
36
+ "Langgraph Code": {
37
+ command: "echo",
38
+ args: ["test"],
39
+ },
40
+ "other-server": {
41
+ url: "https://example.com",
42
+ },
43
+ },
44
+ };
45
+ writeFileSync(join(sandboxDir, ".mcp.json"), JSON.stringify(mcpConfig), "utf-8");
46
+ process.chdir(sandboxDir);
47
+ });
17
48
 
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
49
+ after(() => {
50
+ process.chdir(originalCwd);
51
+ try {
52
+ rmSync(sandboxDir, { recursive: true, force: true });
53
+ } catch {
54
+ // Best-effort cleanup
55
+ }
56
+ });
20
57
 
21
- const source = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");
58
+ test("#3029: getServerConfig finds exact match", () => {
59
+ const cfg = getServerConfig("Langgraph Code");
60
+ assert.ok(cfg, "exact name must resolve");
61
+ assert.equal(cfg?.name, "Langgraph Code");
62
+ });
22
63
 
23
64
  test("#3029: getServerConfig trims whitespace from input name", () => {
24
- assert.ok(
25
- source.includes(".trim()"),
26
- "getServerConfig should trim the input name before comparison",
27
- );
65
+ const cfg = getServerConfig(" Langgraph Code ");
66
+ assert.ok(cfg, "whitespace-padded name must resolve to the same server");
67
+ assert.equal(cfg?.name, "Langgraph Code");
28
68
  });
29
69
 
30
70
  test("#3029: getServerConfig performs case-insensitive matching", () => {
31
- assert.ok(
32
- source.includes(".toLowerCase()"),
33
- "getServerConfig should compare names case-insensitively",
34
- );
71
+ const cfg = getServerConfig("langgraph code");
72
+ assert.ok(cfg, "lower-cased name must resolve");
73
+ assert.equal(cfg?.name, "Langgraph Code");
74
+
75
+ const mixed = getServerConfig("LANGGRAPH CODE");
76
+ assert.ok(mixed, "upper-cased name must resolve");
77
+ assert.equal(mixed?.name, "Langgraph Code");
78
+ });
79
+
80
+ test("#3029: getServerConfig combines trim + case-insensitive", () => {
81
+ const cfg = getServerConfig(" LANGGRAPH code ");
82
+ assert.ok(cfg, "padded + mixed-case must resolve");
83
+ assert.equal(cfg?.name, "Langgraph Code");
35
84
  });
36
85
 
37
- test("#3029: getOrConnect normalizes name for connection cache lookup", () => {
38
- // The connections Map key must use the canonical (config) name, not the
39
- // raw user input, so that subsequent lookups hit the cache even when the
40
- // user's casing differs.
41
- const getOrConnectMatch = source.match(
42
- /async function getOrConnect\(name: string[\s\S]*?const existing = connections\.get\(/,
43
- );
44
- assert.ok(
45
- getOrConnectMatch,
46
- "getOrConnect function should exist",
47
- );
48
- // After the fix, getOrConnect should normalize the name via getServerConfig
49
- // or use config.name as the canonical cache key.
50
- assert.ok(
51
- source.includes("connections.get(config.name") ||
52
- source.includes("connections.set(config.name"),
53
- "getOrConnect should use config.name (canonical) as the connections cache key",
54
- );
86
+ test("#3029: getServerConfig returns undefined for unknown name", () => {
87
+ const cfg = getServerConfig("does-not-exist");
88
+ assert.equal(cfg, undefined);
55
89
  });
@@ -45,8 +45,12 @@ let providerRegistered = false;
45
45
  /**
46
46
  * Probe Ollama and register discovered models.
47
47
  * Safe to call multiple times — re-discovers and re-registers.
48
+ *
49
+ * Exported for tests (see ollama-auth-mode.test.ts, ollama-status-indicator.test.ts)
50
+ * so a fake HTTP endpoint can drive the registration/unregistration paths.
51
+ * Production callers always go through the session_start handler below.
48
52
  */
49
- async function probeAndRegister(pi: ExtensionAPI): Promise<boolean> {
53
+ export async function probeAndRegister(pi: ExtensionAPI): Promise<boolean> {
50
54
  const running = await client.isRunning();
51
55
  if (!running) {
52
56
  if (providerRegistered) {