gsd-pi 2.20.0 → 2.22.0

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 (279) hide show
  1. package/README.md +12 -0
  2. package/dist/cli.js +21 -18
  3. package/dist/help-text.d.ts +2 -0
  4. package/dist/help-text.js +47 -0
  5. package/dist/loader.js +2 -16
  6. package/dist/mcp-server.d.ts +18 -0
  7. package/dist/mcp-server.js +53 -0
  8. package/dist/onboarding.d.ts +0 -6
  9. package/dist/onboarding.js +0 -28
  10. package/dist/resources/agents/javascript-pro.md +280 -0
  11. package/dist/resources/agents/typescript-pro.md +255 -0
  12. package/dist/resources/extensions/bg-shell/index.ts +14 -2
  13. package/dist/resources/extensions/bg-shell/utilities.ts +14 -0
  14. package/dist/resources/extensions/browser-tools/{core.js → core.ts} +329 -190
  15. package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -1
  16. package/dist/resources/extensions/gsd/auto-prompts.ts +42 -1
  17. package/dist/resources/extensions/gsd/auto-worktree.ts +35 -2
  18. package/dist/resources/extensions/gsd/auto.ts +116 -10
  19. package/dist/resources/extensions/gsd/claude-import.ts +656 -0
  20. package/dist/resources/extensions/gsd/collision-diagnostics.ts +332 -0
  21. package/dist/resources/extensions/gsd/commands.ts +60 -7
  22. package/dist/resources/extensions/gsd/diff-context.ts +220 -0
  23. package/dist/resources/extensions/gsd/docs/claude-marketplace-import.md +214 -0
  24. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -1
  25. package/dist/resources/extensions/gsd/doctor.ts +12 -4
  26. package/dist/resources/extensions/gsd/file-watcher.ts +97 -0
  27. package/dist/resources/extensions/gsd/files.ts +1 -1
  28. package/dist/resources/extensions/gsd/forensics.ts +596 -0
  29. package/dist/resources/extensions/gsd/git-service.ts +2 -1
  30. package/dist/resources/extensions/gsd/github-client.ts +235 -0
  31. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  32. package/dist/resources/extensions/gsd/marketplace-discovery.ts +507 -0
  33. package/dist/resources/extensions/gsd/mcp-server.ts +87 -0
  34. package/dist/resources/extensions/gsd/namespaced-registry.ts +467 -0
  35. package/dist/resources/extensions/gsd/namespaced-resolver.ts +307 -0
  36. package/dist/resources/extensions/gsd/plugin-importer.ts +410 -0
  37. package/dist/resources/extensions/gsd/preferences.ts +14 -3
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/forensics.md +71 -0
  40. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/system.md +12 -3
  43. package/dist/resources/extensions/gsd/roadmap-slices.ts +1 -1
  44. package/dist/resources/extensions/gsd/session-forensics.ts +5 -5
  45. package/dist/resources/extensions/gsd/tests/claude-import-tui.test.ts +351 -0
  46. package/dist/resources/extensions/gsd/tests/collision-diagnostics.test.ts +705 -0
  47. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +13 -0
  48. package/dist/resources/extensions/gsd/tests/diff-context.test.ts +136 -0
  49. package/dist/resources/extensions/gsd/tests/doctor-git.test.ts +118 -0
  50. package/dist/resources/extensions/gsd/tests/marketplace-discovery.test.ts +202 -0
  51. package/dist/resources/extensions/gsd/tests/marketplace-test-fixtures.ts +91 -0
  52. package/dist/resources/extensions/gsd/tests/namespaced-registry.test.ts +1027 -0
  53. package/dist/resources/extensions/gsd/tests/namespaced-resolver.test.ts +671 -0
  54. package/dist/resources/extensions/gsd/tests/none-mode-gates.test.ts +105 -0
  55. package/dist/resources/extensions/gsd/tests/plugin-importer-live.test.ts +481 -0
  56. package/dist/resources/extensions/gsd/tests/plugin-importer.test.ts +1383 -0
  57. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +21 -2
  58. package/dist/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +8 -1
  59. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +11 -0
  60. package/dist/resources/extensions/gsd/tests/worktree-bugfix.test.ts +120 -0
  61. package/dist/resources/extensions/gsd/token-counter.ts +45 -0
  62. package/dist/resources/extensions/gsd/visualizer-data.ts +2 -2
  63. package/dist/resources/extensions/gsd/worktree-manager.ts +29 -1
  64. package/dist/resources/extensions/gsd/worktree.ts +3 -0
  65. package/dist/resources/extensions/mcporter/index.ts +90 -7
  66. package/dist/resources/extensions/search-the-web/native-search.ts +2 -0
  67. package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +9 -1
  68. package/dist/resources/extensions/search-the-web/url-utils.ts +35 -1
  69. package/dist/resources/extensions/shared/terminal.ts +1 -1
  70. package/dist/resources/extensions/universal-config/discovery.ts +4 -0
  71. package/dist/resources/extensions/universal-config/format.ts +35 -4
  72. package/dist/resources/extensions/universal-config/index.ts +5 -3
  73. package/dist/resources/extensions/universal-config/scanners.ts +65 -2
  74. package/dist/resources/extensions/universal-config/tests/discovery.test.ts +9 -1
  75. package/dist/resources/extensions/universal-config/tests/format.test.ts +22 -6
  76. package/dist/resources/extensions/universal-config/tests/scanners.test.ts +18 -0
  77. package/dist/resources/extensions/universal-config/types.ts +20 -1
  78. package/dist/resources/skills/lint/SKILL.md +141 -0
  79. package/dist/resources/skills/review/SKILL.md +214 -0
  80. package/dist/resources/skills/test/SKILL.md +201 -0
  81. package/dist/tool-bootstrap.js +2 -1
  82. package/dist/wizard.js +2 -0
  83. package/package.json +12 -7
  84. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  85. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -1
  86. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  87. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  88. package/packages/pi-ai/dist/providers/openai-responses.js +12 -1
  89. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  90. package/packages/pi-ai/node_modules/@smithy/node-http-handler/LICENSE +201 -0
  91. package/packages/pi-ai/node_modules/@smithy/node-http-handler/README.md +9 -0
  92. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-cjs/index.js +762 -0
  93. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/build-abort-error.js +19 -0
  94. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/constants.js +1 -0
  95. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/get-transformed-headers.js +9 -0
  96. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/index.js +3 -0
  97. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http-handler.js +230 -0
  98. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http2-connection-manager.js +87 -0
  99. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http2-connection-pool.js +32 -0
  100. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http2-handler.js +169 -0
  101. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/readable.mock.js +21 -0
  102. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/server.mock.js +88 -0
  103. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-connection-timeout.js +36 -0
  104. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-request-timeout.js +21 -0
  105. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-socket-keep-alive.js +22 -0
  106. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-socket-timeout.js +23 -0
  107. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/stream-collector/collector.js +8 -0
  108. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/stream-collector/index.js +41 -0
  109. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/stream-collector/readable.mock.js +21 -0
  110. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/timing.js +4 -0
  111. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/write-request-body.js +63 -0
  112. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/build-abort-error.d.ts +10 -0
  113. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/constants.d.ts +5 -0
  114. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/get-transformed-headers.d.ts +4 -0
  115. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/index.d.ts +3 -0
  116. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http-handler.d.ts +46 -0
  117. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http2-connection-manager.d.ts +24 -0
  118. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http2-connection-pool.d.ts +12 -0
  119. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http2-handler.d.ts +63 -0
  120. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/readable.mock.d.ts +13 -0
  121. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/server.mock.d.ts +12 -0
  122. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-connection-timeout.d.ts +2 -0
  123. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-request-timeout.d.ts +6 -0
  124. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-socket-keep-alive.d.ts +6 -0
  125. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-socket-timeout.d.ts +2 -0
  126. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/stream-collector/collector.d.ts +5 -0
  127. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/stream-collector/index.d.ts +6 -0
  128. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/stream-collector/readable.mock.d.ts +13 -0
  129. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/timing.d.ts +8 -0
  130. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/build-abort-error.d.ts +10 -0
  131. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/constants.d.ts +5 -0
  132. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/get-transformed-headers.d.ts +4 -0
  133. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/index.d.ts +3 -0
  134. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http-handler.d.ts +46 -0
  135. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-connection-manager.d.ts +24 -0
  136. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-connection-pool.d.ts +12 -0
  137. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-handler.d.ts +63 -0
  138. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/readable.mock.d.ts +13 -0
  139. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/server.mock.d.ts +12 -0
  140. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-connection-timeout.d.ts +2 -0
  141. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-request-timeout.d.ts +6 -0
  142. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-socket-keep-alive.d.ts +6 -0
  143. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-socket-timeout.d.ts +2 -0
  144. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/stream-collector/collector.d.ts +5 -0
  145. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/stream-collector/index.d.ts +6 -0
  146. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/stream-collector/readable.mock.d.ts +13 -0
  147. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/timing.d.ts +8 -0
  148. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/write-request-body.d.ts +12 -0
  149. package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/write-request-body.d.ts +12 -0
  150. package/packages/pi-ai/node_modules/@smithy/node-http-handler/package.json +68 -0
  151. package/packages/pi-ai/package.json +3 -0
  152. package/packages/pi-ai/pnpm-lock.yaml +2022 -0
  153. package/packages/pi-ai/src/providers/azure-openai-responses.ts +12 -1
  154. package/packages/pi-ai/src/providers/openai-responses.ts +12 -1
  155. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  156. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
  158. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +1 -0
  160. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/extensions/loader.js +12 -1
  162. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/core/extensions/loader.test.d.ts +2 -0
  164. package/packages/pi-coding-agent/dist/core/extensions/loader.test.d.ts.map +1 -0
  165. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +113 -0
  166. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -0
  167. package/packages/pi-coding-agent/dist/core/extensions/project-trust.d.ts +4 -0
  168. package/packages/pi-coding-agent/dist/core/extensions/project-trust.d.ts.map +1 -0
  169. package/packages/pi-coding-agent/dist/core/extensions/project-trust.js +42 -0
  170. package/packages/pi-coding-agent/dist/core/extensions/project-trust.js.map +1 -0
  171. package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts +1 -0
  172. package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/core/resolve-config-value.js +18 -0
  174. package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.d.ts +2 -0
  176. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.d.ts.map +1 -0
  177. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +114 -0
  178. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -0
  179. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +8 -2
  181. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts +2 -0
  183. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +23 -9
  185. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.d.ts +2 -0
  187. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.d.ts.map +1 -0
  188. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +67 -0
  189. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -0
  190. package/packages/pi-coding-agent/package.json +1 -1
  191. package/packages/pi-coding-agent/pnpm-lock.yaml +454 -0
  192. package/packages/pi-coding-agent/scripts/copy-assets.cjs +24 -0
  193. package/packages/pi-coding-agent/src/core/extensions/index.ts +3 -0
  194. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +141 -0
  195. package/packages/pi-coding-agent/src/core/extensions/loader.ts +14 -1
  196. package/packages/pi-coding-agent/src/core/extensions/project-trust.ts +51 -0
  197. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +132 -0
  198. package/packages/pi-coding-agent/src/core/resolve-config-value.ts +20 -0
  199. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +7 -2
  200. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +98 -0
  201. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +24 -9
  202. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  203. package/packages/pi-tui/dist/tui.js +2 -2
  204. package/packages/pi-tui/dist/tui.js.map +1 -1
  205. package/packages/pi-tui/package.json +3 -1
  206. package/packages/pi-tui/src/tui.ts +2 -2
  207. package/src/resources/agents/javascript-pro.md +280 -0
  208. package/src/resources/agents/typescript-pro.md +255 -0
  209. package/src/resources/extensions/bg-shell/index.ts +14 -2
  210. package/src/resources/extensions/bg-shell/utilities.ts +14 -0
  211. package/src/resources/extensions/browser-tools/{core.js → core.ts} +329 -190
  212. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
  213. package/src/resources/extensions/gsd/auto-prompts.ts +42 -1
  214. package/src/resources/extensions/gsd/auto-worktree.ts +35 -2
  215. package/src/resources/extensions/gsd/auto.ts +116 -10
  216. package/src/resources/extensions/gsd/claude-import.ts +656 -0
  217. package/src/resources/extensions/gsd/collision-diagnostics.ts +332 -0
  218. package/src/resources/extensions/gsd/commands.ts +60 -7
  219. package/src/resources/extensions/gsd/diff-context.ts +220 -0
  220. package/src/resources/extensions/gsd/docs/claude-marketplace-import.md +214 -0
  221. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -1
  222. package/src/resources/extensions/gsd/doctor.ts +12 -4
  223. package/src/resources/extensions/gsd/file-watcher.ts +97 -0
  224. package/src/resources/extensions/gsd/files.ts +1 -1
  225. package/src/resources/extensions/gsd/forensics.ts +596 -0
  226. package/src/resources/extensions/gsd/git-service.ts +2 -1
  227. package/src/resources/extensions/gsd/github-client.ts +235 -0
  228. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  229. package/src/resources/extensions/gsd/marketplace-discovery.ts +507 -0
  230. package/src/resources/extensions/gsd/mcp-server.ts +87 -0
  231. package/src/resources/extensions/gsd/namespaced-registry.ts +467 -0
  232. package/src/resources/extensions/gsd/namespaced-resolver.ts +307 -0
  233. package/src/resources/extensions/gsd/plugin-importer.ts +410 -0
  234. package/src/resources/extensions/gsd/preferences.ts +14 -3
  235. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  236. package/src/resources/extensions/gsd/prompts/forensics.md +71 -0
  237. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  238. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  239. package/src/resources/extensions/gsd/prompts/system.md +12 -3
  240. package/src/resources/extensions/gsd/roadmap-slices.ts +1 -1
  241. package/src/resources/extensions/gsd/session-forensics.ts +5 -5
  242. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +351 -0
  243. package/src/resources/extensions/gsd/tests/collision-diagnostics.test.ts +705 -0
  244. package/src/resources/extensions/gsd/tests/context-compression.test.ts +13 -0
  245. package/src/resources/extensions/gsd/tests/diff-context.test.ts +136 -0
  246. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +118 -0
  247. package/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts +202 -0
  248. package/src/resources/extensions/gsd/tests/marketplace-test-fixtures.ts +91 -0
  249. package/src/resources/extensions/gsd/tests/namespaced-registry.test.ts +1027 -0
  250. package/src/resources/extensions/gsd/tests/namespaced-resolver.test.ts +671 -0
  251. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +105 -0
  252. package/src/resources/extensions/gsd/tests/plugin-importer-live.test.ts +481 -0
  253. package/src/resources/extensions/gsd/tests/plugin-importer.test.ts +1383 -0
  254. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +21 -2
  255. package/src/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +8 -1
  256. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +11 -0
  257. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +120 -0
  258. package/src/resources/extensions/gsd/token-counter.ts +45 -0
  259. package/src/resources/extensions/gsd/visualizer-data.ts +2 -2
  260. package/src/resources/extensions/gsd/worktree-manager.ts +29 -1
  261. package/src/resources/extensions/gsd/worktree.ts +3 -0
  262. package/src/resources/extensions/mcporter/index.ts +90 -7
  263. package/src/resources/extensions/search-the-web/native-search.ts +2 -0
  264. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +9 -1
  265. package/src/resources/extensions/search-the-web/url-utils.ts +35 -1
  266. package/src/resources/extensions/shared/terminal.ts +1 -1
  267. package/src/resources/extensions/universal-config/discovery.ts +4 -0
  268. package/src/resources/extensions/universal-config/format.ts +35 -4
  269. package/src/resources/extensions/universal-config/index.ts +5 -3
  270. package/src/resources/extensions/universal-config/scanners.ts +65 -2
  271. package/src/resources/extensions/universal-config/tests/discovery.test.ts +9 -1
  272. package/src/resources/extensions/universal-config/tests/format.test.ts +22 -6
  273. package/src/resources/extensions/universal-config/tests/scanners.test.ts +18 -0
  274. package/src/resources/extensions/universal-config/types.ts +20 -1
  275. package/src/resources/skills/lint/SKILL.md +141 -0
  276. package/src/resources/skills/review/SKILL.md +214 -0
  277. package/src/resources/skills/test/SKILL.md +201 -0
  278. package/dist/resources/extensions/browser-tools/core.d.ts +0 -205
  279. package/src/resources/extensions/browser-tools/core.d.ts +0 -205
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Namespaced Resolver Contract Tests
3
+ *
4
+ * Tests that prove the resolver correctly handles:
5
+ * - R007: Canonical skill lookup
6
+ * - R008: Canonical agent lookup
7
+ * - D003: Same-plugin local-first resolution
8
+ * - R009: Shorthand resolution (unambiguous and ambiguous)
9
+ * - Flat component compatibility
10
+ * - Type filtering (skill vs agent)
11
+ */
12
+
13
+ import { describe, it, beforeEach } from 'node:test';
14
+ import assert from 'node:assert';
15
+ import { NamespacedRegistry } from '../namespaced-registry.js';
16
+ import { NamespacedResolver } from '../namespaced-resolver.js';
17
+
18
+ describe('NamespacedResolver', () => {
19
+ let registry: NamespacedRegistry;
20
+ let resolver: NamespacedResolver;
21
+
22
+ beforeEach(() => {
23
+ registry = new NamespacedRegistry();
24
+ resolver = new NamespacedResolver(registry);
25
+ });
26
+
27
+ describe('canonical lookup (R007, R008)', () => {
28
+ it('should resolve canonical skill name with canonical result (R007)', () => {
29
+ registry.register({
30
+ name: 'call-horse',
31
+ namespace: 'farm',
32
+ type: 'skill',
33
+ filePath: '/farm/call-horse/SKILL.md',
34
+ source: 'plugin:farm',
35
+ description: 'Calls a horse',
36
+ metadata: {},
37
+ });
38
+
39
+ const result = resolver.resolve('farm:call-horse');
40
+
41
+ assert.strictEqual(result.resolution, 'canonical');
42
+ if (result.resolution !== 'canonical') throw new Error('Type guard');
43
+
44
+ assert.strictEqual(result.requestedName, 'farm:call-horse');
45
+ assert.strictEqual(result.component.canonicalName, 'farm:call-horse');
46
+ assert.strictEqual(result.component.type, 'skill');
47
+ });
48
+
49
+ it('should resolve canonical agent name with canonical result (R008)', () => {
50
+ registry.register({
51
+ name: 'rancher',
52
+ namespace: 'farm',
53
+ type: 'agent',
54
+ filePath: '/farm/rancher/AGENT.md',
55
+ source: 'plugin:farm',
56
+ description: 'Farm agent',
57
+ metadata: {},
58
+ });
59
+
60
+ const result = resolver.resolve('farm:rancher');
61
+
62
+ assert.strictEqual(result.resolution, 'canonical');
63
+ if (result.resolution !== 'canonical') throw new Error('Type guard');
64
+
65
+ assert.strictEqual(result.component.canonicalName, 'farm:rancher');
66
+ assert.strictEqual(result.component.type, 'agent');
67
+ });
68
+
69
+ it('should return not-found for non-existent canonical name', () => {
70
+ const result = resolver.resolve('nonexistent:skill');
71
+ assert.strictEqual(result.resolution, 'not-found');
72
+ });
73
+
74
+ it('should return not-found for canonical name with wrong type filter', () => {
75
+ registry.register({
76
+ name: 'call-horse',
77
+ namespace: 'farm',
78
+ type: 'skill',
79
+ filePath: '/farm/call-horse/SKILL.md',
80
+ source: 'plugin:farm',
81
+ description: 'Calls a horse',
82
+ metadata: {},
83
+ });
84
+
85
+ const result = resolver.resolve('farm:call-horse', undefined, 'agent');
86
+ assert.strictEqual(result.resolution, 'not-found');
87
+ });
88
+ });
89
+
90
+ describe('local-first resolution (D003)', () => {
91
+ it('should resolve bare name local-first when caller namespace has match', () => {
92
+ registry.register({
93
+ name: 'call-horse',
94
+ namespace: 'farm',
95
+ type: 'skill',
96
+ filePath: '/farm/call-horse/SKILL.md',
97
+ source: 'plugin:farm',
98
+ description: 'Farm horse caller',
99
+ metadata: {},
100
+ });
101
+ registry.register({
102
+ name: 'call-horse',
103
+ namespace: 'zoo',
104
+ type: 'skill',
105
+ filePath: '/zoo/call-horse/SKILL.md',
106
+ source: 'plugin:zoo',
107
+ description: 'Zoo horse caller',
108
+ metadata: {},
109
+ });
110
+
111
+ const result = resolver.resolve('call-horse', { callerNamespace: 'farm' });
112
+
113
+ assert.strictEqual(result.resolution, 'local-first');
114
+ if (result.resolution !== 'local-first') throw new Error('Type guard');
115
+
116
+ assert.strictEqual(result.requestedName, 'call-horse');
117
+ assert.strictEqual(result.component.canonicalName, 'farm:call-horse');
118
+ assert.strictEqual(result.matchedNamespace, 'farm');
119
+ });
120
+
121
+ it('should resolve local-first from zoo namespace context', () => {
122
+ registry.register({
123
+ name: 'call-horse',
124
+ namespace: 'farm',
125
+ type: 'skill',
126
+ filePath: '/farm/call-horse/SKILL.md',
127
+ source: 'plugin:farm',
128
+ description: 'Farm horse caller',
129
+ metadata: {},
130
+ });
131
+ registry.register({
132
+ name: 'call-horse',
133
+ namespace: 'zoo',
134
+ type: 'skill',
135
+ filePath: '/zoo/call-horse/SKILL.md',
136
+ source: 'plugin:zoo',
137
+ description: 'Zoo horse caller',
138
+ metadata: {},
139
+ });
140
+
141
+ const result = resolver.resolve('call-horse', { callerNamespace: 'zoo' });
142
+
143
+ assert.strictEqual(result.resolution, 'local-first');
144
+ if (result.resolution !== 'local-first') throw new Error('Type guard');
145
+
146
+ assert.strictEqual(result.component.canonicalName, 'zoo:call-horse');
147
+ });
148
+
149
+ it('should fall through to shorthand when local namespace has no match', () => {
150
+ registry.register({
151
+ name: 'feed-chickens',
152
+ namespace: 'farm',
153
+ type: 'skill',
154
+ filePath: '/farm/feed-chickens/SKILL.md',
155
+ source: 'plugin:farm',
156
+ description: 'Feed chickens',
157
+ metadata: {},
158
+ });
159
+
160
+ const result = resolver.resolve('feed-chickens', { callerNamespace: 'zoo' });
161
+
162
+ assert.strictEqual(result.resolution, 'shorthand');
163
+ if (result.resolution !== 'shorthand') throw new Error('Type guard');
164
+
165
+ assert.strictEqual(result.component.canonicalName, 'farm:feed-chickens');
166
+ });
167
+
168
+ it('should respect type filter in local-first resolution', () => {
169
+ // Register two different names - one skill, one agent
170
+ registry.register({
171
+ name: 'helper-skill',
172
+ namespace: 'farm',
173
+ type: 'skill',
174
+ filePath: '/farm/helper-skill/SKILL.md',
175
+ source: 'plugin:farm',
176
+ description: 'Helper skill',
177
+ metadata: {},
178
+ });
179
+ registry.register({
180
+ name: 'helper-agent',
181
+ namespace: 'farm',
182
+ type: 'agent',
183
+ filePath: '/farm/helper-agent/AGENT.md',
184
+ source: 'plugin:farm',
185
+ description: 'Helper agent',
186
+ metadata: {},
187
+ });
188
+
189
+ // Request skill - should find helper-skill
190
+ const skillResult = resolver.resolve('helper-skill', { callerNamespace: 'farm' }, 'skill');
191
+ assert.strictEqual(skillResult.resolution, 'local-first');
192
+ if (skillResult.resolution !== 'local-first') throw new Error('Type guard');
193
+ assert.strictEqual(skillResult.component.type, 'skill');
194
+ assert.strictEqual(skillResult.component.name, 'helper-skill');
195
+
196
+ // Request agent - should find helper-agent
197
+ const agentResult = resolver.resolve('helper-agent', { callerNamespace: 'farm' }, 'agent');
198
+ assert.strictEqual(agentResult.resolution, 'local-first');
199
+ if (agentResult.resolution !== 'local-first') throw new Error('Type guard');
200
+ assert.strictEqual(agentResult.component.type, 'agent');
201
+ assert.strictEqual(agentResult.component.name, 'helper-agent');
202
+ });
203
+ });
204
+
205
+ describe('shorthand resolution (R009)', () => {
206
+ it('should resolve unambiguous shorthand with single match', () => {
207
+ registry.register({
208
+ name: 'feed-chickens',
209
+ namespace: 'farm',
210
+ type: 'skill',
211
+ filePath: '/farm/feed-chickens/SKILL.md',
212
+ source: 'plugin:farm',
213
+ description: 'Feed chickens',
214
+ metadata: {},
215
+ });
216
+
217
+ const result = resolver.resolve('feed-chickens');
218
+
219
+ assert.strictEqual(result.resolution, 'shorthand');
220
+ if (result.resolution !== 'shorthand') throw new Error('Type guard');
221
+
222
+ assert.strictEqual(result.requestedName, 'feed-chickens');
223
+ assert.strictEqual(result.component.canonicalName, 'farm:feed-chickens');
224
+ });
225
+
226
+ it('should return ambiguous with candidates for multiple matches', () => {
227
+ registry.register({
228
+ name: 'call-horse',
229
+ namespace: 'farm',
230
+ type: 'skill',
231
+ filePath: '/farm/call-horse/SKILL.md',
232
+ source: 'plugin:farm',
233
+ description: 'Farm horse caller',
234
+ metadata: {},
235
+ });
236
+ registry.register({
237
+ name: 'call-horse',
238
+ namespace: 'zoo',
239
+ type: 'skill',
240
+ filePath: '/zoo/call-horse/SKILL.md',
241
+ source: 'plugin:zoo',
242
+ description: 'Zoo horse caller',
243
+ metadata: {},
244
+ });
245
+
246
+ const result = resolver.resolve('call-horse');
247
+
248
+ assert.strictEqual(result.resolution, 'ambiguous');
249
+ if (result.resolution !== 'ambiguous') throw new Error('Type guard');
250
+
251
+ assert.strictEqual(result.requestedName, 'call-horse');
252
+ assert.strictEqual(result.candidates.length, 2);
253
+
254
+ const canonicalNames = result.candidates.map((c) => c.canonicalName).sort();
255
+ assert.deepStrictEqual(canonicalNames, ['farm:call-horse', 'zoo:call-horse']);
256
+ });
257
+
258
+ it('should return not-found for non-existent bare name', () => {
259
+ const result = resolver.resolve('nonexistent');
260
+ assert.strictEqual(result.resolution, 'not-found');
261
+ });
262
+
263
+ it('should return not-found when type filter eliminates all matches', () => {
264
+ registry.register({
265
+ name: 'helper',
266
+ namespace: 'farm',
267
+ type: 'skill',
268
+ filePath: '/farm/helper/SKILL.md',
269
+ source: 'plugin:farm',
270
+ description: 'Helper skill',
271
+ metadata: {},
272
+ });
273
+
274
+ const result = resolver.resolve('helper', undefined, 'agent');
275
+ assert.strictEqual(result.resolution, 'not-found');
276
+ });
277
+ });
278
+
279
+ describe('flat component compatibility', () => {
280
+ it('should resolve flat component by bare name (no namespace)', () => {
281
+ registry.register({
282
+ name: 'code-review',
283
+ namespace: undefined,
284
+ type: 'skill',
285
+ filePath: '/skills/code-review/SKILL.md',
286
+ source: 'user',
287
+ description: 'Code review skill',
288
+ metadata: {},
289
+ });
290
+
291
+ const result = resolver.resolve('code-review');
292
+
293
+ assert.strictEqual(result.resolution, 'shorthand');
294
+ if (result.resolution !== 'shorthand') throw new Error('Type guard');
295
+
296
+ assert.strictEqual(result.component.canonicalName, 'code-review');
297
+ assert.strictEqual(result.component.namespace, undefined);
298
+ });
299
+
300
+ it('should include flat component in ambiguous candidates', () => {
301
+ registry.register({
302
+ name: 'helper',
303
+ namespace: undefined,
304
+ type: 'skill',
305
+ filePath: '/skills/helper/SKILL.md',
306
+ source: 'user',
307
+ description: 'User helper',
308
+ metadata: {},
309
+ });
310
+ registry.register({
311
+ name: 'helper',
312
+ namespace: 'farm',
313
+ type: 'skill',
314
+ filePath: '/farm/helper/SKILL.md',
315
+ source: 'plugin:farm',
316
+ description: 'Farm helper',
317
+ metadata: {},
318
+ });
319
+
320
+ const result = resolver.resolve('helper');
321
+
322
+ assert.strictEqual(result.resolution, 'ambiguous');
323
+ if (result.resolution !== 'ambiguous') throw new Error('Type guard');
324
+
325
+ assert.strictEqual(result.candidates.length, 2);
326
+ const canonicalNames = result.candidates.map((c) => c.canonicalName).sort();
327
+ assert.deepStrictEqual(canonicalNames, ['farm:helper', 'helper']);
328
+ });
329
+ });
330
+
331
+ describe('type filtering', () => {
332
+ it('should filter by skill type across namespaces', () => {
333
+ // Register skill in one namespace
334
+ registry.register({
335
+ name: 'review',
336
+ namespace: 'tools',
337
+ type: 'skill',
338
+ filePath: '/tools/review/SKILL.md',
339
+ source: 'plugin:tools',
340
+ description: 'Review skill',
341
+ metadata: {},
342
+ });
343
+ // Register agent in another namespace (different canonical name)
344
+ registry.register({
345
+ name: 'review',
346
+ namespace: 'agents',
347
+ type: 'agent',
348
+ filePath: '/agents/review/AGENT.md',
349
+ source: 'plugin:agents',
350
+ description: 'Review agent',
351
+ metadata: {},
352
+ });
353
+
354
+ // Both have same bare name, filtering by type disambiguates
355
+ const skillResult = resolver.resolve('review', undefined, 'skill');
356
+ assert.strictEqual(skillResult.resolution, 'shorthand');
357
+ if (skillResult.resolution !== 'shorthand') throw new Error('Type guard');
358
+ assert.strictEqual(skillResult.component.type, 'skill');
359
+ assert.strictEqual(skillResult.component.namespace, 'tools');
360
+
361
+ const agentResult = resolver.resolve('review', undefined, 'agent');
362
+ assert.strictEqual(agentResult.resolution, 'shorthand');
363
+ if (agentResult.resolution !== 'shorthand') throw new Error('Type guard');
364
+ assert.strictEqual(agentResult.component.type, 'agent');
365
+ assert.strictEqual(agentResult.component.namespace, 'agents');
366
+ });
367
+
368
+ it('should resolve unique skill among multiple agents with same name', () => {
369
+ registry.register({
370
+ name: 'assistant',
371
+ namespace: 'tools',
372
+ type: 'skill',
373
+ filePath: '/tools/assistant/SKILL.md',
374
+ source: 'plugin:tools',
375
+ description: 'Assistant skill',
376
+ metadata: {},
377
+ });
378
+ registry.register({
379
+ name: 'assistant',
380
+ namespace: 'other',
381
+ type: 'agent',
382
+ filePath: '/other/assistant/AGENT.md',
383
+ source: 'plugin:other',
384
+ description: 'Assistant agent',
385
+ metadata: {},
386
+ });
387
+
388
+ const result = resolver.resolve('assistant', undefined, 'skill');
389
+ assert.strictEqual(result.resolution, 'shorthand');
390
+ if (result.resolution !== 'shorthand') throw new Error('Type guard');
391
+ assert.strictEqual(result.component.canonicalName, 'tools:assistant');
392
+ });
393
+ });
394
+
395
+ describe('resolution path diagnostics', () => {
396
+ it('should include requestedName in all result types', () => {
397
+ registry.register({
398
+ name: 'skill',
399
+ namespace: 'ns',
400
+ type: 'skill',
401
+ filePath: '/skill/SKILL.md',
402
+ source: 'test',
403
+ description: undefined,
404
+ metadata: {},
405
+ });
406
+
407
+ const canon = resolver.resolve('ns:skill');
408
+ assert.strictEqual(canon.requestedName, 'ns:skill');
409
+
410
+ const local = resolver.resolve('skill', { callerNamespace: 'ns' });
411
+ assert.strictEqual(local.requestedName, 'skill');
412
+
413
+ const short = resolver.resolve('skill');
414
+ assert.strictEqual(short.requestedName, 'skill');
415
+
416
+ const notFound = resolver.resolve('missing');
417
+ assert.strictEqual(notFound.requestedName, 'missing');
418
+ });
419
+
420
+ it('should provide matchedNamespace in local-first results', () => {
421
+ registry.register({
422
+ name: 'skill',
423
+ namespace: 'my-ns',
424
+ type: 'skill',
425
+ filePath: '/skill/SKILL.md',
426
+ source: 'test',
427
+ description: undefined,
428
+ metadata: {},
429
+ });
430
+
431
+ const result = resolver.resolve('skill', { callerNamespace: 'my-ns' });
432
+ assert.strictEqual(result.resolution, 'local-first');
433
+
434
+ if (result.resolution === 'local-first') {
435
+ assert.strictEqual(result.matchedNamespace, 'my-ns');
436
+ }
437
+ });
438
+
439
+ it('should provide full candidate list in ambiguous results', () => {
440
+ registry.register({
441
+ name: 'dup',
442
+ namespace: 'a',
443
+ type: 'skill',
444
+ filePath: '/a/dup/SKILL.md',
445
+ source: 'a',
446
+ description: 'A dup',
447
+ metadata: {},
448
+ });
449
+ registry.register({
450
+ name: 'dup',
451
+ namespace: 'b',
452
+ type: 'skill',
453
+ filePath: '/b/dup/SKILL.md',
454
+ source: 'b',
455
+ description: 'B dup',
456
+ metadata: {},
457
+ });
458
+
459
+ const result = resolver.resolve('dup');
460
+ assert.strictEqual(result.resolution, 'ambiguous');
461
+
462
+ if (result.resolution === 'ambiguous') {
463
+ assert.strictEqual(result.candidates.length, 2);
464
+ for (const candidate of result.candidates) {
465
+ assert.ok(candidate.canonicalName);
466
+ assert.ok(candidate.filePath);
467
+ assert.strictEqual(candidate.name, 'dup');
468
+ }
469
+ }
470
+ });
471
+ });
472
+
473
+ describe('edge cases', () => {
474
+ it('should handle empty registry gracefully', () => {
475
+ const result = resolver.resolve('anything');
476
+ assert.strictEqual(result.resolution, 'not-found');
477
+ });
478
+
479
+ it('should handle empty caller namespace string', () => {
480
+ registry.register({
481
+ name: 'skill',
482
+ namespace: 'ns',
483
+ type: 'skill',
484
+ filePath: '/skill/SKILL.md',
485
+ source: 'test',
486
+ description: undefined,
487
+ metadata: {},
488
+ });
489
+
490
+ // Empty string is falsy, should fall through to shorthand
491
+ const result = resolver.resolve('skill', { callerNamespace: '' });
492
+ assert.strictEqual(result.resolution, 'shorthand');
493
+ });
494
+ });
495
+
496
+ describe('alias resolution', () => {
497
+ it('should resolve alias with alias result type', () => {
498
+ registry.register({
499
+ name: '3d-visualizer',
500
+ namespace: 'python-tools',
501
+ type: 'skill',
502
+ filePath: '/python-tools/3d-visualizer/SKILL.md',
503
+ source: 'plugin:python-tools',
504
+ description: '3D visualization',
505
+ metadata: {},
506
+ });
507
+ registry.registerAlias('py3d', 'python-tools:3d-visualizer');
508
+
509
+ const result = resolver.resolve('py3d');
510
+
511
+ assert.strictEqual(result.resolution, 'alias');
512
+ if (result.resolution !== 'alias') throw new Error('Type guard');
513
+
514
+ assert.strictEqual(result.requestedName, 'py3d');
515
+ assert.strictEqual(result.alias, 'py3d');
516
+ assert.strictEqual(result.canonicalName, 'python-tools:3d-visualizer');
517
+ assert.strictEqual(result.component.canonicalName, 'python-tools:3d-visualizer');
518
+ assert.strictEqual(result.component.type, 'skill');
519
+ });
520
+
521
+ it('should respect type filter in alias resolution', () => {
522
+ registry.register({
523
+ name: 'visualizer',
524
+ namespace: 'tools',
525
+ type: 'skill',
526
+ filePath: '/tools/visualizer/SKILL.md',
527
+ source: 'plugin:tools',
528
+ description: 'Visualizer skill',
529
+ metadata: {},
530
+ });
531
+ registry.registerAlias('viz', 'tools:visualizer');
532
+
533
+ // Type filter matches - should resolve
534
+ const skillResult = resolver.resolve('viz', undefined, 'skill');
535
+ assert.strictEqual(skillResult.resolution, 'alias');
536
+ if (skillResult.resolution !== 'alias') throw new Error('Type guard');
537
+ assert.strictEqual(skillResult.component.type, 'skill');
538
+
539
+ // Type filter doesn't match - should not resolve alias
540
+ const agentResult = resolver.resolve('viz', undefined, 'agent');
541
+ assert.strictEqual(agentResult.resolution, 'not-found');
542
+ });
543
+
544
+ it('should prioritize alias over shorthand (alias checked first)', () => {
545
+ // Register a component that could match as shorthand
546
+ registry.register({
547
+ name: 'shortcut',
548
+ namespace: 'other-plugin',
549
+ type: 'skill',
550
+ filePath: '/other/shortcut/SKILL.md',
551
+ source: 'plugin:other-plugin',
552
+ description: 'Other shortcut',
553
+ metadata: {},
554
+ });
555
+
556
+ // Register a different component with an alias using the same bare name
557
+ registry.register({
558
+ name: 'aliased-skill',
559
+ namespace: 'main-plugin',
560
+ type: 'skill',
561
+ filePath: '/main/aliased-skill/SKILL.md',
562
+ source: 'plugin:main-plugin',
563
+ description: 'Main skill',
564
+ metadata: {},
565
+ });
566
+ registry.registerAlias('shortcut', 'main-plugin:aliased-skill');
567
+
568
+ // 'shortcut' should resolve via alias, not shorthand
569
+ const result = resolver.resolve('shortcut');
570
+
571
+ assert.strictEqual(result.resolution, 'alias');
572
+ if (result.resolution !== 'alias') throw new Error('Type guard');
573
+
574
+ // Should point to the aliased target, not the shorthand match
575
+ assert.strictEqual(result.canonicalName, 'main-plugin:aliased-skill');
576
+ });
577
+
578
+ it('should prioritize alias over local-first (alias checked first)', () => {
579
+ // Register components in two namespaces
580
+ registry.register({
581
+ name: 'helper',
582
+ namespace: 'local-ns',
583
+ type: 'skill',
584
+ filePath: '/local-ns/helper/SKILL.md',
585
+ source: 'plugin:local-ns',
586
+ description: 'Local helper',
587
+ metadata: {},
588
+ });
589
+ registry.register({
590
+ name: 'aliased-helper',
591
+ namespace: 'alias-ns',
592
+ type: 'skill',
593
+ filePath: '/alias-ns/aliased-helper/SKILL.md',
594
+ source: 'plugin:alias-ns',
595
+ description: 'Aliased helper',
596
+ metadata: {},
597
+ });
598
+
599
+ // Create alias that shadows local namespace name
600
+ registry.registerAlias('helper', 'alias-ns:aliased-helper');
601
+
602
+ // Even with callerNamespace='local-ns', alias should win
603
+ const result = resolver.resolve('helper', { callerNamespace: 'local-ns' });
604
+
605
+ assert.strictEqual(result.resolution, 'alias');
606
+ if (result.resolution !== 'alias') throw new Error('Type guard');
607
+ assert.strictEqual(result.canonicalName, 'alias-ns:aliased-helper');
608
+ });
609
+
610
+ it('should include alias and canonicalName in result', () => {
611
+ registry.register({
612
+ name: 'code-review',
613
+ namespace: 'tools',
614
+ type: 'agent',
615
+ filePath: '/tools/code-review/AGENT.md',
616
+ source: 'plugin:tools',
617
+ description: 'Code review agent',
618
+ metadata: {},
619
+ });
620
+ registry.registerAlias('review', 'tools:code-review');
621
+
622
+ const result = resolver.resolve('review');
623
+
624
+ assert.strictEqual(result.resolution, 'alias');
625
+ if (result.resolution !== 'alias') throw new Error('Type guard');
626
+
627
+ // Both alias and canonicalName should be present
628
+ assert.strictEqual(result.alias, 'review');
629
+ assert.strictEqual(result.canonicalName, 'tools:code-review');
630
+ assert.strictEqual(result.component.canonicalName, 'tools:code-review');
631
+ });
632
+
633
+ it('should fall through to local-first/shorthand when alias does not exist', () => {
634
+ registry.register({
635
+ name: 'existing',
636
+ namespace: 'ns',
637
+ type: 'skill',
638
+ filePath: '/ns/existing/SKILL.md',
639
+ source: 'plugin:ns',
640
+ description: 'Existing skill',
641
+ metadata: {},
642
+ });
643
+
644
+ // No alias registered, should fall through to local-first
645
+ const result = resolver.resolve('existing', { callerNamespace: 'ns' });
646
+
647
+ assert.strictEqual(result.resolution, 'local-first');
648
+ if (result.resolution !== 'local-first') throw new Error('Type guard');
649
+ assert.strictEqual(result.component.canonicalName, 'ns:existing');
650
+ });
651
+
652
+ it('should fall through to shorthand when alias does not exist and no local match', () => {
653
+ registry.register({
654
+ name: 'unique',
655
+ namespace: 'plugin-a',
656
+ type: 'skill',
657
+ filePath: '/plugin-a/unique/SKILL.md',
658
+ source: 'plugin:plugin-a',
659
+ description: 'Unique skill',
660
+ metadata: {},
661
+ });
662
+
663
+ // No alias registered, no local match, should fall through to shorthand
664
+ const result = resolver.resolve('unique', { callerNamespace: 'other-ns' });
665
+
666
+ assert.strictEqual(result.resolution, 'shorthand');
667
+ if (result.resolution !== 'shorthand') throw new Error('Type guard');
668
+ assert.strictEqual(result.component.canonicalName, 'plugin-a:unique');
669
+ });
670
+ });
671
+ });