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
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Unified Component Type Definitions
3
+ *
4
+ * Shared metadata for installable/discoverable skills and agents.
5
+ *
6
+ * Replaces the separate type systems in:
7
+ * - packages/pi-coding-agent/src/core/skills.ts (SkillFrontmatter, Skill)
8
+ * - src/resources/extensions/subagent/agents.ts (AgentConfig)
9
+ *
10
+ * Legacy skill and agent formats are supported via backward-compatible loading.
11
+ */
12
+
13
+ // ============================================================================
14
+ // Component Kind
15
+ // ============================================================================
16
+
17
+ /** All supported component types for the first component-system slice. */
18
+ export type ComponentKind = 'skill' | 'agent';
19
+
20
+ /** API version for component.yaml spec */
21
+ export type ComponentApiVersion = 'gsd/v1';
22
+
23
+ // ============================================================================
24
+ // Component Metadata
25
+ // ============================================================================
26
+
27
+ export interface ComponentAuthor {
28
+ name: string;
29
+ email?: string;
30
+ url?: string;
31
+ }
32
+
33
+ export interface ComponentMetadata {
34
+ /** Component name (lowercase a-z, 0-9, hyphens). Required. */
35
+ name: string;
36
+
37
+ /** Human-readable description. Required. */
38
+ description: string;
39
+
40
+ /** Namespace for plugin-sourced components (e.g., "my-plugin"). */
41
+ namespace?: string;
42
+
43
+ /** Semver version string. */
44
+ version?: string;
45
+
46
+ /** Author information. */
47
+ author?: ComponentAuthor;
48
+
49
+ /** Searchable tags. */
50
+ tags?: string[];
51
+
52
+ /** SPDX license identifier. */
53
+ license?: string;
54
+ }
55
+
56
+ // ============================================================================
57
+ // Skill Spec
58
+ // ============================================================================
59
+
60
+ export interface SkillSpec {
61
+ /** Path to the prompt content file (relative to component dir). */
62
+ prompt: string;
63
+
64
+ /** If true, skill is excluded from LLM system prompt (invoke-only). */
65
+ disableModelInvocation?: boolean;
66
+ }
67
+
68
+ // ============================================================================
69
+ // Agent Spec
70
+ // ============================================================================
71
+
72
+ export interface AgentToolConfig {
73
+ /** Tools the agent is allowed to use. If set, only these tools are available. */
74
+ allow?: string[];
75
+
76
+ /** Tools the agent is explicitly denied. Applied after allow. */
77
+ deny?: string[];
78
+ }
79
+
80
+ export interface AgentContextConfig {
81
+ /** Files to always include in the agent's context. */
82
+ alwaysInclude?: string[];
83
+
84
+ /** Whether to inject project context (project files, structure). */
85
+ injectProjectContext?: boolean;
86
+
87
+ /** Whether to inject current git status. */
88
+ injectGitStatus?: boolean;
89
+ }
90
+
91
+ export interface AgentOutputSchema {
92
+ type: 'object' | 'string';
93
+ properties?: Record<string, { type: string; items?: { type: string }; description?: string }>;
94
+ required?: string[];
95
+ }
96
+
97
+ export interface AgentSpec {
98
+ /** Path to the system prompt file (relative to component dir). */
99
+ systemPrompt: string;
100
+
101
+ /** Model override (e.g., "claude-sonnet-4-6"). */
102
+ model?: string;
103
+
104
+ /** Fallback models to try if primary fails. */
105
+ modelFallbacks?: string[];
106
+
107
+ /** Tool access configuration. */
108
+ tools?: AgentToolConfig | string[];
109
+
110
+ /** Maximum number of turns before the agent is stopped. */
111
+ maxTurns?: number;
112
+
113
+ /** Maximum tokens budget per invocation. */
114
+ maxTokens?: number;
115
+
116
+ /** Hard timeout in minutes. */
117
+ timeoutMinutes?: number;
118
+
119
+ /** Temperature override. */
120
+ temperature?: number;
121
+
122
+ /** Thinking level override. */
123
+ thinking?: 'off' | 'minimal' | 'standard' | 'full';
124
+
125
+ /** Output format preference. */
126
+ outputFormat?: 'text' | 'structured' | 'markdown';
127
+
128
+ /** Context injection configuration. */
129
+ context?: AgentContextConfig;
130
+
131
+ /** Isolation mode for execution. */
132
+ isolation?: 'none' | 'worktree';
133
+
134
+ /** Merge strategy when isolation is used. */
135
+ mergeStrategy?: 'patch' | 'squash' | 'manual';
136
+
137
+ /** Whether the agent accepts {previous} input from chain mode. */
138
+ acceptsInput?: boolean;
139
+
140
+ /** Structured output contract. */
141
+ outputSchema?: AgentOutputSchema;
142
+
143
+ /** Name of another agent to inherit configuration from. */
144
+ extends?: string;
145
+ }
146
+
147
+ // ============================================================================
148
+ // Dependency & Compatibility
149
+ // ============================================================================
150
+
151
+ export interface ComponentDependencies {
152
+ /** Required skills that must be installed. */
153
+ skills?: string[];
154
+
155
+ /** Required agents that must be installed. */
156
+ agents?: string[];
157
+
158
+ /** Required MCP servers. */
159
+ mcpServers?: string[];
160
+ }
161
+
162
+ export interface ComponentCompatibility {
163
+ /** Minimum GSD version (semver range). */
164
+ gsd?: string;
165
+
166
+ /** Minimum Node.js version (semver range). */
167
+ node?: string;
168
+ }
169
+
170
+ // ============================================================================
171
+ // Agent Routing
172
+ // ============================================================================
173
+
174
+ export interface AgentRoutingRule {
175
+ /** Natural-language condition for when this agent should be used. */
176
+ when: string;
177
+
178
+ /** Confidence level for this rule. */
179
+ confidence?: 'low' | 'medium' | 'high';
180
+ }
181
+
182
+ export type ComponentSpec = SkillSpec | AgentSpec;
183
+
184
+ // ============================================================================
185
+ // Full Component Definition
186
+ // ============================================================================
187
+
188
+ /**
189
+ * Complete component.yaml definition.
190
+ * This is the parsed representation of a component.yaml file.
191
+ */
192
+ export interface ComponentDefinition {
193
+ apiVersion: ComponentApiVersion;
194
+ kind: ComponentKind;
195
+ metadata: ComponentMetadata;
196
+ spec: ComponentSpec;
197
+
198
+ /** Dependencies on other components. */
199
+ requires?: ComponentDependencies;
200
+
201
+ /** Version compatibility constraints. */
202
+ compatibility?: ComponentCompatibility;
203
+
204
+ /** Agent routing rules (only for kind: agent). */
205
+ routing?: AgentRoutingRule[];
206
+ }
207
+
208
+ // ============================================================================
209
+ // Resolved Component (Runtime)
210
+ // ============================================================================
211
+
212
+ /** Source of a loaded component */
213
+ export type ComponentSource = 'user' | 'project' | 'builtin' | 'plugin' | 'path';
214
+
215
+ /**
216
+ * A fully resolved component at runtime.
217
+ * Combines the definition with resolution metadata.
218
+ */
219
+ export interface Component {
220
+ /** Unique identifier: `${namespace}:${name}` or bare `name`. */
221
+ id: string;
222
+
223
+ /** Component kind. */
224
+ kind: ComponentKind;
225
+
226
+ /** Component metadata. */
227
+ metadata: ComponentMetadata;
228
+
229
+ /** Kind-specific specification. */
230
+ spec: ComponentSpec;
231
+
232
+ /** Dependencies. */
233
+ requires?: ComponentDependencies;
234
+
235
+ /** Compatibility constraints. */
236
+ compatibility?: ComponentCompatibility;
237
+
238
+ /** Routing rules (agents only). */
239
+ routing?: AgentRoutingRule[];
240
+
241
+ /** Absolute path to the component directory. */
242
+ dirPath: string;
243
+
244
+ /** Absolute path to the definition file (component.yaml or SKILL.md or agent.md). */
245
+ filePath: string;
246
+
247
+ /** How this component was discovered. */
248
+ source: ComponentSource;
249
+
250
+ /** Format of the original definition. */
251
+ format: 'component-yaml' | 'skill-md' | 'agent-md';
252
+
253
+ /** Whether the component is currently enabled. */
254
+ enabled: boolean;
255
+ }
256
+
257
+ // ============================================================================
258
+ // Registry Types
259
+ // ============================================================================
260
+
261
+ export interface ComponentFilter {
262
+ /** Filter by kind. */
263
+ kind?: ComponentKind | ComponentKind[];
264
+
265
+ /** Filter by source. */
266
+ source?: ComponentSource | ComponentSource[];
267
+
268
+ /** Filter by namespace. */
269
+ namespace?: string;
270
+
271
+ /** Filter by tags (any match). */
272
+ tags?: string[];
273
+
274
+ /** Text search across name and description. */
275
+ search?: string;
276
+
277
+ /** Only enabled components. Default: true. */
278
+ enabledOnly?: boolean;
279
+ }
280
+
281
+ export interface ComponentDiagnostic {
282
+ type: 'warning' | 'error' | 'collision';
283
+ message: string;
284
+ componentId?: string;
285
+ path?: string;
286
+ collision?: {
287
+ name: string;
288
+ winnerPath: string;
289
+ loserPath: string;
290
+ winnerSource?: string;
291
+ loserSource?: string;
292
+ };
293
+ }
294
+
295
+ // ============================================================================
296
+ // Validation
297
+ // ============================================================================
298
+
299
+ /** Max name length per spec */
300
+ export const MAX_NAME_LENGTH = 64;
301
+
302
+ /** Max description length per spec */
303
+ export const MAX_DESCRIPTION_LENGTH = 1024;
304
+
305
+ /** Valid name pattern: lowercase a-z, 0-9, hyphens, no leading/trailing/consecutive hyphens */
306
+ export const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
307
+
308
+ /**
309
+ * Validate a component name.
310
+ * @returns Array of error messages (empty if valid).
311
+ */
312
+ export function validateComponentName(name: string): string[] {
313
+ const errors: string[] = [];
314
+
315
+ if (!name || name.trim() === '') {
316
+ errors.push('name is required');
317
+ return errors;
318
+ }
319
+
320
+ if (name.length > MAX_NAME_LENGTH) {
321
+ errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);
322
+ }
323
+
324
+ if (name.includes('--')) {
325
+ errors.push('name must not contain consecutive hyphens');
326
+ }
327
+
328
+ if (!NAME_PATTERN.test(name)) {
329
+ if (/[A-Z]/.test(name)) {
330
+ errors.push('name must be lowercase');
331
+ } else if (name.startsWith('-') || name.endsWith('-')) {
332
+ errors.push('name must not start or end with a hyphen');
333
+ } else if (!name.includes('--')) {
334
+ errors.push('name must contain only lowercase a-z, 0-9, and hyphens');
335
+ }
336
+ }
337
+
338
+ return errors;
339
+ }
340
+
341
+ /**
342
+ * Validate a component description.
343
+ * @returns Array of error messages (empty if valid).
344
+ */
345
+ export function validateComponentDescription(description: string | undefined): string[] {
346
+ const errors: string[] = [];
347
+
348
+ if (!description || description.trim() === '') {
349
+ errors.push('description is required');
350
+ } else if (description.length > MAX_DESCRIPTION_LENGTH) {
351
+ errors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);
352
+ }
353
+
354
+ return errors;
355
+ }
356
+
357
+ /**
358
+ * Compute the canonical ID for a component.
359
+ */
360
+ export function computeComponentId(name: string, namespace?: string): string {
361
+ return namespace ? `${namespace}:${name}` : name;
362
+ }
@@ -211,7 +211,13 @@ export function queryProject(): string | null {
211
211
 
212
212
  /**
213
213
  * Filter KNOWLEDGE.md sections by keyword matching.
214
- * Uses H2 sections, matches keywords case-insensitively against:
214
+ *
215
+ * Structure-adaptive (issue #4719): files that organise entries as H3 items
216
+ * under one or more H2 topics are filtered at H3 granularity. Files with only
217
+ * H2 topic headers (no H3) fall back to H2-level filtering for backwards
218
+ * compatibility.
219
+ *
220
+ * Matches keywords case-insensitively against:
215
221
  * 1. Section header text
216
222
  * 2. First paragraph of section content (up to first blank line or next heading)
217
223
  *
@@ -220,7 +226,7 @@ export function queryProject(): string | null {
220
226
  *
221
227
  * @param content - Full KNOWLEDGE.md content
222
228
  * @param keywords - Keywords to match (case-insensitive)
223
- * @returns Concatenated matching sections with H2 headers, or empty string
229
+ * @returns Concatenated matching sections with their original heading prefix, or empty string
224
230
  */
225
231
  export async function queryKnowledge(content: string, keywords: string[]): Promise<string> {
226
232
  if (!content || keywords.length === 0) return '';
@@ -228,11 +234,23 @@ export async function queryKnowledge(content: string, keywords: string[]): Promi
228
234
  // Lazy import to avoid circular dependency
229
235
  const { extractAllSections } = await import('./files.js');
230
236
 
231
- const sections = extractAllSections(content, 2);
237
+ // Prefer H3 granularity when available; fall back to H2 for H2-only files.
238
+ // This prevents single-H2-with-many-H3 layouts from returning the entire
239
+ // file on a keyword match against the H2 header or its first paragraph.
240
+ const h3Sections = extractAllSections(content, 3);
241
+ const useH3 = h3Sections.size > 0;
242
+ const sections = useH3 ? h3Sections : extractAllSections(content, 2);
232
243
  if (sections.size === 0) return '';
244
+ const prefix = useH3 ? '###' : '##';
233
245
 
234
- // Normalize keywords for case-insensitive matching
235
- const normalizedKeywords = keywords.map(k => k.toLowerCase());
246
+ // Trim, lowercase, drop empties, and de-dupe so callers can pass raw
247
+ // user-provided strings without risking empty-string / whitespace matches.
248
+ const normalizedKeywords = [...new Set(
249
+ keywords
250
+ .map(k => k.trim().toLowerCase())
251
+ .filter(k => k.length > 0),
252
+ )];
253
+ if (normalizedKeywords.length === 0) return '';
236
254
 
237
255
  const matchingSections: string[] = [];
238
256
 
@@ -240,16 +258,15 @@ export async function queryKnowledge(content: string, keywords: string[]): Promi
240
258
  // Extract first paragraph: everything up to first blank line or next heading
241
259
  const firstParagraph = body.split(/\n\s*\n|\n#/)[0] || '';
242
260
 
243
- // Check if any keyword matches header or first paragraph
244
261
  const headerLower = header.toLowerCase();
245
262
  const paragraphLower = firstParagraph.toLowerCase();
246
263
 
247
264
  const matches = normalizedKeywords.some(kw =>
248
- headerLower.includes(kw) || paragraphLower.includes(kw)
265
+ headerLower.includes(kw) || paragraphLower.includes(kw),
249
266
  );
250
267
 
251
268
  if (matches) {
252
- matchingSections.push(`## ${header}\n\n${body}`);
269
+ matchingSections.push(`${prefix} ${header}\n\n${body}`);
253
270
  }
254
271
  }
255
272
 
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { existsSync, openSync, readSync, closeSync, readdirSync, readFileSync, statSync } from "node:fs";
10
- import { join } from "node:path";
10
+ import { dirname, join, parse as parsePath } from "node:path";
11
11
  import { homedir } from "node:os";
12
12
  import { gsdRoot } from "./paths.js";
13
13
 
@@ -1120,6 +1120,63 @@ function resolveVersionCatalogAccessors(
1120
1120
  return accessors;
1121
1121
  }
1122
1122
 
1123
+ /**
1124
+ * Walk ancestor directories of `startDir` looking for any file in
1125
+ * `PROJECT_FILES`. Stops at the filesystem root or at a `.git` boundary
1126
+ * (so ancestors above a git repo root — e.g. `$HOME` or `/usr/local` —
1127
+ * can't trigger false positives). Returns true if an ancestor contains
1128
+ * one of the project markers.
1129
+ *
1130
+ * Used by the worktree health check (#2347) to avoid warning about
1131
+ * monorepos where package.json / Cargo.toml / etc. live in a parent
1132
+ * directory rather than in the worktree's own checkout.
1133
+ *
1134
+ * `existsFn` is injectable so this remains deterministically testable
1135
+ * without touching the real filesystem; defaults to `fs.existsSync`.
1136
+ */
1137
+ export function hasProjectFileInAncestor(
1138
+ startDir: string,
1139
+ existsFn: (p: string) => boolean = existsSync,
1140
+ ): boolean {
1141
+ let checkDir = dirname(startDir);
1142
+ const { root } = parsePath(checkDir);
1143
+ while (checkDir !== root) {
1144
+ if (PROJECT_FILES.some((f) => existsFn(join(checkDir, f)))) {
1145
+ return true;
1146
+ }
1147
+ // Stop at git repository boundary — ancestors above the repo root
1148
+ // may contain unrelated project files. Check AFTER project-file scan
1149
+ // so a repo root containing both .git and a marker is still recognized.
1150
+ if (existsFn(join(checkDir, ".git"))) return false;
1151
+ checkDir = dirname(checkDir);
1152
+ }
1153
+ return false;
1154
+ }
1155
+
1156
+ /**
1157
+ * Check whether a project's `.gsd/` directory contains the bootstrap artifacts
1158
+ * (`PREFERENCES.md` or `milestones/`) that indicate a completed init run.
1159
+ *
1160
+ * A zombie `.gsd/` state — symlink exists but neither artifact is present —
1161
+ * must be treated as "needs init wizard". The previous guard checked only
1162
+ * `existsSync(gsdRoot(basePath))`, which accepted zombie states and skipped
1163
+ * the wizard (#2942).
1164
+ *
1165
+ * `existsFn` is injectable so tests can run deterministically; defaults to
1166
+ * `fs.existsSync`.
1167
+ */
1168
+ export function hasGsdBootstrapArtifacts(
1169
+ gsdPath: string,
1170
+ existsFn: (p: string) => boolean = existsSync,
1171
+ ): boolean {
1172
+ return (
1173
+ existsFn(gsdPath) &&
1174
+ (existsFn(join(gsdPath, "PREFERENCES.md")) ||
1175
+ existsFn(join(gsdPath, "preferences.md")) ||
1176
+ existsFn(join(gsdPath, "milestones")))
1177
+ );
1178
+ }
1179
+
1123
1180
  export function scanProjectFiles(basePath: string): string[] {
1124
1181
  const files: string[] = [];
1125
1182
  const queue: Array<{ path: string; depth: number }> = [{ path: basePath, depth: 0 }];
@@ -159,7 +159,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
159
159
 
160
160
  - `phases`: fine-grained control over which phases run. Usually set by `token_profile`, but can be overridden. Keys:
161
161
  - `skip_research`: boolean — skip milestone-level research. Default: `false`.
162
- - `reassess_after_slice`: boolean — run roadmap reassessment after each completed slice. Default: `true`.
162
+ - `reassess_after_slice`: boolean — run a dedicated roadmap-reassessment unit after each completed slice. Default: `false` (per ADR-003 §4). The plan-slice agent for the next slice performs JIT reassessment via a prompt preamble at zero additional token cost; a dedicated reassess session is opt-in. Set to `true` (e.g. via the `burn-max` profile) if you want the explicit session.
163
163
  - `skip_reassess`: boolean — force-disable roadmap reassessment even if `reassess_after_slice` is enabled. Default: `false`.
164
164
  - `skip_slice_research`: boolean — skip per-slice research. Default: `false`.
165
165
 
@@ -35,11 +35,12 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
35
35
  import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
36
36
  import { showNextAction } from "../shared/tui.js";
37
37
  import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
38
+ import { summarizeWorktreeTelemetry, percentile, type WorktreeTelemetrySummary } from "./worktree-telemetry.js";
38
39
 
39
40
  // ─── Types ────────────────────────────────────────────────────────────────────
40
41
 
41
42
  export interface ForensicAnomaly {
42
- type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace" | "journal-stuck" | "journal-guard-block" | "journal-rapid-iterations" | "journal-worktree-failure";
43
+ type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace" | "journal-stuck" | "journal-guard-block" | "journal-rapid-iterations" | "journal-worktree-failure" | "worktree-orphan" | "worktree-unmerged-exit";
43
44
  severity: "info" | "warning" | "error";
44
45
  unitType?: string;
45
46
  unitId?: string;
@@ -113,6 +114,8 @@ interface ForensicReport {
113
114
  recentUnits: { type: string; id: string; cost: number; duration: number; model: string; finishedAt: number }[];
114
115
  journalSummary: JournalSummary | null;
115
116
  activityLogMeta: ActivityLogMeta | null;
117
+ /** #4764 — worktree lifespan / divergence telemetry aggregates. */
118
+ worktreeTelemetry: WorktreeTelemetrySummary | null;
116
119
  }
117
120
 
118
121
  // ─── Duplicate Detection ──────────────────────────────────────────────────────
@@ -337,6 +340,16 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
337
340
  detectCrash(crashLock, anomalies);
338
341
  detectDoctorIssues(doctorIssues, anomalies);
339
342
  detectErrorTraces(unitTraces, anomalies);
343
+
344
+ // 11b. #4764 — worktree lifecycle telemetry
345
+ let worktreeTelemetry: WorktreeTelemetrySummary | null = null;
346
+ try {
347
+ worktreeTelemetry = summarizeWorktreeTelemetry(basePath);
348
+ detectWorktreeOrphans(worktreeTelemetry, anomalies);
349
+ } catch {
350
+ // Telemetry is best-effort — do not let an aggregator failure block the
351
+ // rest of the forensic report.
352
+ }
340
353
  detectJournalAnomalies(journalSummary, anomalies);
341
354
 
342
355
  return {
@@ -356,6 +369,7 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
356
369
  recentUnits,
357
370
  journalSummary,
358
371
  activityLogMeta,
372
+ worktreeTelemetry,
359
373
  };
360
374
  }
361
375
 
@@ -783,6 +797,51 @@ function detectMissingArtifacts(completedKeys: string[], basePath: string, activ
783
797
  }
784
798
  }
785
799
 
800
+ /**
801
+ * #4764 — surface worktree lifecycle and orphan signals in the forensic report.
802
+ *
803
+ * Consumes only the aggregated summary (not raw journal events) to respect
804
+ * the forensics memory-bloat guard in forensics-journal.test.ts — per-event
805
+ * detail stays in the journal itself where the LLM can query it on demand.
806
+ */
807
+ function detectWorktreeOrphans(
808
+ summary: WorktreeTelemetrySummary,
809
+ anomalies: ForensicAnomaly[],
810
+ ): void {
811
+ // 1. Orphan aggregate — severity depends on reason. In-progress orphans are
812
+ // the #4761 consumer-side signal (live work sitting on an unmerged branch).
813
+ for (const [reason, count] of Object.entries(summary.orphansByReason)) {
814
+ if (count <= 0) continue;
815
+ const severity: ForensicAnomaly["severity"] =
816
+ reason === "in-progress-unmerged" ? "warning" : "info";
817
+ anomalies.push({
818
+ type: "worktree-orphan",
819
+ severity,
820
+ summary: `${count} worktree orphan(s) detected (${reason})`,
821
+ details:
822
+ reason === "in-progress-unmerged"
823
+ ? "Auto-mode exited without completing a milestone; live work sits on an unmerged milestone branch. Run `/gsd auto` to resume, or merge manually."
824
+ : reason === "complete-unmerged"
825
+ ? "A completed milestone's branch was never merged back to main. Run `/gsd health --fix` to resolve."
826
+ : `Reason: ${reason}.`,
827
+ });
828
+ }
829
+
830
+ // 2. Auto-exit producer signal — #4761's upstream cause.
831
+ if (summary.exitsWithUnmergedWork > 0) {
832
+ const reasonBreakdown = Object.entries(summary.exitsByReason)
833
+ .filter(([, n]) => n > 0)
834
+ .map(([r, n]) => `${r}=${n}`)
835
+ .join(", ");
836
+ anomalies.push({
837
+ type: "worktree-unmerged-exit",
838
+ severity: "warning",
839
+ summary: `${summary.exitsWithUnmergedWork} auto-exit(s) left milestone work unmerged`,
840
+ details: `Exit reasons: ${reasonBreakdown || "(none)"} · Producer-side signal for #4761-class orphans. Inspect .gsd/journal/*.jsonl with eventType:"auto-exit" for per-exit detail.`,
841
+ });
842
+ }
843
+ }
844
+
786
845
  function detectCrash(crashLock: LockData | null, anomalies: ForensicAnomaly[]): void {
787
846
  if (!crashLock) return;
788
847
  if (isLockProcessAlive(crashLock)) return; // Process still running, not a crash
@@ -972,6 +1031,40 @@ function saveForensicReport(basePath: string, report: ForensicReport, problemDes
972
1031
  sections.push(``);
973
1032
  }
974
1033
 
1034
+ // #4764 — Worktree telemetry summary
1035
+ if (report.worktreeTelemetry) {
1036
+ const t = report.worktreeTelemetry;
1037
+ const p50 = percentile(t.mergeDurationsMs, 0.5);
1038
+ const p95 = percentile(t.mergeDurationsMs, 0.95);
1039
+ sections.push(`## Worktree Telemetry`, ``);
1040
+ sections.push(`- Worktrees created: ${t.worktreesCreated}`);
1041
+ sections.push(`- Worktrees merged: ${t.worktreesMerged}`);
1042
+ sections.push(`- Orphans detected: ${t.orphansDetected}`);
1043
+ if (t.orphansDetected > 0) {
1044
+ const breakdown = Object.entries(t.orphansByReason)
1045
+ .map(([r, n]) => `${r}=${n}`).join(", ");
1046
+ sections.push(` - By reason: ${breakdown}`);
1047
+ }
1048
+ sections.push(`- Merge conflicts: ${t.mergeConflicts}`);
1049
+ if (t.mergeDurationsMs.length > 0) {
1050
+ sections.push(`- Merge duration p50 / p95: ${p50 ?? "-"} / ${p95 ?? "-"} ms (n=${t.mergeDurationsMs.length})`);
1051
+ }
1052
+ sections.push(`- Auto-exits leaving unmerged work: ${t.exitsWithUnmergedWork}`);
1053
+ if (Object.keys(t.exitsByReason).length > 0) {
1054
+ const breakdown = Object.entries(t.exitsByReason)
1055
+ .sort((a, b) => b[1] - a[1])
1056
+ .map(([r, n]) => `${r}=${n}`).join(", ");
1057
+ sections.push(` - Exit reasons: ${breakdown}`);
1058
+ }
1059
+ sections.push(`- Canonical-root redirects (#4761 fix fired): ${t.canonicalRedirects}`);
1060
+ // #4765 slice-cadence counters
1061
+ if (t.slicesMerged + t.sliceMergeConflicts + t.milestoneResquashes > 0) {
1062
+ sections.push(`- Slices merged: ${t.slicesMerged} · Slice merge conflicts: ${t.sliceMergeConflicts}`);
1063
+ sections.push(`- Milestone re-squashes: ${t.milestoneResquashes}`);
1064
+ }
1065
+ sections.push(``);
1066
+ }
1067
+
975
1068
  // Journal summary
976
1069
  if (report.journalSummary) {
977
1070
  const js = report.journalSummary;
@@ -1117,6 +1210,30 @@ function formatReportForPrompt(report: ForensicReport): string {
1117
1210
  sections.push("");
1118
1211
  }
1119
1212
 
1213
+ // #4764 — worktree telemetry (compact prompt form)
1214
+ if (report.worktreeTelemetry) {
1215
+ const t = report.worktreeTelemetry;
1216
+ const hasSignal =
1217
+ t.worktreesCreated + t.worktreesMerged + t.orphansDetected +
1218
+ t.exitsWithUnmergedWork + t.canonicalRedirects +
1219
+ t.slicesMerged + t.milestoneResquashes > 0;
1220
+ if (hasSignal) {
1221
+ sections.push("### Worktree Telemetry");
1222
+ sections.push(`- Created: ${t.worktreesCreated} · Merged: ${t.worktreesMerged} · Conflicts: ${t.mergeConflicts}`);
1223
+ sections.push(`- Orphans: ${t.orphansDetected} · Unmerged exits: ${t.exitsWithUnmergedWork} · Redirects (#4761): ${t.canonicalRedirects}`);
1224
+ if (t.orphansDetected > 0) {
1225
+ const breakdown = Object.entries(t.orphansByReason)
1226
+ .map(([r, n]) => `${r}=${n}`).join(", ");
1227
+ sections.push(`- Orphan reasons: ${breakdown}`);
1228
+ }
1229
+ // #4765 — slice-cadence counters (only shown when the feature was exercised)
1230
+ if (t.slicesMerged + t.sliceMergeConflicts + t.milestoneResquashes > 0) {
1231
+ sections.push(`- Slices merged: ${t.slicesMerged} · Slice conflicts: ${t.sliceMergeConflicts} · Re-squashes: ${t.milestoneResquashes}`);
1232
+ }
1233
+ sections.push("");
1234
+ }
1235
+ }
1236
+
1120
1237
  // Activity log metadata
1121
1238
  if (report.activityLogMeta) {
1122
1239
  const meta = report.activityLogMeta;