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
@@ -19,12 +19,13 @@ import { invalidateAllCaches } from "./cache.js";
19
19
  import { writeLock, clearLock } from "./crash-recovery.js";
20
20
  import { acquireSessionLock, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
21
21
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
22
- import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeCheckoutBranch, nativeBranchList, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, } from "./native-git-bridge.js";
22
+ import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeCheckoutBranch, nativeBranchList, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, nativeCommitCountBetween, } from "./native-git-bridge.js";
23
23
  import { GitServiceImpl } from "./git-service.js";
24
24
  import { captureIntegrationBranch, detectWorktreeName, setActiveMilestoneId, } from "./worktree.js";
25
25
  import { getAutoWorktreePath } from "./auto-worktree.js";
26
26
  import { readResourceVersion, cleanStaleRuntimeUnits } from "./auto-worktree.js";
27
27
  import { worktreePath as getWorktreeDir, isInsideWorktreesDir } from "./worktree-manager.js";
28
+ import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
28
29
  import { initMetrics } from "./metrics.js";
29
30
  import { initRoutingHistory } from "./routing-history.js";
30
31
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
@@ -63,25 +64,15 @@ export async function openProjectDbIfPresent(basePath) {
63
64
  logWarning("engine", `gsd-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`);
64
65
  }
65
66
  }
66
- /**
67
- * Audit for orphaned milestone branches at bootstrap.
68
- *
69
- * After a milestone completes, the teardown step (merge branch → main,
70
- * delete branch, remove worktree) runs as a post-completion engine step.
71
- * If the session ends between completion and teardown, the branch and
72
- * worktree are orphaned — the DB says "complete" so auto-mode won't
73
- * re-enter the milestone, and the teardown is never retried.
74
- *
75
- * This audit runs on every fresh bootstrap to catch that gap:
76
- * 1. Lists all local `milestone/*` branches.
77
- * 2. For each, checks if the milestone's DB status is "complete".
78
- * 3. If the branch is already merged into main → deletes the branch
79
- * and cleans up any orphaned worktree directory (safe, no data loss).
80
- * 4. If the branch is NOT merged → preserves it and warns the user
81
- * so they can merge manually (data safety first).
82
- *
83
- * Returns a summary of actions taken for the caller to surface via notify.
84
- */
67
+ export function decideSurvivorAction(hasSurvivorBranch, phase) {
68
+ if (!hasSurvivorBranch)
69
+ return "none";
70
+ if (phase === "needs-discussion")
71
+ return "discuss";
72
+ if (phase === "complete")
73
+ return "finalize";
74
+ return "none";
75
+ }
85
76
  export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
86
77
  const recovered = [];
87
78
  const warnings = [];
@@ -120,10 +111,58 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
120
111
  for (const branch of milestoneBranches) {
121
112
  const milestoneId = branch.replace(/^milestone\//, "");
122
113
  const milestone = getMilestone(milestoneId);
123
- // Only audit completed milestones
124
- if (!milestone || milestone.status !== "complete")
114
+ if (!milestone)
125
115
  continue;
126
116
  const isMerged = mergedBranches.has(branch);
117
+ // #4762 — in-progress milestone branch with unmerged commits ahead of
118
+ // main. This is the pre-completion orphan case: auto-mode exited without
119
+ // completing the milestone (pause, stop, crash, merge error, blocker) and
120
+ // work is stranded on the branch or in the worktree. Data safety first:
121
+ // we never delete or touch; we just surface a warning so the user knows
122
+ // where to look.
123
+ //
124
+ // Gate on isClosedStatus so we only warn about genuinely open milestones.
125
+ // Parked/other closed statuses go through the legacy complete/unmerged
126
+ // path below where appropriate.
127
+ if (!isClosedStatus(milestone.status)) {
128
+ if (isMerged)
129
+ continue; // nothing to recover
130
+ let commitsAhead = 0;
131
+ try {
132
+ commitsAhead = nativeCommitCountBetween(basePath, mainBranch, branch);
133
+ }
134
+ catch {
135
+ // Rev-walk failure — skip rather than noise
136
+ continue;
137
+ }
138
+ if (commitsAhead === 0)
139
+ continue;
140
+ const wtDir = getWorktreeDir(basePath, milestoneId);
141
+ const wtDirExists = existsSync(wtDir);
142
+ const wtSuffix = wtDirExists
143
+ ? ` Worktree directory at .gsd/worktrees/${milestoneId}/ holds the live work.`
144
+ : "";
145
+ warnings.push(`Branch ${branch} has ${commitsAhead} commit(s) ahead of ${mainBranch} for in-progress milestone ${milestoneId}.` +
146
+ wtSuffix +
147
+ ` Run \`/gsd auto\` to resume, or merge manually if abandoning.`);
148
+ // #4764 telemetry
149
+ try {
150
+ emitWorktreeOrphaned(basePath, milestoneId, {
151
+ reason: "in-progress-unmerged",
152
+ commitsAhead,
153
+ worktreeDirExists: wtDirExists,
154
+ });
155
+ }
156
+ catch (err) {
157
+ logWarning("engine", `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`);
158
+ }
159
+ continue;
160
+ }
161
+ // Only the "complete" status participates in the merged/unmerged cleanup
162
+ // paths below — other closed statuses (parked, etc.) are intentionally
163
+ // left alone.
164
+ if (milestone.status !== "complete")
165
+ continue;
127
166
  if (isMerged) {
128
167
  // Branch is merged — safe to delete branch and clean up worktree dir
129
168
  try {
@@ -170,6 +209,16 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) {
170
209
  // Branch is NOT merged — preserve for safety, warn the user
171
210
  warnings.push(`Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` +
172
211
  `This may contain unmerged work. Merge manually or run \`/gsd health --fix\` to resolve.`);
212
+ // #4764 telemetry
213
+ try {
214
+ emitWorktreeOrphaned(basePath, milestoneId, {
215
+ reason: "complete-unmerged",
216
+ worktreeDirExists: existsSync(getWorktreeDir(basePath, milestoneId)),
217
+ });
218
+ }
219
+ catch (err) {
220
+ logWarning("engine", `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`);
221
+ }
173
222
  }
174
223
  }
175
224
  return { recovered, warnings };
@@ -391,7 +440,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
391
440
  // The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md.
392
441
  // Route to the interactive discussion handler instead of falling through to
393
442
  // auto-mode, which would immediately stop with "needs discussion".
394
- if (hasSurvivorBranch && state.phase === "needs-discussion") {
443
+ if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss") {
395
444
  const { showSmartEntry } = await import("./guided-flow.js");
396
445
  await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
397
446
  invalidateAllCaches();
@@ -411,7 +460,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
411
460
  // The milestone artifacts were written but finalization (merge, worktree
412
461
  // cleanup) never ran. Run mergeAndExit to finalize, then re-derive state
413
462
  // so the normal "all milestones complete" or "next milestone" path runs.
414
- if (hasSurvivorBranch && state.phase === "complete") {
463
+ // Re-evaluate via the helper the discuss branch above may have cleared
464
+ // hasSurvivorBranch after a successful promotion.
465
+ if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "finalize") {
415
466
  const mid = state.activeMilestone.id;
416
467
  ctx.ui.notify(`Milestone ${mid} is complete but branch/worktree was not finalized. Running merge now.`, "info");
417
468
  const resolver = buildResolver();
@@ -528,6 +528,40 @@ export async function stopAuto(ctx, pi, reason) {
528
528
  return;
529
529
  const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
530
530
  const reasonSuffix = reason ? ` — ${reason}` : "";
531
+ // #4764 — telemetry: record the exit reason and whether the current milestone
532
+ // was merged before we entered stopAuto. This is the producer-side signal for
533
+ // the #4761 orphan class: milestoneMerged=false + currentMilestoneId present
534
+ // is exactly the pattern that strands work.
535
+ try {
536
+ const { emitAutoExit } = await import("./worktree-telemetry.js");
537
+ // Normalize the free-form reason to a closed set so the telemetry
538
+ // aggregator buckets stably. Raw detail is preserved in the phases.ts
539
+ // notification and the notify'd error string.
540
+ const rawReason = reason ?? "stop";
541
+ const normalizedReason = rawReason.startsWith("Blocked:")
542
+ ? "blocked"
543
+ : rawReason.startsWith("Merge conflict")
544
+ ? "merge-conflict"
545
+ : rawReason.startsWith("Merge error") || rawReason.startsWith("Merge failed")
546
+ ? "merge-failed"
547
+ : rawReason.startsWith("slice-merge-conflict")
548
+ ? "slice-merge-conflict"
549
+ : rawReason === "All milestones complete"
550
+ ? "all-complete"
551
+ : rawReason === "No active milestone"
552
+ ? "no-active-milestone"
553
+ : rawReason === "stop" || rawReason === "pause"
554
+ ? rawReason
555
+ : "other";
556
+ emitAutoExit(s.originalBasePath || s.basePath, {
557
+ reason: normalizedReason,
558
+ milestoneId: s.currentMilestoneId ?? undefined,
559
+ milestoneMerged: s.milestoneMergedInPhases === true,
560
+ });
561
+ }
562
+ catch (err) {
563
+ logWarning("engine", `auto-exit telemetry failed: ${err instanceof Error ? err.message : String(err)}`);
564
+ }
531
565
  try {
532
566
  // ── Step 1: Timers and locks ──
533
567
  try {
@@ -12,7 +12,15 @@ import { classifyError, createRetryState, resetRetryState, isTransient, } from "
12
12
  import { blockModel, isModelBlocked } from "../blocked-models.js";
13
13
  const retryState = createRetryState();
14
14
  const MAX_NETWORK_RETRIES = 2;
15
- const MAX_TRANSIENT_AUTO_RESUMES = 8;
15
+ /**
16
+ * Cap on auto-resume attempts for sustained transient-provider errors.
17
+ *
18
+ * Exported so tests assert against the shared constant instead of
19
+ * regex-scraping the source literal (see #4837). Raising this value to
20
+ * handle longer provider overloads should update the single constant; the
21
+ * test in provider-errors.test.ts consumes it directly.
22
+ */
23
+ export const MAX_TRANSIENT_AUTO_RESUMES = 8;
16
24
  /**
17
25
  * Reset the module-level retry state so a resumed auto-session starts fresh.
18
26
  * Called by provider-error-resume.ts before startAuto() — without this, the
@@ -1,6 +1,12 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
3
+ /**
4
+ * Regex matching milestone CONTEXT.md file names in both legacy M001
5
+ * and unique M001-abc123 formats. Exported so regex-hardening tests
6
+ * can exercise the real pattern rather than a drift-prone inline
7
+ * re-implementation (see #4835).
8
+ */
9
+ export const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
4
10
  const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
5
11
  const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
6
12
  /**
@@ -0,0 +1,447 @@
1
+ /**
2
+ * Component Loader
3
+ *
4
+ * Multi-format loader that handles:
5
+ * 1. New format: component.yaml + SKILL.md/AGENT.md
6
+ * 2. Legacy skill format: SKILL.md with YAML frontmatter
7
+ * 3. Legacy agent format: .md with YAML frontmatter (name, description, tools, model)
8
+ *
9
+ * Auto-detects format by checking for component.yaml first, then falling back
10
+ * to legacy formats based on file naming conventions.
11
+ */
12
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
13
+ import { basename, dirname, join } from 'node:path';
14
+ import { parse as parseYaml } from 'yaml';
15
+ import { parseFrontmatter } from '@gsd/pi-coding-agent';
16
+ import { validateComponentName, validateComponentDescription, computeComponentId, } from './component-types.js';
17
+ const SUPPORTED_COMPONENT_KINDS = ['skill', 'agent'];
18
+ const SUPPORTED_API_VERSIONS = ['gsd/v1'];
19
+ // ============================================================================
20
+ // Single Component Loading
21
+ // ============================================================================
22
+ /**
23
+ * Load a component from a directory.
24
+ * Checks for component.yaml first, then legacy formats.
25
+ */
26
+ export function loadComponentFromDir(dir, source) {
27
+ const diagnostics = [];
28
+ // Try new format first: component.yaml
29
+ const componentYamlPath = join(dir, 'component.yaml');
30
+ if (existsSync(componentYamlPath)) {
31
+ return loadFromComponentYaml(componentYamlPath, dir, source);
32
+ }
33
+ // Try legacy skill format: SKILL.md
34
+ const skillMdPath = join(dir, 'SKILL.md');
35
+ if (existsSync(skillMdPath)) {
36
+ return loadFromLegacySkill(skillMdPath, dir, source);
37
+ }
38
+ // No recognized component format found
39
+ return { component: null, diagnostics };
40
+ }
41
+ /**
42
+ * Load a component from a legacy agent .md file (flat file, not directory).
43
+ */
44
+ export function loadComponentFromAgentFile(filePath, source) {
45
+ return loadFromLegacyAgent(filePath, source);
46
+ }
47
+ // ============================================================================
48
+ // New Format: component.yaml
49
+ // ============================================================================
50
+ function loadFromComponentYaml(yamlPath, dir, source) {
51
+ const diagnostics = [];
52
+ let raw;
53
+ try {
54
+ raw = readFileSync(yamlPath, 'utf-8');
55
+ }
56
+ catch (error) {
57
+ const msg = error instanceof Error ? error.message : 'failed to read component.yaml';
58
+ diagnostics.push({ type: 'error', message: msg, path: yamlPath });
59
+ return { component: null, diagnostics };
60
+ }
61
+ let definition;
62
+ try {
63
+ definition = parseYaml(raw);
64
+ }
65
+ catch (error) {
66
+ const msg = error instanceof Error ? error.message : 'failed to parse component.yaml';
67
+ diagnostics.push({ type: 'error', message: `invalid YAML: ${msg}`, path: yamlPath });
68
+ return { component: null, diagnostics };
69
+ }
70
+ // Validate required fields
71
+ if (!definition?.apiVersion) {
72
+ diagnostics.push({ type: 'error', message: 'missing apiVersion', path: yamlPath });
73
+ return { component: null, diagnostics };
74
+ }
75
+ if (!SUPPORTED_API_VERSIONS.includes(definition.apiVersion)) {
76
+ diagnostics.push({
77
+ type: 'error',
78
+ message: `unsupported apiVersion "${String(definition.apiVersion)}"`,
79
+ path: yamlPath,
80
+ });
81
+ return { component: null, diagnostics };
82
+ }
83
+ if (!definition.kind) {
84
+ diagnostics.push({ type: 'error', message: 'missing kind', path: yamlPath });
85
+ return { component: null, diagnostics };
86
+ }
87
+ if (!SUPPORTED_COMPONENT_KINDS.includes(definition.kind)) {
88
+ diagnostics.push({
89
+ type: 'error',
90
+ message: `unsupported kind "${definition.kind}"`,
91
+ path: yamlPath,
92
+ });
93
+ return { component: null, diagnostics };
94
+ }
95
+ if (!definition.metadata?.name) {
96
+ diagnostics.push({ type: 'error', message: 'missing metadata.name', path: yamlPath });
97
+ return { component: null, diagnostics };
98
+ }
99
+ if (!definition.metadata?.description) {
100
+ diagnostics.push({ type: 'error', message: 'missing metadata.description', path: yamlPath });
101
+ return { component: null, diagnostics };
102
+ }
103
+ const nameErrors = validateComponentName(definition.metadata.name);
104
+ for (const err of nameErrors) {
105
+ diagnostics.push({ type: 'error', message: err, path: yamlPath });
106
+ }
107
+ const descErrors = validateComponentDescription(definition.metadata.description);
108
+ for (const err of descErrors) {
109
+ diagnostics.push({ type: 'error', message: err, path: yamlPath });
110
+ }
111
+ if (nameErrors.length > 0 || descErrors.length > 0) {
112
+ return { component: null, diagnostics };
113
+ }
114
+ // Validate kind-specific spec
115
+ if (!definition.spec) {
116
+ diagnostics.push({ type: 'error', message: 'missing spec', path: yamlPath });
117
+ return { component: null, diagnostics };
118
+ }
119
+ const entryFileDiagnostic = validateEntryFile(definition.kind, definition.spec, dir, yamlPath);
120
+ if (entryFileDiagnostic) {
121
+ diagnostics.push(entryFileDiagnostic);
122
+ return { component: null, diagnostics };
123
+ }
124
+ const id = computeComponentId(definition.metadata.name, definition.metadata.namespace);
125
+ const component = {
126
+ id,
127
+ kind: definition.kind,
128
+ metadata: definition.metadata,
129
+ spec: definition.spec,
130
+ requires: definition.requires,
131
+ compatibility: definition.compatibility,
132
+ routing: definition.routing,
133
+ dirPath: dir,
134
+ filePath: yamlPath,
135
+ source,
136
+ format: 'component-yaml',
137
+ enabled: true,
138
+ };
139
+ return { component, diagnostics };
140
+ }
141
+ function loadFromLegacySkill(filePath, dir, source) {
142
+ const diagnostics = [];
143
+ let raw;
144
+ try {
145
+ raw = readFileSync(filePath, 'utf-8');
146
+ }
147
+ catch (error) {
148
+ const msg = error instanceof Error ? error.message : 'failed to read SKILL.md';
149
+ diagnostics.push({ type: 'warning', message: msg, path: filePath });
150
+ return { component: null, diagnostics };
151
+ }
152
+ const { frontmatter } = parseFrontmatter(raw);
153
+ const parentDirName = basename(dir);
154
+ const name = frontmatter.name || parentDirName;
155
+ // Validate
156
+ const nameErrors = validateComponentName(name);
157
+ for (const err of nameErrors) {
158
+ diagnostics.push({ type: 'warning', message: err, path: filePath });
159
+ }
160
+ const descErrors = validateComponentDescription(frontmatter.description);
161
+ for (const err of descErrors) {
162
+ diagnostics.push({ type: 'warning', message: err, path: filePath });
163
+ }
164
+ if (!frontmatter.description || frontmatter.description.trim() === '') {
165
+ return { component: null, diagnostics };
166
+ }
167
+ const spec = {
168
+ prompt: 'SKILL.md',
169
+ disableModelInvocation: frontmatter['disable-model-invocation'] === true,
170
+ };
171
+ const id = computeComponentId(name);
172
+ const component = {
173
+ id,
174
+ kind: 'skill',
175
+ metadata: {
176
+ name,
177
+ description: frontmatter.description,
178
+ },
179
+ spec,
180
+ dirPath: dir,
181
+ filePath,
182
+ source,
183
+ format: 'skill-md',
184
+ enabled: true,
185
+ };
186
+ return { component, diagnostics };
187
+ }
188
+ function loadFromLegacyAgent(filePath, source) {
189
+ const diagnostics = [];
190
+ let raw;
191
+ try {
192
+ raw = readFileSync(filePath, 'utf-8');
193
+ }
194
+ catch (error) {
195
+ const msg = error instanceof Error ? error.message : 'failed to read agent file';
196
+ diagnostics.push({ type: 'warning', message: msg, path: filePath });
197
+ return { component: null, diagnostics };
198
+ }
199
+ const { frontmatter } = parseFrontmatter(raw);
200
+ if (!frontmatter.name || !frontmatter.description) {
201
+ diagnostics.push({
202
+ type: 'warning',
203
+ message: 'agent file missing name or description in frontmatter',
204
+ path: filePath,
205
+ });
206
+ return { component: null, diagnostics };
207
+ }
208
+ // Parse tools from comma-separated string
209
+ const tools = frontmatter.tools
210
+ ? {
211
+ allow: frontmatter.tools
212
+ .split(',')
213
+ .map((t) => t.trim())
214
+ .filter(Boolean),
215
+ }
216
+ : undefined;
217
+ const spec = {
218
+ systemPrompt: basename(filePath),
219
+ model: frontmatter.model,
220
+ tools,
221
+ };
222
+ const id = computeComponentId(frontmatter.name);
223
+ const dir = dirname(filePath);
224
+ const component = {
225
+ id,
226
+ kind: 'agent',
227
+ metadata: {
228
+ name: frontmatter.name,
229
+ description: frontmatter.description,
230
+ },
231
+ spec,
232
+ dirPath: dir,
233
+ filePath,
234
+ source,
235
+ format: 'agent-md',
236
+ enabled: true,
237
+ };
238
+ return { component, diagnostics };
239
+ }
240
+ // ============================================================================
241
+ // Directory Scanning
242
+ // ============================================================================
243
+ /**
244
+ * Scan a directory for components (skills format).
245
+ * Handles both new and legacy directory layouts.
246
+ *
247
+ * Expected layouts:
248
+ * - dir/{component-name}/component.yaml (new format)
249
+ * - dir/{component-name}/SKILL.md (legacy skill)
250
+ * - dir/{name}.md (legacy root-level skill)
251
+ */
252
+ export function scanComponentDir(dir, source, kind) {
253
+ const components = [];
254
+ const diagnostics = [];
255
+ if (!existsSync(dir)) {
256
+ return { components, diagnostics };
257
+ }
258
+ let entries;
259
+ try {
260
+ entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf-8' });
261
+ }
262
+ catch {
263
+ return { components, diagnostics };
264
+ }
265
+ for (const entry of entries) {
266
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
267
+ continue;
268
+ }
269
+ const fullPath = join(dir, entry.name);
270
+ let isDir = entry.isDirectory();
271
+ let isFile = entry.isFile();
272
+ if (entry.isSymbolicLink()) {
273
+ try {
274
+ const stats = statSync(fullPath);
275
+ isDir = stats.isDirectory();
276
+ isFile = stats.isFile();
277
+ }
278
+ catch {
279
+ continue;
280
+ }
281
+ }
282
+ if (isDir) {
283
+ const result = loadComponentFromDir(fullPath, source);
284
+ if (result.component) {
285
+ if (!kind || result.component.kind === kind) {
286
+ components.push(result.component);
287
+ }
288
+ }
289
+ diagnostics.push(...result.diagnostics);
290
+ }
291
+ else if (isFile && entry.name.endsWith('.md')) {
292
+ // Root-level .md files — could be legacy skills or agents
293
+ // Peek at frontmatter to determine type
294
+ const result = loadFromFile(fullPath, source);
295
+ if (result.component) {
296
+ if (!kind || result.component.kind === kind) {
297
+ components.push(result.component);
298
+ }
299
+ }
300
+ diagnostics.push(...result.diagnostics);
301
+ }
302
+ }
303
+ return { components, diagnostics };
304
+ }
305
+ /**
306
+ * Scan a directory specifically for agent .md files (legacy agent format).
307
+ */
308
+ export function scanAgentDir(dir, source) {
309
+ const components = [];
310
+ const diagnostics = [];
311
+ if (!existsSync(dir)) {
312
+ return { components, diagnostics };
313
+ }
314
+ let entries;
315
+ try {
316
+ entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf-8' });
317
+ }
318
+ catch {
319
+ return { components, diagnostics };
320
+ }
321
+ for (const entry of entries) {
322
+ const fullPath = join(dir, entry.name);
323
+ let isDir = entry.isDirectory();
324
+ let isFile = entry.isFile();
325
+ if (entry.isSymbolicLink()) {
326
+ try {
327
+ const stats = statSync(fullPath);
328
+ isDir = stats.isDirectory();
329
+ isFile = stats.isFile();
330
+ }
331
+ catch {
332
+ continue;
333
+ }
334
+ }
335
+ if (isDir) {
336
+ const result = loadComponentFromDir(fullPath, source);
337
+ if (result.component?.kind === 'agent') {
338
+ components.push(result.component);
339
+ }
340
+ diagnostics.push(...result.diagnostics);
341
+ continue;
342
+ }
343
+ if (!entry.name.endsWith('.md'))
344
+ continue;
345
+ if (!isFile)
346
+ continue;
347
+ // Check if there's a component.yaml in a same-named directory
348
+ const nameWithoutExt = entry.name.replace(/\.md$/, '');
349
+ const componentDir = join(dir, nameWithoutExt);
350
+ if (existsSync(join(componentDir, 'component.yaml'))) {
351
+ // New format takes precedence and is loaded by the directory branch.
352
+ continue;
353
+ }
354
+ const result = loadComponentFromAgentFile(fullPath, source);
355
+ if (result.component) {
356
+ components.push(result.component);
357
+ }
358
+ diagnostics.push(...result.diagnostics);
359
+ }
360
+ return { components, diagnostics };
361
+ }
362
+ // ============================================================================
363
+ // Helpers
364
+ // ============================================================================
365
+ /**
366
+ * Load a single file, detecting whether it's a skill or agent by frontmatter.
367
+ */
368
+ function loadFromFile(filePath, source) {
369
+ const diagnostics = [];
370
+ let raw;
371
+ try {
372
+ raw = readFileSync(filePath, 'utf-8');
373
+ }
374
+ catch (error) {
375
+ const msg = error instanceof Error ? error.message : 'failed to read file';
376
+ diagnostics.push({ type: 'warning', message: msg, path: filePath });
377
+ return { component: null, diagnostics };
378
+ }
379
+ const { frontmatter } = parseFrontmatter(raw);
380
+ // If it has 'tools' field, treat as agent
381
+ if (frontmatter.tools !== undefined) {
382
+ return loadFromLegacyAgent(filePath, source);
383
+ }
384
+ // Otherwise treat as a legacy skill (root-level .md)
385
+ const dir = dirname(filePath);
386
+ const name = frontmatter.name || basename(filePath, '.md');
387
+ const description = frontmatter.description;
388
+ if (!description || description.trim() === '') {
389
+ return { component: null, diagnostics };
390
+ }
391
+ const spec = {
392
+ prompt: basename(filePath),
393
+ disableModelInvocation: frontmatter['disable-model-invocation'] === true,
394
+ };
395
+ const id = computeComponentId(name);
396
+ const component = {
397
+ id,
398
+ kind: 'skill',
399
+ metadata: { name, description },
400
+ spec,
401
+ dirPath: dir,
402
+ filePath,
403
+ source,
404
+ format: 'skill-md',
405
+ enabled: true,
406
+ };
407
+ return { component, diagnostics };
408
+ }
409
+ function validateEntryFile(kind, spec, dir, yamlPath) {
410
+ const relativePath = kind === 'skill'
411
+ ? spec.prompt
412
+ : spec.systemPrompt;
413
+ const field = kind === 'skill' ? 'spec.prompt' : 'spec.systemPrompt';
414
+ if (!relativePath || typeof relativePath !== 'string') {
415
+ return {
416
+ type: 'error',
417
+ message: `missing ${field}`,
418
+ path: yamlPath,
419
+ };
420
+ }
421
+ const entryPath = join(dir, relativePath);
422
+ if (!existsSync(entryPath)) {
423
+ return {
424
+ type: 'error',
425
+ message: `missing referenced file for ${field}: ${relativePath}`,
426
+ path: entryPath,
427
+ };
428
+ }
429
+ try {
430
+ if (!statSync(entryPath).isFile()) {
431
+ return {
432
+ type: 'error',
433
+ message: `referenced ${field} is not a file: ${relativePath}`,
434
+ path: entryPath,
435
+ };
436
+ }
437
+ }
438
+ catch (error) {
439
+ const msg = error instanceof Error ? error.message : 'failed to inspect referenced file';
440
+ return {
441
+ type: 'error',
442
+ message: `${msg}: ${relativePath}`,
443
+ path: entryPath,
444
+ };
445
+ }
446
+ return null;
447
+ }