@inharness-ai/claude4spec 1.0.21 → 1.0.22

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 (241) hide show
  1. package/.claude/skills/c4s-brief-implementer/SKILL.md +120 -53
  2. package/CHANGELOG.md +13 -0
  3. package/dist/bin/c4s/commands/agent.d.ts +18 -0
  4. package/dist/bin/c4s/commands/agent.js +102 -0
  5. package/dist/bin/c4s/commands/agent.js.map +1 -0
  6. package/dist/bin/c4s/commands/ask.d.ts +9 -8
  7. package/dist/bin/c4s/commands/ask.js +23 -31
  8. package/dist/bin/c4s/commands/ask.js.map +1 -1
  9. package/dist/bin/c4s/commands/catalog.d.ts +1 -1
  10. package/dist/bin/c4s/commands/catalog.js +2 -2
  11. package/dist/bin/c4s/commands/catalog.js.map +1 -1
  12. package/dist/bin/c4s/commands/describe.d.ts +1 -1
  13. package/dist/bin/c4s/commands/describe.js +2 -2
  14. package/dist/bin/c4s/commands/describe.js.map +1 -1
  15. package/dist/bin/c4s/commands/detail.d.ts +1 -1
  16. package/dist/bin/c4s/commands/detail.js +2 -2
  17. package/dist/bin/c4s/commands/detail.js.map +1 -1
  18. package/dist/bin/c4s/commands/element-list.d.ts +1 -1
  19. package/dist/bin/c4s/commands/element-list.js +2 -2
  20. package/dist/bin/c4s/commands/element-list.js.map +1 -1
  21. package/dist/bin/c4s/commands/find-references.js +1 -1
  22. package/dist/bin/c4s/commands/find-references.js.map +1 -1
  23. package/dist/bin/c4s/commands/inline-mention.d.ts +1 -1
  24. package/dist/bin/c4s/commands/inline-mention.js +2 -2
  25. package/dist/bin/c4s/commands/inline-mention.js.map +1 -1
  26. package/dist/bin/c4s/commands/list-slugs.d.ts +1 -1
  27. package/dist/bin/c4s/commands/list-slugs.js +2 -2
  28. package/dist/bin/c4s/commands/list-slugs.js.map +1 -1
  29. package/dist/bin/c4s/commands/list-tags.d.ts +1 -1
  30. package/dist/bin/c4s/commands/list-tags.js +2 -2
  31. package/dist/bin/c4s/commands/list-tags.js.map +1 -1
  32. package/dist/bin/c4s/commands/plugins.d.ts +9 -0
  33. package/dist/bin/c4s/commands/plugins.js +81 -0
  34. package/dist/bin/c4s/commands/plugins.js.map +1 -0
  35. package/dist/bin/c4s/commands/resolve.d.ts +1 -1
  36. package/dist/bin/c4s/commands/resolve.js +2 -2
  37. package/dist/bin/c4s/commands/resolve.js.map +1 -1
  38. package/dist/bin/c4s/commands/single-element.d.ts +1 -1
  39. package/dist/bin/c4s/commands/single-element.js +2 -2
  40. package/dist/bin/c4s/commands/single-element.js.map +1 -1
  41. package/dist/bin/c4s/commands/tagged-list-mixed.d.ts +1 -1
  42. package/dist/bin/c4s/commands/tagged-list-mixed.js +2 -2
  43. package/dist/bin/c4s/commands/tagged-list-mixed.js.map +1 -1
  44. package/dist/bin/c4s/commands/tagged-list.d.ts +1 -1
  45. package/dist/bin/c4s/commands/tagged-list.js +2 -2
  46. package/dist/bin/c4s/commands/tagged-list.js.map +1 -1
  47. package/dist/bin/c4s/context.d.ts +3 -3
  48. package/dist/bin/c4s/context.js +7 -4
  49. package/dist/bin/c4s/context.js.map +1 -1
  50. package/dist/bin/c4s/errors.d.ts +1 -1
  51. package/dist/bin/c4s/errors.js.map +1 -1
  52. package/dist/bin/c4s-mcp.js +6 -2
  53. package/dist/bin/c4s-mcp.js.map +1 -1
  54. package/dist/bin/c4s.js +19 -4
  55. package/dist/bin/c4s.js.map +1 -1
  56. package/dist/bin/gitignore.js +7 -11
  57. package/dist/bin/gitignore.js.map +1 -1
  58. package/dist/client/assets/{arc-Coa9fbDO.js → arc-C9oTB3b1.js} +1 -1
  59. package/dist/client/assets/{architectureDiagram-3BPJPVTR-VJZcRMEN.js → architectureDiagram-3BPJPVTR-Bno5ViqK.js} +1 -1
  60. package/dist/client/assets/{blockDiagram-GPEHLZMM-C7dU2lW1.js → blockDiagram-GPEHLZMM-CJYBoeWQ.js} +1 -1
  61. package/dist/client/assets/{c4Diagram-AAUBKEIU-CG_BBm_5.js → c4Diagram-AAUBKEIU-DaaqWE6s.js} +1 -1
  62. package/dist/client/assets/channel-Cei_E79M.js +1 -0
  63. package/dist/client/assets/{chunk-2J33WTMH-BnS95YpK.js → chunk-2J33WTMH-P_I0OPMy.js} +1 -1
  64. package/dist/client/assets/{chunk-4BX2VUAB-COr8w3X3.js → chunk-4BX2VUAB-CCySMlh5.js} +1 -1
  65. package/dist/client/assets/{chunk-55IACEB6-CxrIbZhB.js → chunk-55IACEB6-CEUyqKGF.js} +1 -1
  66. package/dist/client/assets/{chunk-727SXJPM-DRchPPRe.js → chunk-727SXJPM-CoTSYOVz.js} +1 -1
  67. package/dist/client/assets/{chunk-AQP2D5EJ-_SfN7mED.js → chunk-AQP2D5EJ-DB0nMxPr.js} +1 -1
  68. package/dist/client/assets/{chunk-FMBD7UC4-p2tIiP1D.js → chunk-FMBD7UC4-PG3m6-_a.js} +1 -1
  69. package/dist/client/assets/{chunk-ND2GUHAM-B1mbl2hP.js → chunk-ND2GUHAM-CBNk6IaV.js} +1 -1
  70. package/dist/client/assets/{chunk-QZHKN3VN-DBUpeqvS.js → chunk-QZHKN3VN-CLFa0WvN.js} +1 -1
  71. package/dist/client/assets/classDiagram-4FO5ZUOK-DUe_fAto.js +1 -0
  72. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DUe_fAto.js +1 -0
  73. package/dist/client/assets/{cose-bilkent-S5V4N54A-BBsY5aKD.js → cose-bilkent-S5V4N54A-BDwDDxHN.js} +1 -1
  74. package/dist/client/assets/{dagre-BM42HDAG-b-vu4RC9.js → dagre-BM42HDAG-DR-1e8N5.js} +1 -1
  75. package/dist/client/assets/{diagram-2AECGRRQ-j-DW9qSf.js → diagram-2AECGRRQ-C-tHgBWt.js} +1 -1
  76. package/dist/client/assets/{diagram-5GNKFQAL-DHX_WYWQ.js → diagram-5GNKFQAL-uLmoKaJL.js} +1 -1
  77. package/dist/client/assets/{diagram-KO2AKTUF-DxdQok_M.js → diagram-KO2AKTUF-8Fd-xuOy.js} +1 -1
  78. package/dist/client/assets/{diagram-LMA3HP47-BbTIX5c1.js → diagram-LMA3HP47-C1gniWsn.js} +1 -1
  79. package/dist/client/assets/{diagram-OG6HWLK6-BQ9ODAlH.js → diagram-OG6HWLK6-BMWjZbeC.js} +1 -1
  80. package/dist/client/assets/{erDiagram-TEJ5UH35-6lwAL1y9.js → erDiagram-TEJ5UH35-CIUO8hrR.js} +1 -1
  81. package/dist/client/assets/{flowDiagram-I6XJVG4X-ioSHb5-t.js → flowDiagram-I6XJVG4X-Ccw5YEf3.js} +1 -1
  82. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CXFQp9Nj.js → ganttDiagram-6RSMTGT7-BAvlf8cY.js} +1 -1
  83. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-C1pVD_NQ.js → gitGraphDiagram-PVQCEYII-ChCkdYar.js} +1 -1
  84. package/dist/client/assets/{index-JaAxQJXh.js → index-BotCWOKC.js} +1 -1
  85. package/dist/client/assets/index-CtdzGyqj.js +805 -0
  86. package/dist/client/assets/{index-hz5T_W2e.js → index-DtpKHGD1.js} +1 -1
  87. package/dist/client/assets/{infoDiagram-5YYISTIA-C2Guev-g.js → infoDiagram-5YYISTIA-CZMGwNyB.js} +1 -1
  88. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-D3v8VBb2.js → ishikawaDiagram-YF4QCWOH-CubjZGk6.js} +1 -1
  89. package/dist/client/assets/{journeyDiagram-JHISSGLW-DX1AW_Ap.js → journeyDiagram-JHISSGLW-DzwOyVzB.js} +1 -1
  90. package/dist/client/assets/{kanban-definition-UN3LZRKU-CzXThvq2.js → kanban-definition-UN3LZRKU-CXznoTY9.js} +1 -1
  91. package/dist/client/assets/{linear-CVw4gl5i.js → linear-EDwTFRrU.js} +1 -1
  92. package/dist/client/assets/{mermaid.core-C29eoXRj.js → mermaid.core-Lae2097R.js} +4 -4
  93. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DEEGVQVA.js → mindmap-definition-RKZ34NQL-DFjIW41g.js} +1 -1
  94. package/dist/client/assets/{pieDiagram-4H26LBE5-Dme-VM6m.js → pieDiagram-4H26LBE5-CZDLR1jd.js} +1 -1
  95. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CURFNo24.js → quadrantDiagram-W4KKPZXB-CTGDzbqp.js} +1 -1
  96. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DxMZWXME.js → requirementDiagram-4Y6WPE33-CeH0NbMv.js} +1 -1
  97. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BW64D4_P.js → sankeyDiagram-5OEKKPKP-B6ZTjurV.js} +1 -1
  98. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BATRPm8Q.js → sequenceDiagram-3UESZ5HK-DcTRztVx.js} +1 -1
  99. package/dist/client/assets/{stateDiagram-AJRCARHV-pgHVMnO3.js → stateDiagram-AJRCARHV-nUgRVZSA.js} +1 -1
  100. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D9pJuQbI.js +1 -0
  101. package/dist/client/assets/{timeline-definition-PNZ67QCA-qBxJZPWd.js → timeline-definition-PNZ67QCA-CXm5n4rV.js} +1 -1
  102. package/dist/client/assets/{vennDiagram-CIIHVFJN-BZJK9hqJ.js → vennDiagram-CIIHVFJN-EmNJBYNd.js} +1 -1
  103. package/dist/client/assets/{wardley-L42UT6IY-BobtgEXg.js → wardley-L42UT6IY-MLrlp8H-.js} +1 -1
  104. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DuWfX_-P.js → wardleyDiagram-YWT4CUSO-5QoES25n.js} +1 -1
  105. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DCw1iURo.js → xychartDiagram-2RQKCTM6-CbDEQbY0.js} +1 -1
  106. package/dist/client/index.html +1 -1
  107. package/dist/core/agent/run-agent.d.ts +61 -0
  108. package/dist/core/agent/run-agent.js +212 -0
  109. package/dist/core/agent/run-agent.js.map +1 -0
  110. package/dist/core/ask/run-ask.d.ts +1 -1
  111. package/dist/core/ask/run-ask.js +14 -5
  112. package/dist/core/ask/run-ask.js.map +1 -1
  113. package/dist/core/workspace/resolve.d.ts +2 -0
  114. package/dist/core/workspace/resolve.js +2 -1
  115. package/dist/core/workspace/resolve.js.map +1 -1
  116. package/dist/plugin-types/ambient.d.ts +30 -0
  117. package/dist/plugin-types/plugin-runtime.d.ts +172 -0
  118. package/dist/plugin-types/ui.d.ts +154 -0
  119. package/dist/server/config.d.ts +12 -0
  120. package/dist/server/config.js +26 -0
  121. package/dist/server/config.js.map +1 -1
  122. package/dist/server/core/plugin-host/__fixtures__/plugins.d.ts +14 -0
  123. package/dist/server/core/plugin-host/__fixtures__/plugins.js +56 -0
  124. package/dist/server/core/plugin-host/__fixtures__/plugins.js.map +1 -0
  125. package/dist/server/core/plugin-host/base-packages.d.ts +14 -0
  126. package/dist/server/core/plugin-host/base-packages.js +26 -0
  127. package/dist/server/core/plugin-host/base-packages.js.map +1 -0
  128. package/dist/server/core/plugin-host/cache-bust.d.ts +10 -0
  129. package/dist/server/core/plugin-host/cache-bust.js +21 -0
  130. package/dist/server/core/plugin-host/cache-bust.js.map +1 -0
  131. package/dist/server/core/plugin-host/cli-engine.d.ts +7 -2
  132. package/dist/server/core/plugin-host/cli-engine.js +11 -3
  133. package/dist/server/core/plugin-host/cli-engine.js.map +1 -1
  134. package/dist/server/core/plugin-host/cli-plugins.d.ts +23 -0
  135. package/dist/server/core/plugin-host/cli-plugins.js +49 -0
  136. package/dist/server/core/plugin-host/cli-plugins.js.map +1 -0
  137. package/dist/server/core/plugin-host/cross-cutting.d.ts +41 -4
  138. package/dist/server/core/plugin-host/cross-cutting.js +52 -4
  139. package/dist/server/core/plugin-host/cross-cutting.js.map +1 -1
  140. package/dist/server/core/plugin-host/frontend-assets.d.ts +100 -0
  141. package/dist/server/core/plugin-host/frontend-assets.js +262 -0
  142. package/dist/server/core/plugin-host/frontend-assets.js.map +1 -0
  143. package/dist/server/core/plugin-host/frontend-manifest.d.ts +33 -0
  144. package/dist/server/core/plugin-host/frontend-manifest.js +52 -0
  145. package/dist/server/core/plugin-host/frontend-manifest.js.map +1 -0
  146. package/dist/server/core/plugin-host/loader.d.ts +107 -0
  147. package/dist/server/core/plugin-host/loader.js +196 -0
  148. package/dist/server/core/plugin-host/loader.js.map +1 -0
  149. package/dist/server/core/plugin-host/manifest-adapter.d.ts +30 -0
  150. package/dist/server/core/plugin-host/manifest-adapter.js +119 -0
  151. package/dist/server/core/plugin-host/manifest-adapter.js.map +1 -0
  152. package/dist/server/core/plugin-host/overlay-loader.d.ts +60 -0
  153. package/dist/server/core/plugin-host/overlay-loader.js +243 -0
  154. package/dist/server/core/plugin-host/overlay-loader.js.map +1 -0
  155. package/dist/server/core/plugin-host/plugin-watcher.d.ts +41 -0
  156. package/dist/server/core/plugin-host/plugin-watcher.js +95 -0
  157. package/dist/server/core/plugin-host/plugin-watcher.js.map +1 -0
  158. package/dist/server/core/plugin-host/project-host.d.ts +8 -2
  159. package/dist/server/core/plugin-host/project-host.js +56 -6
  160. package/dist/server/core/plugin-host/project-host.js.map +1 -1
  161. package/dist/server/core/plugin-host/registry.d.ts +26 -2
  162. package/dist/server/core/plugin-host/registry.js +107 -2
  163. package/dist/server/core/plugin-host/registry.js.map +1 -1
  164. package/dist/server/core/plugin-host/runtime-shims.d.ts +26 -0
  165. package/dist/server/core/plugin-host/runtime-shims.js +96 -0
  166. package/dist/server/core/plugin-host/runtime-shims.js.map +1 -0
  167. package/dist/server/core/plugin-host/types.d.ts +105 -3
  168. package/dist/server/db/migrations/042_chat_thread_drop_context_type_check.sql +61 -0
  169. package/dist/server/external-skills/brief-implementer-template.d.ts +1 -1
  170. package/dist/server/external-skills/brief-implementer-template.js +5 -2
  171. package/dist/server/external-skills/brief-implementer-template.js.map +1 -1
  172. package/dist/server/external-skills/spec-reader-template.d.ts +1 -1
  173. package/dist/server/external-skills/spec-reader-template.js +14 -0
  174. package/dist/server/external-skills/spec-reader-template.js.map +1 -1
  175. package/dist/server/index.js +82 -1
  176. package/dist/server/index.js.map +1 -1
  177. package/dist/server/mcp/c4s-tools.d.ts +6 -1
  178. package/dist/server/mcp/c4s-tools.js +18 -13
  179. package/dist/server/mcp/c4s-tools.js.map +1 -1
  180. package/dist/server/plugin-runtime/index.d.ts +17 -0
  181. package/dist/server/plugin-runtime/index.js +16 -0
  182. package/dist/server/plugin-runtime/index.js.map +1 -0
  183. package/dist/server/plugin-runtime/ui.d.ts +15 -0
  184. package/dist/server/plugin-runtime/ui.js +15 -0
  185. package/dist/server/plugin-runtime/ui.js.map +1 -0
  186. package/dist/server/routes/agent-turn.d.ts +8 -1
  187. package/dist/server/routes/agent-turn.js +67 -39
  188. package/dist/server/routes/agent-turn.js.map +1 -1
  189. package/dist/server/routes/config.d.ts +8 -0
  190. package/dist/server/routes/config.js +41 -3
  191. package/dist/server/routes/config.js.map +1 -1
  192. package/dist/server/routes/plugins.d.ts +43 -0
  193. package/dist/server/routes/plugins.js +93 -0
  194. package/dist/server/routes/plugins.js.map +1 -0
  195. package/dist/server/routes/threads.js +21 -3
  196. package/dist/server/routes/threads.js.map +1 -1
  197. package/dist/server/serialization/registerAll.d.ts +4 -0
  198. package/dist/server/serialization/registerAll.js +4 -2
  199. package/dist/server/serialization/registerAll.js.map +1 -1
  200. package/dist/server/services/chat-context.d.ts +34 -0
  201. package/dist/server/services/chat-context.js +58 -15
  202. package/dist/server/services/chat-context.js.map +1 -1
  203. package/dist/server/services/chat.d.ts +6 -0
  204. package/dist/server/services/chat.js +18 -2
  205. package/dist/server/services/chat.js.map +1 -1
  206. package/dist/server/services/entity-writer.d.ts +2 -3
  207. package/dist/server/services/entity-writer.js.map +1 -1
  208. package/dist/server/services/skill-registry.d.ts +31 -2
  209. package/dist/server/services/skill-registry.js +73 -1
  210. package/dist/server/services/skill-registry.js.map +1 -1
  211. package/dist/server/workspace/context-cache.d.ts +9 -0
  212. package/dist/server/workspace/context-cache.js +14 -0
  213. package/dist/server/workspace/context-cache.js.map +1 -1
  214. package/dist/server/workspace/project-context.d.ts +3 -0
  215. package/dist/server/workspace/project-context.js +106 -4
  216. package/dist/server/workspace/project-context.js.map +1 -1
  217. package/dist/server/workspace/registry.d.ts +28 -1
  218. package/dist/server/workspace/registry.js +57 -1
  219. package/dist/server/workspace/registry.js.map +1 -1
  220. package/dist/server/workspace/types.d.ts +15 -0
  221. package/dist/shared/entities.d.ts +4 -1
  222. package/dist/shared/entities.js.map +1 -1
  223. package/dist/shared/plugin-host/frontend-manifest.d.ts +56 -0
  224. package/dist/shared/plugin-host/frontend-manifest.js +89 -0
  225. package/dist/shared/plugin-host/frontend-manifest.js.map +1 -0
  226. package/dist/shared/plugin-host/host-api.d.ts +60 -0
  227. package/dist/shared/plugin-host/host-api.js +67 -0
  228. package/dist/shared/plugin-host/host-api.js.map +1 -0
  229. package/dist/shared/plugin-host/manifest.d.ts +194 -0
  230. package/dist/shared/plugin-host/manifest.js +52 -0
  231. package/dist/shared/plugin-host/manifest.js.map +1 -0
  232. package/dist/shared/plugin-host/ui-kit-surface.d.ts +22 -0
  233. package/dist/shared/plugin-host/ui-kit-surface.js +25 -0
  234. package/dist/shared/plugin-host/ui-kit-surface.js.map +1 -0
  235. package/dist/shared/types.d.ts +5 -0
  236. package/package.json +18 -3
  237. package/dist/client/assets/channel-D5gzBvo7.js +0 -1
  238. package/dist/client/assets/classDiagram-4FO5ZUOK-CT-amIxa.js +0 -1
  239. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CT-amIxa.js +0 -1
  240. package/dist/client/assets/index-DyAnw7YN.js +0 -798
  241. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-szm_LIuD.js +0 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * M33 plugin loader — the single mechanism that turns workspace-declared npm
3
+ * packages into registered host capabilities, run identically from every entry
4
+ * point (server, `c4s`, `c4s-mcp`, `c4s-reader`).
5
+ *
6
+ * Four steps, per package, each isolated in its own try/catch so a single bad
7
+ * plugin is skipped — never crashing the process:
8
+ * 1. Dynamic import — `await import(pkg)` (native ESM).
9
+ * 2. Manifest extraction — `mod.manifest` or `mod.default`.
10
+ * 3. Validation gate — hostApiVersion range + engines.node.
11
+ * 4. Registration — `registry.registerPlugin(manifest)`.
12
+ */
13
+ import { createRequire } from 'node:module';
14
+ import { pathToFileURL } from 'node:url';
15
+ import semver from 'semver';
16
+ import { HOST_API_VERSION } from '../../../shared/plugin-host/manifest.js';
17
+ import { buildMigrationInfo } from '../../../shared/plugin-host/host-api.js';
18
+ import { entryCacheBust } from './cache-bust.js';
19
+ const defaultImporter = (specifier) => import(specifier);
20
+ /** Pull the `PluginManifest` off a freshly-imported module namespace. Shared by
21
+ * the workspace loader and the project-local overlay loader. */
22
+ export function extractManifest(mod) {
23
+ if (mod == null || typeof mod !== 'object')
24
+ return null;
25
+ const ns = mod;
26
+ const candidate = (ns.manifest ?? ns.default);
27
+ if (candidate == null || typeof candidate !== 'object')
28
+ return null;
29
+ return candidate;
30
+ }
31
+ /** A structurally-valid manifest carries at least string `name` + `hostApiVersion`. */
32
+ export function isValidManifestShape(manifest) {
33
+ return (manifest != null &&
34
+ typeof manifest.name === 'string' &&
35
+ typeof manifest.hostApiVersion === 'string');
36
+ }
37
+ /** engines.node satisfied by the running node? Missing constraint = satisfied. */
38
+ function enginesSatisfied(manifest) {
39
+ const node = manifest.engines?.node;
40
+ if (!node)
41
+ return true;
42
+ return semver.satisfies(process.versions.node, node);
43
+ }
44
+ /**
45
+ * Compatibility gate shared by all layers: hostApiVersion range + engines.node.
46
+ * Returns `null` when the manifest may register; otherwise the gate result.
47
+ * A MAJOR Host API mismatch → `incompatible` + a migration descriptor; an
48
+ * `engines` miss (or a same-major host-api miss) → `skipped`.
49
+ */
50
+ export function gateManifest(manifest) {
51
+ if (!semver.satisfies(HOST_API_VERSION, manifest.hostApiVersion)) {
52
+ const migration = buildMigrationInfo(manifest.hostApiVersion);
53
+ return {
54
+ status: migration ? 'incompatible' : 'skipped',
55
+ code: 'PLUGIN_HOST_API_MISMATCH',
56
+ reason: `host API ${HOST_API_VERSION} does not satisfy plugin requirement "${manifest.hostApiVersion}"`,
57
+ migration: migration ?? undefined,
58
+ };
59
+ }
60
+ if (!enginesSatisfied(manifest)) {
61
+ return {
62
+ status: 'skipped',
63
+ code: 'PLUGIN_ENGINE_UNSATISFIED',
64
+ reason: `node ${process.versions.node} does not satisfy engines.node "${manifest.engines?.node}"`,
65
+ };
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * Load and register the given workspace plugin packages onto `registry`.
71
+ * Returns one record per package for the diagnostics route. Never throws.
72
+ */
73
+ export async function loadWorkspacePlugins(registry, packageNames, importer = defaultImporter) {
74
+ const records = [];
75
+ for (const pkg of packageNames) {
76
+ let mod;
77
+ try {
78
+ mod = await importer(pkg);
79
+ }
80
+ catch (err) {
81
+ const reason = err.message;
82
+ console.warn(`[plugin-loader] PLUGIN_IMPORT_FAILED ${pkg}: ${reason}`);
83
+ records.push({ package: pkg, status: 'failed', code: 'PLUGIN_IMPORT_FAILED', reason });
84
+ continue;
85
+ }
86
+ const manifest = extractManifest(mod);
87
+ if (!isValidManifestShape(manifest)) {
88
+ const reason = 'package does not export a valid PluginManifest (manifest/default)';
89
+ console.warn(`[plugin-loader] PLUGIN_INVALID_MANIFEST ${pkg}: ${reason}`);
90
+ records.push({ package: pkg, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason });
91
+ continue;
92
+ }
93
+ const base = {
94
+ package: pkg,
95
+ status: 'loaded',
96
+ layer: 'base',
97
+ manifestName: manifest.name,
98
+ manifestVersion: manifest.version,
99
+ };
100
+ const gate = gateManifest(manifest);
101
+ if (gate) {
102
+ console.warn(`[plugin-loader] ${gate.code} ${pkg}: ${gate.reason}`);
103
+ records.push({
104
+ ...base,
105
+ status: gate.status,
106
+ code: gate.code,
107
+ reason: gate.reason,
108
+ migration: gate.migration,
109
+ });
110
+ continue;
111
+ }
112
+ try {
113
+ registry.registerPlugin(manifest);
114
+ }
115
+ catch (err) {
116
+ const reason = err.message;
117
+ console.warn(`[plugin-loader] PLUGIN_INVALID_MANIFEST ${pkg}: ${reason}`);
118
+ records.push({ ...base, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason });
119
+ continue;
120
+ }
121
+ records.push({
122
+ ...base,
123
+ contributedTypes: (manifest.contributes.entities ?? []).map((e) => e.type),
124
+ });
125
+ }
126
+ return { records };
127
+ }
128
+ /** Resolve a base package's main entry to an absolute path (cache-bust + watch). */
129
+ function resolveBaseEntry(pkg) {
130
+ try {
131
+ return createRequire(import.meta.url).resolve(pkg);
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ }
137
+ /**
138
+ * M33 — hot-reload pipeline for ONE base (workspace/npm) package.
139
+ *
140
+ * Atomicity: the failure modes the brief names "old stays" — import failure,
141
+ * missing manifest export, incompatible major — are all checked BEFORE any
142
+ * teardown, so the previously-registered version remains registered and the
143
+ * caller surfaces a warning. Only once a fresh, compatible manifest is in hand
144
+ * do we tear the old version down (`unregisterPlugin` → its `onUnregister`) and
145
+ * register the new one — the brief's "onUnregister old, then register new".
146
+ *
147
+ * Returns the per-package record (`loaded` / `skipped` / `failed`). Never throws.
148
+ */
149
+ export async function reloadPlugin(registry, pkg, opts = {}) {
150
+ const importer = opts.importer ?? defaultImporter;
151
+ const resolveEntry = opts.resolveEntry ?? resolveBaseEntry;
152
+ const cacheBust = opts.cacheBust ?? entryCacheBust;
153
+ const base = { package: pkg, status: 'loaded', layer: 'base' };
154
+ const entry = resolveEntry(pkg);
155
+ const specifier = entry ? pathToFileURL(entry).href + cacheBust(entry) : pkg;
156
+ let mod;
157
+ try {
158
+ mod = await importer(specifier);
159
+ }
160
+ catch (err) {
161
+ const reason = err.message;
162
+ console.warn(`[plugin-loader] reload PLUGIN_IMPORT_FAILED ${pkg}: ${reason} (old version retained)`);
163
+ return { ...base, status: 'failed', code: 'PLUGIN_IMPORT_FAILED', reason };
164
+ }
165
+ const manifest = extractManifest(mod);
166
+ if (!isValidManifestShape(manifest)) {
167
+ const reason = 'package does not export a valid PluginManifest (manifest/default)';
168
+ console.warn(`[plugin-loader] reload PLUGIN_INVALID_MANIFEST ${pkg}: ${reason} (old version retained)`);
169
+ return { ...base, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason };
170
+ }
171
+ const named = { ...base, manifestName: manifest.name, manifestVersion: manifest.version };
172
+ const gate = gateManifest(manifest);
173
+ if (gate) {
174
+ console.warn(`[plugin-loader] reload ${gate.code} ${pkg}: ${gate.reason} (old version retained)`);
175
+ return { ...named, status: gate.status, code: gate.code, reason: gate.reason, migration: gate.migration };
176
+ }
177
+ // Atomicity: validate + lower the NEW manifest BEFORE tearing the old one
178
+ // down, so a structurally-broken new version leaves the old one in place
179
+ // (parity with the import/gate failures above — "old stays").
180
+ try {
181
+ registry.validatePlugin(manifest);
182
+ }
183
+ catch (err) {
184
+ const reason = err.message;
185
+ console.warn(`[plugin-loader] reload PLUGIN_INVALID_MANIFEST ${pkg}: ${reason} (old version retained)`);
186
+ return { ...named, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason };
187
+ }
188
+ // Fresh, compatible, valid: tear the old version down, then register the new.
189
+ registry.unregisterPlugin(manifest.name);
190
+ registry.registerPlugin(manifest);
191
+ return {
192
+ ...named,
193
+ contributedTypes: (manifest.contributes.entities ?? []).map((e) => e.type),
194
+ };
195
+ }
196
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../../src/server/core/plugin-host/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * M33: lower a runtime plugin's authoring shape (`EntityContribution`) into the
3
+ * internal `BackendModule` the host already understands. This keeps
4
+ * `mountBackend` / `MountContext` / `ProjectPluginHost` 100% unchanged — the
5
+ * manifest is purely an authoring envelope, reconciled here.
6
+ *
7
+ * Two backend shapes are supported:
8
+ * - `backend.mount` (full power) — passes through as the `BackendModule` mount.
9
+ * - `backend.routes` (sugar) — a pre-built express Router; synthesized into
10
+ * `mount = (ctx) => ctx.app.use(pathPrefix, routes)`. No MCP server, no entity
11
+ * service, no migrations beyond what is declared.
12
+ */
13
+ import type { EntityContribution, WritingStyleContribution } from '../../../shared/plugin-host/manifest.js';
14
+ import type { BackendModule } from './types.js';
15
+ /** Thrown when a contribution is structurally invalid. Caught per-package by the loader. */
16
+ export declare class PluginManifestError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ /**
20
+ * Validate one writing-style contribution (M15). Mirrors the SKILL.md
21
+ * frontmatter checks in skill-registry so a plugin style is held to the same
22
+ * shape as a file-authored one. Throws `PluginManifestError` (caught per-package
23
+ * by the loader) on any structural problem.
24
+ */
25
+ export declare function validateWritingStyle(c: WritingStyleContribution): WritingStyleContribution;
26
+ /**
27
+ * Convert one `EntityContribution` (shared authoring shape with `unknown`
28
+ * server payloads) into a fully-typed `BackendModule`.
29
+ */
30
+ export declare function lowerEntityContribution(c: EntityContribution): BackendModule;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * M33: lower a runtime plugin's authoring shape (`EntityContribution`) into the
3
+ * internal `BackendModule` the host already understands. This keeps
4
+ * `mountBackend` / `MountContext` / `ProjectPluginHost` 100% unchanged — the
5
+ * manifest is purely an authoring envelope, reconciled here.
6
+ *
7
+ * Two backend shapes are supported:
8
+ * - `backend.mount` (full power) — passes through as the `BackendModule` mount.
9
+ * - `backend.routes` (sugar) — a pre-built express Router; synthesized into
10
+ * `mount = (ctx) => ctx.app.use(pathPrefix, routes)`. No MCP server, no entity
11
+ * service, no migrations beyond what is declared.
12
+ */
13
+ /** Thrown when a contribution is structurally invalid. Caught per-package by the loader. */
14
+ export class PluginManifestError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'PluginManifestError';
18
+ }
19
+ }
20
+ /**
21
+ * Validate one writing-style contribution (M15). Mirrors the SKILL.md
22
+ * frontmatter checks in skill-registry so a plugin style is held to the same
23
+ * shape as a file-authored one. Throws `PluginManifestError` (caught per-package
24
+ * by the loader) on any structural problem.
25
+ */
26
+ export function validateWritingStyle(c) {
27
+ if (!c || typeof c !== 'object') {
28
+ throw new PluginManifestError('writingStyle contribution must be an object');
29
+ }
30
+ if (typeof c.slug !== 'string' || c.slug.length === 0) {
31
+ throw new PluginManifestError('writingStyle — slug must be a non-empty string');
32
+ }
33
+ if (typeof c.title !== 'string' || c.title.length === 0) {
34
+ throw new PluginManifestError(`writingStyle "${c.slug}" — title must be a non-empty string`);
35
+ }
36
+ if (typeof c.description !== 'string' || c.description.length === 0) {
37
+ throw new PluginManifestError(`writingStyle "${c.slug}" — description must be a non-empty string`);
38
+ }
39
+ if (typeof c.version !== 'number' || !Number.isInteger(c.version) || c.version < 1) {
40
+ throw new PluginManifestError(`writingStyle "${c.slug}" — version must be a positive integer`);
41
+ }
42
+ if (c.language !== 'en' && c.language !== 'pl') {
43
+ throw new PluginManifestError(`writingStyle "${c.slug}" — language must be 'en' or 'pl'`);
44
+ }
45
+ if (typeof c.content !== 'string') {
46
+ throw new PluginManifestError(`writingStyle "${c.slug}" — content must be a string`);
47
+ }
48
+ return c;
49
+ }
50
+ const MANIFEST_FIELDS = [
51
+ 'type',
52
+ 'table',
53
+ 'label',
54
+ 'labelPlural',
55
+ 'displayOrder',
56
+ 'slugFrom',
57
+ 'pathPrefix',
58
+ ];
59
+ function assertContribution(c) {
60
+ if (!c || typeof c !== 'object') {
61
+ throw new PluginManifestError('entity contribution must be an object');
62
+ }
63
+ const record = c;
64
+ for (const f of MANIFEST_FIELDS) {
65
+ if (record[f] == null) {
66
+ throw new PluginManifestError(`entity "${c.type ?? '?'}" missing required field "${f}"`);
67
+ }
68
+ }
69
+ if (typeof c.slugFrom !== 'function') {
70
+ throw new PluginManifestError(`entity "${c.type}" — slugFrom must be a function`);
71
+ }
72
+ if (c.serializer == null) {
73
+ throw new PluginManifestError(`entity "${c.type}" — serializer is required`);
74
+ }
75
+ if (c.systemPrompt == null) {
76
+ throw new PluginManifestError(`entity "${c.type}" — systemPrompt is required`);
77
+ }
78
+ const backend = c.backend;
79
+ if (backend != null && typeof backend !== 'object') {
80
+ throw new PluginManifestError(`entity "${c.type}" — backend must be an object`);
81
+ }
82
+ }
83
+ /**
84
+ * Convert one `EntityContribution` (shared authoring shape with `unknown`
85
+ * server payloads) into a fully-typed `BackendModule`.
86
+ */
87
+ export function lowerEntityContribution(c) {
88
+ assertContribution(c);
89
+ const backend = c.backend;
90
+ let backendSlot;
91
+ if (backend) {
92
+ const migrations = backend.migrations;
93
+ let mount = backend.mount;
94
+ if (!mount && backend.routes != null) {
95
+ const routes = backend.routes;
96
+ const pathPrefix = c.pathPrefix;
97
+ mount = (ctx) => {
98
+ ctx.app.use(pathPrefix, routes);
99
+ };
100
+ }
101
+ if (mount && typeof mount !== 'function') {
102
+ throw new PluginManifestError(`entity "${c.type}" — backend.mount must be a function`);
103
+ }
104
+ backendSlot = { migrations, mount };
105
+ }
106
+ return {
107
+ type: c.type,
108
+ table: c.table,
109
+ label: c.label,
110
+ labelPlural: c.labelPlural,
111
+ displayOrder: c.displayOrder,
112
+ slugFrom: c.slugFrom,
113
+ pathPrefix: c.pathPrefix,
114
+ serializer: c.serializer,
115
+ systemPrompt: c.systemPrompt,
116
+ backend: backendSlot,
117
+ };
118
+ }
119
+ //# sourceMappingURL=manifest-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-adapter.js","sourceRoot":"","sources":["../../../../src/server/core/plugin-host/manifest-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * M33 — project-local plugin overlay loader.
3
+ *
4
+ * The second of the two M33 tracks (the first being the process-global
5
+ * `loadWorkspacePlugins`). This one loads plugins committed into a repo at
6
+ * `<cwd>/.claude4spec/plugins/<pkg>/` and builds a per-`ProjectContext`
7
+ * `ProjectPluginOverlay`. Because a `git clone` brings this code with it,
8
+ * loading is gated by `trustProjectPlugins` — the CALLER decides trust and only
9
+ * invokes `loadProjectOverlay` when trusted; `enumerateOverlayPackages` is the
10
+ * code-free probe used to drive the trust prompt.
11
+ *
12
+ * Per package, each step is isolated in its own try/catch so one bad plugin is
13
+ * skipped — never failing the project build:
14
+ * 1. Resolve entry — package.json main/module/exports, else index.{js,mjs}.
15
+ * 2. Dynamic import — `import(pathToFileURL(entry))` (native ESM).
16
+ * 3. Validate+gate — shared with the workspace loader (hostApiVersion+engines).
17
+ * 4. Lower entities — `lowerEntityContribution`, enforcing within-overlay
18
+ * type uniqueness (second duplicate → PLUGIN_TYPE_CONFLICT, rejected).
19
+ */
20
+ import { type PluginLoadRecord } from './loader.js';
21
+ import type { ProjectPluginOverlay } from './types.js';
22
+ import type { WritingStyleContribution } from '../../../shared/plugin-host/manifest.js';
23
+ /** Importer seam — overridable in tests; defaults to native dynamic import. */
24
+ export type PluginImporter = (specifier: string) => Promise<unknown>;
25
+ export interface ProjectOverlayResult {
26
+ /** The overlay, or `undefined` when no loadable project-local module exists. */
27
+ overlay: ProjectPluginOverlay | undefined;
28
+ /** Per-package diagnostics for the per-project `/_meta/plugins` route. */
29
+ records: PluginLoadRecord[];
30
+ /** M15: trusted project-local writing styles, pushed into SkillRegistry. */
31
+ writingStyles: WritingStyleContribution[];
32
+ /** Best-effort detach of imported project-local modules (see dispose note). */
33
+ dispose: () => void;
34
+ }
35
+ /** Absolute path to a project's local plugins directory. */
36
+ export declare function projectPluginsDir(cwd: string): string;
37
+ /**
38
+ * Package directory names under `<cwd>/.claude4spec/plugins/`. Pure FS read — no
39
+ * code runs — so it is safe to call before the trust decision (drives the prompt
40
+ * and the untrusted-layer diagnostics).
41
+ */
42
+ export declare function enumerateOverlayPackages(cwd: string): string[];
43
+ /** True when the project ships at least one local plugin package. */
44
+ export declare function hasProjectPlugins(cwd: string): boolean;
45
+ /**
46
+ * Resolve a package.json `exports` field to a relative entry path. Handles the
47
+ * common shapes: a bare string, the `"."` subpath, and a conditions object
48
+ * (`{ import, default, require, node }`) at either the top level or under `"."`.
49
+ * Returns the first string match (preferring ESM `import`/`default`), or
50
+ * undefined if no string entry is reachable (caller falls back to module/main).
51
+ * Exported so the workspace-tier frontend resolver reuses the same condition
52
+ * handling for a plugin's `exports['./frontend']` (see `frontend-assets.ts`).
53
+ */
54
+ export declare function resolveExportsEntry(exp: unknown): string | undefined;
55
+ /**
56
+ * Load and validate the trusted project-local overlay for `cwd`. The caller must
57
+ * have already confirmed `trustProjectPlugins === true`; this function executes
58
+ * project-committed code.
59
+ */
60
+ export declare function loadProjectOverlay(cwd: string, importer?: PluginImporter): Promise<ProjectOverlayResult>;
@@ -0,0 +1,243 @@
1
+ /**
2
+ * M33 — project-local plugin overlay loader.
3
+ *
4
+ * The second of the two M33 tracks (the first being the process-global
5
+ * `loadWorkspacePlugins`). This one loads plugins committed into a repo at
6
+ * `<cwd>/.claude4spec/plugins/<pkg>/` and builds a per-`ProjectContext`
7
+ * `ProjectPluginOverlay`. Because a `git clone` brings this code with it,
8
+ * loading is gated by `trustProjectPlugins` — the CALLER decides trust and only
9
+ * invokes `loadProjectOverlay` when trusted; `enumerateOverlayPackages` is the
10
+ * code-free probe used to drive the trust prompt.
11
+ *
12
+ * Per package, each step is isolated in its own try/catch so one bad plugin is
13
+ * skipped — never failing the project build:
14
+ * 1. Resolve entry — package.json main/module/exports, else index.{js,mjs}.
15
+ * 2. Dynamic import — `import(pathToFileURL(entry))` (native ESM).
16
+ * 3. Validate+gate — shared with the workspace loader (hostApiVersion+engines).
17
+ * 4. Lower entities — `lowerEntityContribution`, enforcing within-overlay
18
+ * type uniqueness (second duplicate → PLUGIN_TYPE_CONFLICT, rejected).
19
+ */
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import { pathToFileURL } from 'node:url';
23
+ import { entryCacheBust } from './cache-bust.js';
24
+ import { extractManifest, gateManifest, isValidManifestShape, } from './loader.js';
25
+ import { lowerEntityContribution, validateWritingStyle } from './manifest-adapter.js';
26
+ const defaultImporter = (specifier) => import(specifier);
27
+ /** Absolute path to a project's local plugins directory. */
28
+ export function projectPluginsDir(cwd) {
29
+ return path.join(cwd, '.claude4spec', 'plugins');
30
+ }
31
+ /**
32
+ * Package directory names under `<cwd>/.claude4spec/plugins/`. Pure FS read — no
33
+ * code runs — so it is safe to call before the trust decision (drives the prompt
34
+ * and the untrusted-layer diagnostics).
35
+ */
36
+ export function enumerateOverlayPackages(cwd) {
37
+ const dir = projectPluginsDir(cwd);
38
+ let entries;
39
+ try {
40
+ if (!fs.existsSync(dir))
41
+ return [];
42
+ entries = fs.readdirSync(dir, { withFileTypes: true });
43
+ }
44
+ catch {
45
+ return [];
46
+ }
47
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
48
+ }
49
+ /** True when the project ships at least one local plugin package. */
50
+ export function hasProjectPlugins(cwd) {
51
+ return enumerateOverlayPackages(cwd).length > 0;
52
+ }
53
+ /**
54
+ * Resolve a package.json `exports` field to a relative entry path. Handles the
55
+ * common shapes: a bare string, the `"."` subpath, and a conditions object
56
+ * (`{ import, default, require, node }`) at either the top level or under `"."`.
57
+ * Returns the first string match (preferring ESM `import`/`default`), or
58
+ * undefined if no string entry is reachable (caller falls back to module/main).
59
+ * Exported so the workspace-tier frontend resolver reuses the same condition
60
+ * handling for a plugin's `exports['./frontend']` (see `frontend-assets.ts`).
61
+ */
62
+ export function resolveExportsEntry(exp) {
63
+ if (typeof exp === 'string')
64
+ return exp;
65
+ if (!exp || typeof exp !== 'object')
66
+ return undefined;
67
+ const obj = exp;
68
+ // Subpath map: drill into "." first, then treat the whole object as conditions.
69
+ const dot = obj['.'];
70
+ if (typeof dot === 'string')
71
+ return dot;
72
+ const conditions = (dot && typeof dot === 'object' ? dot : obj);
73
+ for (const key of ['import', 'module', 'default', 'node', 'require']) {
74
+ const v = conditions[key];
75
+ if (typeof v === 'string')
76
+ return v;
77
+ }
78
+ return undefined;
79
+ }
80
+ /** Resolve the ESM entry file of a package directory. Returns null if none found. */
81
+ function resolveEntry(pkgDir) {
82
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
83
+ if (fs.existsSync(pkgJsonPath)) {
84
+ try {
85
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
86
+ const rel = resolveExportsEntry(pkg.exports) ?? pkg.module ?? pkg.main;
87
+ if (rel) {
88
+ const abs = path.resolve(pkgDir, rel);
89
+ if (fs.existsSync(abs))
90
+ return abs;
91
+ }
92
+ }
93
+ catch {
94
+ /* fall through to index.* probing */
95
+ }
96
+ }
97
+ for (const candidate of ['index.js', 'index.mjs', 'index.cjs']) {
98
+ const abs = path.join(pkgDir, candidate);
99
+ if (fs.existsSync(abs))
100
+ return abs;
101
+ }
102
+ return null;
103
+ }
104
+ /**
105
+ * Load and validate the trusted project-local overlay for `cwd`. The caller must
106
+ * have already confirmed `trustProjectPlugins === true`; this function executes
107
+ * project-committed code.
108
+ */
109
+ export async function loadProjectOverlay(cwd, importer = defaultImporter) {
110
+ const records = [];
111
+ const modules = new Map();
112
+ const originByType = new Map();
113
+ const writingStyles = [];
114
+ // M33: non-entity capabilities of trusted project-local plugins. An
115
+ // entity-less plugin (commands/settings only) still produces these.
116
+ const settingsSections = [];
117
+ const commands = [];
118
+ const teardowns = [];
119
+ for (const pkg of enumerateOverlayPackages(cwd)) {
120
+ const pkgDir = path.join(projectPluginsDir(cwd), pkg);
121
+ const origin = path.join('.claude4spec', 'plugins', pkg);
122
+ const base = { package: pkg, status: 'loaded', layer: 'overlay', trust: 'trusted', origin };
123
+ const entry = resolveEntry(pkgDir);
124
+ if (!entry) {
125
+ const reason = 'no resolvable ESM entry (package.json main/module/exports or index.{js,mjs,cjs})';
126
+ console.warn(`[overlay-loader] PLUGIN_INVALID_MANIFEST ${pkg}: ${reason}`);
127
+ records.push({ ...base, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason });
128
+ continue;
129
+ }
130
+ let mod;
131
+ try {
132
+ mod = await importer(pathToFileURL(entry).href + entryCacheBust(entry));
133
+ }
134
+ catch (err) {
135
+ const reason = err.message;
136
+ console.warn(`[overlay-loader] PLUGIN_IMPORT_FAILED ${pkg}: ${reason}`);
137
+ records.push({ ...base, status: 'failed', code: 'PLUGIN_IMPORT_FAILED', reason });
138
+ continue;
139
+ }
140
+ const manifest = extractManifest(mod);
141
+ if (!isValidManifestShape(manifest)) {
142
+ const reason = 'package does not export a valid PluginManifest (manifest/default)';
143
+ console.warn(`[overlay-loader] PLUGIN_INVALID_MANIFEST ${pkg}: ${reason}`);
144
+ records.push({ ...base, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason });
145
+ continue;
146
+ }
147
+ const gate = gateManifest(manifest);
148
+ if (gate) {
149
+ console.warn(`[overlay-loader] ${gate.code} ${pkg}: ${gate.reason}`);
150
+ records.push({
151
+ ...base,
152
+ status: gate.status,
153
+ code: gate.code,
154
+ reason: gate.reason,
155
+ migration: gate.migration,
156
+ manifestName: manifest.name,
157
+ manifestVersion: manifest.version,
158
+ });
159
+ continue;
160
+ }
161
+ // Lower contributions (entities + styles) before committing — a throw on
162
+ // either fails the whole plugin atomically.
163
+ let lowered;
164
+ let styles;
165
+ try {
166
+ lowered = (manifest.contributes?.entities ?? []).map(lowerEntityContribution);
167
+ styles = (manifest.contributes?.writingStyles ?? []).map(validateWritingStyle);
168
+ }
169
+ catch (err) {
170
+ const reason = err.message;
171
+ console.warn(`[overlay-loader] PLUGIN_INVALID_MANIFEST ${pkg}: ${reason}`);
172
+ records.push({ ...base, status: 'failed', code: 'PLUGIN_INVALID_MANIFEST', reason });
173
+ continue;
174
+ }
175
+ // Enforce within-overlay (same-layer) type uniqueness.
176
+ const conflict = lowered.find((m) => modules.has(m.type));
177
+ if (conflict) {
178
+ const reason = `type "${conflict.type}" already provided by another project-local plugin (origin ${originByType.get(conflict.type)})`;
179
+ console.warn(`[overlay-loader] PLUGIN_TYPE_CONFLICT ${pkg}: ${reason}`);
180
+ records.push({ ...base, status: 'failed', code: 'PLUGIN_TYPE_CONFLICT', reason });
181
+ continue;
182
+ }
183
+ for (const m of lowered) {
184
+ modules.set(m.type, m);
185
+ originByType.set(m.type, origin);
186
+ }
187
+ writingStyles.push(...styles);
188
+ // M33: capture non-entity capabilities + teardown of this trusted plugin.
189
+ if ((manifest.contributes?.settings ?? []).length > 0) {
190
+ settingsSections.push({
191
+ name: manifest.name,
192
+ version: manifest.version,
193
+ fields: manifest.contributes.settings,
194
+ });
195
+ }
196
+ commands.push(...(manifest.contributes?.commands ?? []));
197
+ if (typeof manifest.onUnregister === 'function') {
198
+ const fn = manifest.onUnregister.bind(manifest);
199
+ teardowns.push(fn);
200
+ }
201
+ else {
202
+ console.warn(`[overlay-loader] plugin "${manifest.name}" — required slot onUnregister is missing; using a no-op teardown`);
203
+ }
204
+ records.push({
205
+ ...base,
206
+ manifestName: manifest.name,
207
+ manifestVersion: manifest.version,
208
+ contributedTypes: lowered.map((m) => m.type),
209
+ });
210
+ }
211
+ // Node's ESM registry caches modules by URL — a true unload is not possible.
212
+ // dispose() runs each plugin's `onUnregister` (M33 teardown) then
213
+ // drops the host-side references (mirrors clearMcpFactories); a rebuild
214
+ // re-imports (cached) fresh manifests. Stateful native handles, if a plugin
215
+ // opens any, are the plugin's own teardown responsibility.
216
+ const dispose = () => {
217
+ for (const teardown of teardowns) {
218
+ try {
219
+ teardown();
220
+ }
221
+ catch (err) {
222
+ console.warn(`[overlay-loader] onUnregister threw during dispose: ${err.message}`);
223
+ }
224
+ }
225
+ modules.clear();
226
+ originByType.clear();
227
+ };
228
+ // No capabilities at all ⇒ no overlay host layer, but a plugin may still have
229
+ // contributed writing styles — return those so the caller can push them.
230
+ // M33: an entity-less plugin with only settings/commands DOES produce
231
+ // an overlay so the host surfaces those capabilities (axis B).
232
+ if (modules.size === 0 && settingsSections.length === 0 && commands.length === 0) {
233
+ return { overlay: undefined, records, writingStyles, dispose };
234
+ }
235
+ const overlay = {
236
+ listLocal: () => Array.from(modules.values()),
237
+ origin: (type) => originByType.get(type) ?? '',
238
+ listSettings: () => settingsSections,
239
+ listCommands: () => commands,
240
+ };
241
+ return { overlay, records, writingStyles, dispose };
242
+ }
243
+ //# sourceMappingURL=overlay-loader.js.map