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,656 @@
1
+ import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
2
+ import { SettingsManager, getAgentDir } from "@gsd/pi-coding-agent";
3
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
4
+ import { basename, dirname, join, relative, resolve } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { PluginImporter, type ImportManifestEntry } from "./plugin-importer.js";
7
+ import type { NamespacedComponent } from "./namespaced-registry.js";
8
+
9
+ export interface ClaudeSkillCandidate {
10
+ type: "skill";
11
+ name: string;
12
+ path: string;
13
+ root: string;
14
+ sourceLabel: string;
15
+ }
16
+
17
+ export interface ClaudePluginCandidate {
18
+ type: "plugin";
19
+ name: string;
20
+ path: string;
21
+ root: string;
22
+ sourceLabel: string;
23
+ packageName?: string;
24
+ }
25
+
26
+ const SKIP_DIRS = new Set([
27
+ ".git",
28
+ "node_modules",
29
+ ".worktrees",
30
+ "dist",
31
+ "build",
32
+ ".next",
33
+ ".turbo",
34
+ "cache",
35
+ ".cache",
36
+ ]);
37
+
38
+ function uniqueExistingDirs(paths: string[]): string[] {
39
+ const seen = new Set<string>();
40
+ const out: string[] = [];
41
+ for (const candidate of paths) {
42
+ const resolvedPath = resolve(candidate);
43
+ if (seen.has(resolvedPath)) continue;
44
+ seen.add(resolvedPath);
45
+ if (existsSync(resolvedPath)) out.push(resolvedPath);
46
+ }
47
+ return out;
48
+ }
49
+
50
+ export function getClaudeSearchRoots(cwd: string): { skillRoots: string[]; pluginRoots: string[] } {
51
+ const home = homedir();
52
+ const parent = resolve(cwd, "..");
53
+ const grandparent = resolve(cwd, "..", "..");
54
+
55
+ // Claude Code user-scope skills live under ~/.claude/skills.
56
+ // Keep sibling/local clone fallbacks for developer workflows, but they are
57
+ // examples/convenience paths rather than the primary Claude storage model.
58
+ const skillRoots = uniqueExistingDirs([
59
+ join(home, ".claude", "skills"),
60
+ join(home, "repos", "claude_skills"),
61
+ join(home, "repos", "skills"),
62
+ join(parent, "claude_skills"),
63
+ join(parent, "skills"),
64
+ join(grandparent, "claude_skills"),
65
+ join(grandparent, "skills"),
66
+ ]);
67
+
68
+ // Anthropic docs model marketplaces as sources users add with
69
+ // `/plugin marketplace add ...`, and Claude stores those marketplaces under
70
+ // ~/.claude/plugins/marketplaces/. Installed plugin payloads are copied into
71
+ // ~/.claude/plugins/cache/. We prefer those stable Claude-managed locations
72
+ // before local example clones.
73
+ const pluginRoots = uniqueExistingDirs([
74
+ join(home, ".claude", "plugins", "marketplaces"),
75
+ join(home, ".claude", "plugins", "cache"),
76
+ join(home, ".claude", "plugins"),
77
+ join(home, "repos", "claude-plugins-official"),
78
+ join(home, "repos", "claude_skills"),
79
+ join(parent, "claude-plugins-official"),
80
+ join(parent, "claude_skills"),
81
+ join(grandparent, "claude-plugins-official"),
82
+ join(grandparent, "claude_skills"),
83
+ ]);
84
+
85
+ return { skillRoots, pluginRoots };
86
+ }
87
+
88
+ function sourceLabel(path: string): string {
89
+ const home = homedir();
90
+ if (path.startsWith(join(home, ".claude"))) return "claude-home";
91
+ if (path.startsWith(join(home, "repos"))) return "repos";
92
+ return "local";
93
+ }
94
+
95
+ /**
96
+ * Check if a path is a marketplace directory (contains .claude-plugin/marketplace.json).
97
+ * Marketplace paths use the PluginImporter flow; non-marketplace use the legacy flat flow.
98
+ */
99
+ function isMarketplacePath(pluginPath: string): boolean {
100
+ const marketplaceJson = join(pluginPath, ".claude-plugin", "marketplace.json");
101
+ return existsSync(marketplaceJson);
102
+ }
103
+
104
+ /**
105
+ * Detect which plugin roots are marketplaces and which are legacy flat paths.
106
+ */
107
+ function categorizePluginRoots(pluginRoots: string[]): { marketplaces: string[]; flat: string[] } {
108
+ const marketplaces: string[] = [];
109
+ const flat: string[] = [];
110
+
111
+ for (const root of pluginRoots) {
112
+ if (isMarketplacePath(root)) {
113
+ marketplaces.push(root);
114
+ } else {
115
+ flat.push(root);
116
+ }
117
+ }
118
+
119
+ return { marketplaces, flat };
120
+ }
121
+
122
+ function walkDirs(root: string, visit: (dir: string, depth: number) => void, maxDepth = 4): void {
123
+ function walk(dir: string, depth: number) {
124
+ visit(dir, depth);
125
+ if (depth >= maxDepth) return;
126
+ let entries: Array<{ name: string; isDirectory: () => boolean }> = [];
127
+ try {
128
+ entries = readdirSync(dir, { withFileTypes: true });
129
+ } catch {
130
+ return;
131
+ }
132
+ for (const entry of entries) {
133
+ if (!entry.isDirectory()) continue;
134
+ if (SKIP_DIRS.has(entry.name)) continue;
135
+ walk(join(dir, entry.name), depth + 1);
136
+ }
137
+ }
138
+ walk(root, 0);
139
+ }
140
+
141
+ export function discoverClaudeSkills(cwd: string): ClaudeSkillCandidate[] {
142
+ const { skillRoots } = getClaudeSearchRoots(cwd);
143
+ const results: ClaudeSkillCandidate[] = [];
144
+ const seen = new Set<string>();
145
+
146
+ for (const root of skillRoots) {
147
+ walkDirs(root, (dir) => {
148
+ const skillFile = join(dir, "SKILL.md");
149
+ if (!existsSync(skillFile)) return;
150
+ const resolvedDir = resolve(dir);
151
+ if (seen.has(resolvedDir)) return;
152
+ seen.add(resolvedDir);
153
+ results.push({
154
+ type: "skill",
155
+ name: basename(dir),
156
+ path: resolvedDir,
157
+ root,
158
+ sourceLabel: sourceLabel(root),
159
+ });
160
+ }, 5);
161
+ }
162
+
163
+ return results.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
164
+ }
165
+
166
+ export function discoverClaudePlugins(cwd: string): ClaudePluginCandidate[] {
167
+ const { pluginRoots } = getClaudeSearchRoots(cwd);
168
+ const results: ClaudePluginCandidate[] = [];
169
+ const seen = new Set<string>();
170
+
171
+ for (const root of pluginRoots) {
172
+ walkDirs(root, (dir) => {
173
+ const pkgPath = join(dir, "package.json");
174
+ if (!existsSync(pkgPath)) return;
175
+ const resolvedDir = resolve(dir);
176
+ if (seen.has(resolvedDir)) return;
177
+ seen.add(resolvedDir);
178
+ let packageName: string | undefined;
179
+ try {
180
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { name?: string };
181
+ packageName = pkg.name;
182
+ } catch {
183
+ packageName = undefined;
184
+ }
185
+ results.push({
186
+ type: "plugin",
187
+ name: packageName || basename(dir),
188
+ packageName,
189
+ path: resolvedDir,
190
+ root,
191
+ sourceLabel: sourceLabel(root),
192
+ });
193
+ }, 4);
194
+ }
195
+
196
+ return results.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
197
+ }
198
+
199
+ async function chooseMany<T extends { name: string; path: string; root: string; sourceLabel: string }>(
200
+ ctx: ExtensionCommandContext,
201
+ title: string,
202
+ candidates: T[],
203
+ ): Promise<T[]> {
204
+ if (candidates.length === 0) return [];
205
+
206
+ const mode = await ctx.ui.select(`${title} (${candidates.length} found)`, [
207
+ "Import all discovered",
208
+ "Select individually",
209
+ "Cancel",
210
+ ]);
211
+
212
+ if (!mode || mode === "Cancel") return [];
213
+ if (mode === "Import all discovered") return candidates;
214
+
215
+ const remaining = [...candidates];
216
+ const selected: T[] = [];
217
+ while (remaining.length > 0) {
218
+ const options = [
219
+ ...remaining.map((item) => `${item.name} — ${item.sourceLabel} — ${relative(item.root, item.path) || "."}`),
220
+ "Done selecting",
221
+ ];
222
+ const picked = await ctx.ui.select(`${title}: choose an item`, options);
223
+ if (!picked || picked === "Done selecting") break;
224
+ const pickedStr = Array.isArray(picked) ? picked[0] : picked;
225
+ if (!pickedStr) break;
226
+ const idx = options.indexOf(pickedStr);
227
+ if (idx < 0 || idx >= remaining.length) break;
228
+ selected.push(remaining[idx]!);
229
+ remaining.splice(idx, 1);
230
+ }
231
+ return selected;
232
+ }
233
+
234
+ function mergeStringList(existing: unknown, additions: string[]): string[] {
235
+ const list = Array.isArray(existing) ? existing.filter((v): v is string => typeof v === "string") : [];
236
+ const seen = new Set(list);
237
+ for (const item of additions) {
238
+ if (!seen.has(item)) {
239
+ list.push(item);
240
+ seen.add(item);
241
+ }
242
+ }
243
+ return list;
244
+ }
245
+
246
+ function mergePackageSources(existing: unknown, additions: string[]): Array<string | { source: string }> {
247
+ const current = Array.isArray(existing)
248
+ ? existing.filter((v): v is string | { source: string } => typeof v === "string" || (typeof v === "object" && v !== null && typeof (v as { source?: unknown }).source === "string"))
249
+ : [];
250
+
251
+ const seen = new Set(current.map((entry) => typeof entry === "string" ? entry : entry.source));
252
+ const merged = [...current];
253
+ for (const add of additions) {
254
+ if (!seen.has(add)) {
255
+ merged.push(add);
256
+ seen.add(add);
257
+ }
258
+ }
259
+ return merged;
260
+ }
261
+
262
+ // ============================================================================
263
+ // Marketplace PluginImporter Integration (T02)
264
+ // ============================================================================
265
+
266
+ /**
267
+ * Component candidate from marketplace discovery.
268
+ * Extends NamespacedComponent with UI-friendly fields.
269
+ */
270
+ interface MarketplaceComponentCandidate {
271
+ component: NamespacedComponent;
272
+ displayName: string;
273
+ pluginName: string;
274
+ }
275
+
276
+ /**
277
+ * Format a component for display in selection UI.
278
+ */
279
+ function formatComponentForSelection(comp: NamespacedComponent): string {
280
+ const typeLabel = comp.type === 'skill' ? '🔧' : '🤖';
281
+ const nsLabel = comp.namespace ? `${comp.namespace}:` : '';
282
+ return `${typeLabel} ${nsLabel}${comp.name}`;
283
+ }
284
+
285
+ /**
286
+ * Present marketplace components for user selection, grouped by plugin.
287
+ * Returns the selected components for import.
288
+ */
289
+ async function selectMarketplaceComponents(
290
+ ctx: ExtensionCommandContext,
291
+ importer: PluginImporter,
292
+ scope: "global" | "project"
293
+ ): Promise<NamespacedComponent[]> {
294
+ const plugins = importer.getDiscoveredPlugins();
295
+
296
+ if (plugins.length === 0) {
297
+ ctx.ui.notify("No plugins discovered in marketplace.", "info");
298
+ return [];
299
+ }
300
+
301
+ // Build component candidates grouped by plugin
302
+ const allComponents: MarketplaceComponentCandidate[] = [];
303
+ for (const plugin of plugins) {
304
+ const components = importer.selectComponents(c => c.namespace === plugin.canonicalName);
305
+ for (const comp of components) {
306
+ allComponents.push({
307
+ component: comp,
308
+ displayName: formatComponentForSelection(comp),
309
+ pluginName: plugin.canonicalName,
310
+ });
311
+ }
312
+ }
313
+
314
+ if (allComponents.length === 0) {
315
+ ctx.ui.notify("No components (skills/agents) found in marketplace plugins.", "info");
316
+ return [];
317
+ }
318
+
319
+ // Ask user for selection mode
320
+ const mode = await ctx.ui.select(
321
+ `Marketplace components → ${scope} config (${allComponents.length} found across ${plugins.length} plugins)`,
322
+ [
323
+ "Import all components",
324
+ "Select by plugin",
325
+ "Select individually",
326
+ "Cancel",
327
+ ]
328
+ );
329
+
330
+ if (!mode || mode === "Cancel") return [];
331
+
332
+ if (mode === "Import all components") {
333
+ return allComponents.map(c => c.component);
334
+ }
335
+
336
+ if (mode === "Select by plugin") {
337
+ // Let user select plugins, then import all their components
338
+ const pluginNames = plugins.map(p => p.canonicalName);
339
+ const selectedPluginNames: string[] = [];
340
+
341
+ while (true) {
342
+ const remaining = pluginNames.filter(n => !selectedPluginNames.includes(n));
343
+ if (remaining.length === 0) break;
344
+
345
+ const options = [...remaining, "Done selecting"];
346
+ const picked = await ctx.ui.select("Select a plugin to import all its components", options);
347
+
348
+ if (!picked || picked === "Done selecting") break;
349
+ const pickedStr = Array.isArray(picked) ? picked[0] : picked;
350
+ if (!pickedStr) break;
351
+ selectedPluginNames.push(pickedStr);
352
+ }
353
+
354
+ return allComponents
355
+ .filter(c => selectedPluginNames.includes(c.pluginName))
356
+ .map(c => c.component);
357
+ }
358
+
359
+ // Select individually
360
+ const remaining = [...allComponents];
361
+ const selected: NamespacedComponent[] = [];
362
+
363
+ while (remaining.length > 0) {
364
+ const options = remaining.map(c =>
365
+ `${c.displayName} — ${c.pluginName}`
366
+ );
367
+ options.push("Done selecting");
368
+
369
+ const picked = await ctx.ui.select("Select a component to import", options);
370
+ if (!picked || picked === "Done selecting") break;
371
+ const pickedStr = Array.isArray(picked) ? picked[0] : picked;
372
+ if (!pickedStr) break;
373
+
374
+ const idx = options.indexOf(pickedStr);
375
+ if (idx < 0 || idx >= remaining.length) break;
376
+
377
+ selected.push(remaining[idx]!.component);
378
+ remaining.splice(idx, 1);
379
+ }
380
+
381
+ return selected;
382
+ }
383
+
384
+ /**
385
+ * Format diagnostics for display to user.
386
+ * Returns a human-readable summary string.
387
+ */
388
+ function formatDiagnosticsForUser(
389
+ diagnostics: Array<{ severity: string; class: string; remediation: string; involvedCanonicalNames: string[] }>
390
+ ): string {
391
+ const lines: string[] = [];
392
+
393
+ const errors = diagnostics.filter(d => d.severity === 'error');
394
+ const warnings = diagnostics.filter(d => d.severity === 'warning');
395
+
396
+ if (errors.length > 0) {
397
+ lines.push(`❌ ${errors.length} error(s) blocking import:`);
398
+ for (const err of errors) {
399
+ lines.push(` - ${err.class}: ${err.involvedCanonicalNames.join(', ')}`);
400
+ lines.push(` ${err.remediation}`);
401
+ }
402
+ }
403
+
404
+ if (warnings.length > 0) {
405
+ lines.push(`⚠️ ${warnings.length} warning(s):`);
406
+ for (const warn of warnings) {
407
+ lines.push(` - ${warn.class}: ${warn.involvedCanonicalNames.join(', ')}`);
408
+ }
409
+ }
410
+
411
+ return lines.join('\n');
412
+ }
413
+
414
+ /**
415
+ * Persist import manifest entries to settings.
416
+ * Maps manifest entries to the appropriate settings format.
417
+ */
418
+ function persistManifestToSettings(
419
+ manifestEntries: ImportManifestEntry[],
420
+ settingsManager: SettingsManager,
421
+ scope: "global" | "project"
422
+ ): void {
423
+ // Group entries by namespace for organized persistence
424
+ const skillPaths = manifestEntries
425
+ .filter(e => e.type === 'skill')
426
+ .map(e => e.filePath);
427
+
428
+ const agentPaths = manifestEntries
429
+ .filter(e => e.type === 'agent')
430
+ .map(e => e.filePath);
431
+
432
+ // For marketplace plugins, we also want to store plugin-level metadata
433
+ // Currently this adds component paths to skills/agents lists
434
+ // Future enhancement: store canonical names with metadata
435
+
436
+ if (skillPaths.length > 0) {
437
+ if (scope === "project") {
438
+ settingsManager.setProjectSkillPaths(
439
+ mergeStringList(settingsManager.getProjectSettings().skills, skillPaths)
440
+ );
441
+ } else {
442
+ settingsManager.setSkillPaths(
443
+ mergeStringList(settingsManager.getGlobalSettings().skills, skillPaths)
444
+ );
445
+ }
446
+ }
447
+
448
+ // Do not persist imported marketplace agents into settings.packages.
449
+ // Claude plugin agent directories contain markdown agent definitions, not loadable Pi
450
+ // extension packages. Writing `.../agents` paths into packages makes startup treat
451
+ // them as extension roots and produces module-load errors.
452
+ //
453
+ // For now, marketplace agents remain discoverable via the import manifest and
454
+ // canonical metadata, but are not persisted into package sources.
455
+ }
456
+
457
+
458
+ export async function runClaudeImportFlow(
459
+ ctx: ExtensionCommandContext,
460
+ scope: "global" | "project",
461
+ readPrefs: () => Record<string, unknown>,
462
+ writePrefs: (prefs: Record<string, unknown>) => Promise<void>,
463
+ ): Promise<void> {
464
+ const cwd = process.cwd();
465
+ const settingsManager = SettingsManager.create(cwd, getAgentDir());
466
+ const { skillRoots, pluginRoots } = getClaudeSearchRoots(cwd);
467
+
468
+ // Categorize plugin roots into marketplaces vs flat paths
469
+ const { marketplaces, flat } = categorizePluginRoots(pluginRoots);
470
+
471
+ // Determine import mode
472
+ const assetChoice = await ctx.ui.select("Import Claude assets into GSD/Pi config", [
473
+ "Skills + plugins",
474
+ "Skills only",
475
+ "Plugins only",
476
+ "Cancel",
477
+ ]);
478
+ if (!assetChoice || assetChoice === "Cancel") return;
479
+
480
+ const importSkills = assetChoice !== "Plugins only";
481
+ const importPlugins = assetChoice !== "Skills only";
482
+
483
+ // Track what we're importing
484
+ let importedSkillsCount = 0;
485
+ let importedPluginsCount = 0;
486
+ let importedMarketplaceComponents = 0;
487
+ const canonicalNamesPersisted: string[] = [];
488
+
489
+ // ========== SKILLS (legacy flat flow) ==========
490
+ if (importSkills) {
491
+ const discoveredSkills = discoverClaudeSkills(cwd);
492
+ const selectedSkills = await chooseMany(ctx, `Claude skills → ${scope} preferences`, discoveredSkills);
493
+
494
+ if (selectedSkills.length > 0) {
495
+ const prefMode = await ctx.ui.select("How should GSD treat the imported skills?", [
496
+ "Always use when relevant",
497
+ "Prefer when relevant",
498
+ "Do not modify skill preferences",
499
+ ]);
500
+
501
+ const prefs = readPrefs();
502
+ const skillPaths = selectedSkills.map((skill) => skill.path);
503
+ if (prefMode === "Always use when relevant") {
504
+ prefs.always_use_skills = mergeStringList(prefs.always_use_skills, skillPaths);
505
+ } else if (prefMode === "Prefer when relevant") {
506
+ prefs.prefer_skills = mergeStringList(prefs.prefer_skills, skillPaths);
507
+ }
508
+
509
+ await writePrefs(prefs);
510
+
511
+ if (scope === "project") {
512
+ settingsManager.setProjectSkillPaths(mergeStringList(settingsManager.getProjectSettings().skills, skillPaths));
513
+ } else {
514
+ settingsManager.setSkillPaths(mergeStringList(settingsManager.getGlobalSettings().skills, skillPaths));
515
+ }
516
+
517
+ importedSkillsCount = selectedSkills.length;
518
+ }
519
+ }
520
+
521
+ // ========== MARKETPLACE PLUGINS (new PluginImporter flow) ==========
522
+ if (importPlugins && marketplaces.length > 0) {
523
+ const marketplaceChoice = await ctx.ui.select(
524
+ `Found ${marketplaces.length} marketplace(s). Import from marketplace?`,
525
+ [
526
+ "Yes - discover plugins and select components",
527
+ "Skip marketplaces (use legacy plugin paths only)",
528
+ "Cancel",
529
+ ]
530
+ );
531
+
532
+ if (marketplaceChoice === "Yes - discover plugins and select components") {
533
+ // Instantiate PluginImporter and discover
534
+ const importer = new PluginImporter();
535
+ const discovery = importer.discover(marketplaces);
536
+
537
+ if (discovery.summary.totalPlugins > 0) {
538
+ // Present components for selection
539
+ const selectedComponents = await selectMarketplaceComponents(ctx, importer, scope);
540
+
541
+ if (selectedComponents.length > 0) {
542
+ // Run validation (pre-import diagnostics)
543
+ const validation = importer.validateImport(selectedComponents);
544
+
545
+ // Show diagnostics
546
+ if (validation.diagnostics.length > 0) {
547
+ const diagMessage = formatDiagnosticsForUser(validation.diagnostics);
548
+ ctx.ui.notify(diagMessage, validation.canProceed ? "warning" : "error");
549
+
550
+ // Block if errors exist
551
+ if (!validation.canProceed) {
552
+ ctx.ui.notify(
553
+ "Import blocked due to canonical name conflicts. Please resolve the errors above.",
554
+ "error"
555
+ );
556
+ return;
557
+ }
558
+
559
+ // Warn but allow proceed for warnings
560
+ const proceed = await ctx.ui.select(
561
+ "Warnings detected. Continue with import?",
562
+ ["Yes, continue", "Cancel"]
563
+ );
564
+ if (proceed !== "Yes, continue") {
565
+ return;
566
+ }
567
+ }
568
+
569
+ // Generate manifest and persist
570
+ const manifest = importer.getImportManifest(selectedComponents);
571
+ persistManifestToSettings(manifest.entries, settingsManager, scope);
572
+
573
+ importedMarketplaceComponents = selectedComponents.length;
574
+ canonicalNamesPersisted.push(...manifest.entries.map(e => e.canonicalName));
575
+ }
576
+ } else {
577
+ ctx.ui.notify(`No plugins discovered in ${marketplaces.length} marketplace(s).`, "info");
578
+ }
579
+ }
580
+ }
581
+
582
+ // ========== FLAT PLUGIN PATHS (legacy flow) ==========
583
+ if (importPlugins && flat.length > 0) {
584
+ // Use legacy discovery for non-marketplace paths
585
+ const discoveredPlugins: ClaudePluginCandidate[] = [];
586
+ const seen = new Set<string>();
587
+
588
+ for (const root of flat) {
589
+ walkDirs(root, (dir) => {
590
+ const pkgPath = join(dir, "package.json");
591
+ if (!existsSync(pkgPath)) return;
592
+ const resolvedDir = resolve(dir);
593
+ if (seen.has(resolvedDir)) return;
594
+ seen.add(resolvedDir);
595
+ let packageName: string | undefined;
596
+ try {
597
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { name?: string };
598
+ packageName = pkg.name;
599
+ } catch {
600
+ packageName = undefined;
601
+ }
602
+ discoveredPlugins.push({
603
+ type: "plugin",
604
+ name: packageName || basename(dir),
605
+ packageName,
606
+ path: resolvedDir,
607
+ root,
608
+ sourceLabel: sourceLabel(root),
609
+ });
610
+ }, 4);
611
+ }
612
+
613
+ const sortedPlugins = discoveredPlugins.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
614
+ const selectedPlugins = await chooseMany(ctx, `Claude plugins/packages → ${scope} Pi settings`, sortedPlugins);
615
+
616
+ if (selectedPlugins.length > 0) {
617
+ const pluginPaths = selectedPlugins.map((plugin) => plugin.path);
618
+ if (scope === "project") {
619
+ settingsManager.setProjectPackages(mergePackageSources(settingsManager.getProjectSettings().packages, pluginPaths));
620
+ } else {
621
+ settingsManager.setPackages(mergePackageSources(settingsManager.getGlobalSettings().packages, pluginPaths));
622
+ }
623
+ importedPluginsCount = selectedPlugins.length;
624
+ }
625
+ }
626
+
627
+ // ========== FINAL SUMMARY ==========
628
+ if (importedSkillsCount === 0 && importedPluginsCount === 0 && importedMarketplaceComponents === 0) {
629
+ ctx.ui.notify("Claude import cancelled or nothing selected.", "info");
630
+ return;
631
+ }
632
+
633
+ await ctx.waitForIdle();
634
+ await ctx.reload();
635
+
636
+ const lines = [
637
+ `Imported Claude assets into ${scope} config:`,
638
+ `- Skills (flat): ${importedSkillsCount}`,
639
+ `- Plugins (flat paths): ${importedPluginsCount}`,
640
+ `- Marketplace components: ${importedMarketplaceComponents}`,
641
+ ];
642
+ if (importedSkillsCount > 0) {
643
+ lines.push(`- Skill paths added to Pi settings (${scope}) for availability`);
644
+ lines.push(`- Skill refs added to GSD preferences (${scope}) when selected`);
645
+ }
646
+ if (importedPluginsCount > 0) {
647
+ lines.push(`- Plugin/package paths added to Pi settings (${scope}) packages`);
648
+ }
649
+ if (importedMarketplaceComponents > 0) {
650
+ lines.push(`- Canonical names preserved: ${canonicalNamesPersisted.length} entries`);
651
+ if (canonicalNamesPersisted.length <= 10) {
652
+ lines.push(` Names: ${canonicalNamesPersisted.join(', ')}`);
653
+ }
654
+ }
655
+ ctx.ui.notify(lines.join("\n"), "info");
656
+ }