@kolisachint/hoocode-agent 0.5.2 → 0.5.4

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 (174) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli/args.d.ts +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +10 -7
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +10 -2
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/capabilities/deferral.d.ts +78 -0
  10. package/dist/core/capabilities/deferral.d.ts.map +1 -0
  11. package/dist/core/capabilities/deferral.js +99 -0
  12. package/dist/core/capabilities/deferral.js.map +1 -0
  13. package/dist/core/capabilities/dense.d.ts +51 -0
  14. package/dist/core/capabilities/dense.d.ts.map +1 -0
  15. package/dist/core/capabilities/dense.js +121 -0
  16. package/dist/core/capabilities/dense.js.map +1 -0
  17. package/dist/core/capabilities/lexical.d.ts +43 -0
  18. package/dist/core/capabilities/lexical.d.ts.map +1 -0
  19. package/dist/core/capabilities/lexical.js +104 -0
  20. package/dist/core/capabilities/lexical.js.map +1 -0
  21. package/dist/core/capabilities/registry.d.ts +56 -0
  22. package/dist/core/capabilities/registry.d.ts.map +1 -0
  23. package/dist/core/capabilities/registry.js +0 -0
  24. package/dist/core/capabilities/registry.js.map +1 -0
  25. package/dist/core/capabilities/search.d.ts +47 -0
  26. package/dist/core/capabilities/search.d.ts.map +1 -0
  27. package/dist/core/capabilities/search.js +82 -0
  28. package/dist/core/capabilities/search.js.map +1 -0
  29. package/dist/core/extensions/loader.d.ts +27 -3
  30. package/dist/core/extensions/loader.d.ts.map +1 -1
  31. package/dist/core/extensions/loader.js +61 -7
  32. package/dist/core/extensions/loader.js.map +1 -1
  33. package/dist/core/extensions/plugins/authoring.d.ts +56 -9
  34. package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
  35. package/dist/core/extensions/plugins/authoring.js +0 -0
  36. package/dist/core/extensions/plugins/authoring.js.map +1 -1
  37. package/dist/core/extensions/plugins/drift.d.ts +75 -0
  38. package/dist/core/extensions/plugins/drift.d.ts.map +1 -0
  39. package/dist/core/extensions/plugins/drift.js +194 -0
  40. package/dist/core/extensions/plugins/drift.js.map +1 -0
  41. package/dist/core/extensions/plugins/formats/claude.d.ts.map +1 -1
  42. package/dist/core/extensions/plugins/formats/claude.js +5 -0
  43. package/dist/core/extensions/plugins/formats/claude.js.map +1 -1
  44. package/dist/core/extensions/plugins/formats/copilot.d.ts +6 -0
  45. package/dist/core/extensions/plugins/formats/copilot.d.ts.map +1 -1
  46. package/dist/core/extensions/plugins/formats/copilot.js +57 -16
  47. package/dist/core/extensions/plugins/formats/copilot.js.map +1 -1
  48. package/dist/core/extensions/plugins/formats/index.d.ts +39 -1
  49. package/dist/core/extensions/plugins/formats/index.d.ts.map +1 -1
  50. package/dist/core/extensions/plugins/formats/index.js +90 -13
  51. package/dist/core/extensions/plugins/formats/index.js.map +1 -1
  52. package/dist/core/extensions/plugins/formats/jsonManifest.d.ts +14 -0
  53. package/dist/core/extensions/plugins/formats/jsonManifest.d.ts.map +1 -1
  54. package/dist/core/extensions/plugins/formats/jsonManifest.js +37 -4
  55. package/dist/core/extensions/plugins/formats/jsonManifest.js.map +1 -1
  56. package/dist/core/extensions/plugins/formats/platform-targets.d.ts +48 -24
  57. package/dist/core/extensions/plugins/formats/platform-targets.d.ts.map +1 -1
  58. package/dist/core/extensions/plugins/formats/platform-targets.js +62 -30
  59. package/dist/core/extensions/plugins/formats/platform-targets.js.map +1 -1
  60. package/dist/core/extensions/plugins/formats/shared.d.ts +22 -0
  61. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
  62. package/dist/core/extensions/plugins/formats/shared.js +81 -4
  63. package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
  64. package/dist/core/extensions/plugins/formats/types.d.ts +10 -1
  65. package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -1
  66. package/dist/core/extensions/plugins/formats/types.js.map +1 -1
  67. package/dist/core/extensions/plugins/gates.d.ts +64 -0
  68. package/dist/core/extensions/plugins/gates.d.ts.map +1 -0
  69. package/dist/core/extensions/plugins/gates.js +297 -0
  70. package/dist/core/extensions/plugins/gates.js.map +1 -0
  71. package/dist/core/extensions/plugins/hooks-bridge.d.ts +1 -1
  72. package/dist/core/extensions/plugins/hooks-bridge.d.ts.map +1 -1
  73. package/dist/core/extensions/plugins/hooks-bridge.js +12 -8
  74. package/dist/core/extensions/plugins/hooks-bridge.js.map +1 -1
  75. package/dist/core/extensions/plugins/index.d.ts +36 -5
  76. package/dist/core/extensions/plugins/index.d.ts.map +1 -1
  77. package/dist/core/extensions/plugins/index.js +66 -15
  78. package/dist/core/extensions/plugins/index.js.map +1 -1
  79. package/dist/core/extensions/plugins/install.d.ts +43 -24
  80. package/dist/core/extensions/plugins/install.d.ts.map +1 -1
  81. package/dist/core/extensions/plugins/install.js +167 -54
  82. package/dist/core/extensions/plugins/install.js.map +1 -1
  83. package/dist/core/extensions/plugins/locations.d.ts +84 -0
  84. package/dist/core/extensions/plugins/locations.d.ts.map +1 -0
  85. package/dist/core/extensions/plugins/locations.js +137 -0
  86. package/dist/core/extensions/plugins/locations.js.map +1 -0
  87. package/dist/core/extensions/plugins/manifest.d.ts +21 -1
  88. package/dist/core/extensions/plugins/manifest.d.ts.map +1 -1
  89. package/dist/core/extensions/plugins/manifest.js +2 -2
  90. package/dist/core/extensions/plugins/manifest.js.map +1 -1
  91. package/dist/core/extensions/plugins/packaging.d.ts +115 -0
  92. package/dist/core/extensions/plugins/packaging.d.ts.map +1 -0
  93. package/dist/core/extensions/plugins/packaging.js +362 -0
  94. package/dist/core/extensions/plugins/packaging.js.map +1 -0
  95. package/dist/core/extensions/plugins/smoke.d.ts +33 -0
  96. package/dist/core/extensions/plugins/smoke.d.ts.map +1 -0
  97. package/dist/core/extensions/plugins/smoke.js +231 -0
  98. package/dist/core/extensions/plugins/smoke.js.map +1 -0
  99. package/dist/core/extensions/plugins/trigger-eval.d.ts +136 -0
  100. package/dist/core/extensions/plugins/trigger-eval.d.ts.map +1 -0
  101. package/dist/core/extensions/plugins/trigger-eval.js +0 -0
  102. package/dist/core/extensions/plugins/trigger-eval.js.map +1 -0
  103. package/dist/core/package-manager.d.ts +6 -0
  104. package/dist/core/package-manager.d.ts.map +1 -1
  105. package/dist/core/package-manager.js.map +1 -1
  106. package/dist/core/resource-loader.d.ts +2 -0
  107. package/dist/core/resource-loader.d.ts.map +1 -1
  108. package/dist/core/resource-loader.js +15 -0
  109. package/dist/core/resource-loader.js.map +1 -1
  110. package/dist/core/settings-manager.d.ts +2 -2
  111. package/dist/core/settings-manager.d.ts.map +1 -1
  112. package/dist/core/settings-manager.js +3 -3
  113. package/dist/core/settings-manager.js.map +1 -1
  114. package/dist/core/settings-types.d.ts +1 -1
  115. package/dist/core/settings-types.d.ts.map +1 -1
  116. package/dist/core/settings-types.js.map +1 -1
  117. package/dist/core/skills.d.ts +9 -0
  118. package/dist/core/skills.d.ts.map +1 -1
  119. package/dist/core/skills.js +23 -3
  120. package/dist/core/skills.js.map +1 -1
  121. package/dist/core/tools/package-plugin.d.ts +19 -0
  122. package/dist/core/tools/package-plugin.d.ts.map +1 -0
  123. package/dist/core/tools/package-plugin.js +81 -0
  124. package/dist/core/tools/package-plugin.js.map +1 -0
  125. package/dist/core/tools/plugin-tool-names.d.ts +1 -0
  126. package/dist/core/tools/plugin-tool-names.d.ts.map +1 -1
  127. package/dist/core/tools/plugin-tool-names.js +6 -0
  128. package/dist/core/tools/plugin-tool-names.js.map +1 -1
  129. package/dist/core/tools/plugins.d.ts.map +1 -1
  130. package/dist/core/tools/plugins.js +48 -7
  131. package/dist/core/tools/plugins.js.map +1 -1
  132. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  133. package/dist/core/tools/propose-plugin.js +49 -21
  134. package/dist/core/tools/propose-plugin.js.map +1 -1
  135. package/dist/core/tools/webfetch.d.ts +3 -1
  136. package/dist/core/tools/webfetch.d.ts.map +1 -1
  137. package/dist/core/tools/webfetch.js +8 -2
  138. package/dist/core/tools/webfetch.js.map +1 -1
  139. package/dist/core/tools/websearch.d.ts +2 -0
  140. package/dist/core/tools/websearch.d.ts.map +1 -1
  141. package/dist/core/tools/websearch.js +2 -1
  142. package/dist/core/tools/websearch.js.map +1 -1
  143. package/dist/core/tools/webtools-shared.d.ts +23 -2
  144. package/dist/core/tools/webtools-shared.d.ts.map +1 -1
  145. package/dist/core/tools/webtools-shared.js +59 -9
  146. package/dist/core/tools/webtools-shared.js.map +1 -1
  147. package/dist/extensions/core/marketplace.d.ts +14 -3
  148. package/dist/extensions/core/marketplace.d.ts.map +1 -1
  149. package/dist/extensions/core/marketplace.js +77 -18
  150. package/dist/extensions/core/marketplace.js.map +1 -1
  151. package/dist/extensions/core/mcp-deferred.d.ts +39 -2
  152. package/dist/extensions/core/mcp-deferred.d.ts.map +1 -1
  153. package/dist/extensions/core/mcp-deferred.js +74 -10
  154. package/dist/extensions/core/mcp-deferred.js.map +1 -1
  155. package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
  156. package/dist/extensions/core/mcp-loader.js +52 -9
  157. package/dist/extensions/core/mcp-loader.js.map +1 -1
  158. package/dist/extensions/core/scaffold.d.ts +3 -3
  159. package/dist/extensions/core/scaffold.d.ts.map +1 -1
  160. package/dist/extensions/core/scaffold.js +9 -9
  161. package/dist/extensions/core/scaffold.js.map +1 -1
  162. package/dist/main.d.ts.map +1 -1
  163. package/dist/main.js +25 -7
  164. package/dist/main.js.map +1 -1
  165. package/dist/utils/tools-manager.d.ts +8 -0
  166. package/dist/utils/tools-manager.d.ts.map +1 -1
  167. package/dist/utils/tools-manager.js +12 -3
  168. package/dist/utils/tools-manager.js.map +1 -1
  169. package/docs/providers.md +11 -0
  170. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  171. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  172. package/examples/extensions/sandbox/package.json +1 -1
  173. package/examples/extensions/with-deps/package.json +1 -1
  174. package/package.json +5 -4
@@ -1,49 +1,73 @@
1
1
  /**
2
- * Session-wide artifact platform targeting (`--support-platform`).
2
+ * Session-wide artifact platform targeting (`--platform`).
3
3
  *
4
4
  * hoocode reads resources from every vendor convention, but when it *writes*
5
- * artifacts authored plugins via ProposePlugin, workspace scaffolds via
6
- * /new-skill //new-agent //new-command it needs a target layout. This module
7
- * owns that choice for the whole process:
5
+ * artifacts it needs a target layout. The two kinds of write have **different
6
+ * rules**, which is why this module exposes two resolvers rather than one:
7
+ *
8
+ * Plugins distribution units. A plugin only means anything as a
9
+ * member of a platform ecosystem (there is no `.agents`
10
+ * marketplace), so production is platform-specific and
11
+ * `agents` is never a valid target.
12
+ * → {@link resolvePluginPlatforms}
13
+ * Workspace scaffolds `/new-skill`, `/new-agent`, `/new-command`. These are
14
+ * consumed in place, never shipped, and `.agents/` is a
15
+ * real cross-vendor convention for them.
16
+ * → {@link getWorkspacePlatforms}
8
17
  *
9
18
  * token vocabulary `agents` (alias `native`), `claude`,
10
19
  * `github` (aliases `copilot`, `gh`)
11
- * session state set once at startup from the `--support-platform` flag
12
- * or the `supportPlatform` setting (main.ts)
13
- * resolution explicit per-call platforms → session targets →
14
- * {@link DEFAULT_AUTHORING_PLATFORMS}
20
+ * session state set once at startup from the `--platform` flag or the
21
+ * `platform` setting (main.ts)
22
+ *
23
+ * See docs/plugin-system-architecture.md §1 for the reasoning.
15
24
  *
16
25
  * Kept beside the format registry (and importing only `types.ts`) so the
17
26
  * platform vocabulary and the adapters stay a one-directory concern: adding a
18
27
  * vendor means a new adapter file plus a token here, nothing else.
19
28
  */
20
29
  import type { MarketplacePlatform } from "./types.js";
30
+ /** The platforms a *plugin* can be produced for. `agents` is excluded by construction. */
31
+ export type PluginPlatform = Exclude<MarketplacePlatform, "agents">;
32
+ /** True for the platform tokens that own a plugin ecosystem. */
33
+ export declare function isPluginPlatform(platform: MarketplacePlatform): platform is PluginPlatform;
21
34
  /**
22
- * Default authoring target when neither the call nor the session picked any: the
23
- * portable native format. Authored artifacts are meant to be reusable, so they
24
- * default to one vendor-neutral layout (a strict superset hoocode reads directly)
25
- * rather than forking into vendor copies. Vendor layouts are an opt-in interop
26
- * concern, requested only via the `--support-platform` session flag.
35
+ * Default plugin production target when neither the call nor the session picked
36
+ * one. Claude is the default because it is the only platform whose local loop
37
+ * closes: an authored plugin lands in `~/.claude/skills/<id>/` and is live on the
38
+ * next session, whereas Copilot has no in-place drop-in at all.
27
39
  */
28
- export declare const DEFAULT_AUTHORING_PLATFORMS: readonly MarketplacePlatform[];
40
+ export declare const DEFAULT_PLUGIN_PLATFORMS: readonly PluginPlatform[];
29
41
  /** Canonicalize one platform token, folding the user-facing aliases. */
30
42
  export declare function normalizePlatformToken(token: string): MarketplacePlatform | undefined;
31
- export interface SupportPlatformParse {
43
+ export interface PlatformParse {
32
44
  /** Canonical platforms, deduped, in the order first mentioned. */
33
45
  platforms: MarketplacePlatform[];
34
46
  /** Tokens that matched no known platform (surfaced as diagnostics, never fatal). */
35
47
  invalid: string[];
36
48
  }
37
- /** Parse raw `--support-platform` / `supportPlatform` tokens into canonical platforms. */
38
- export declare function parseSupportPlatforms(tokens: readonly string[]): SupportPlatformParse;
49
+ /** Parse raw `--platform` / `platform` tokens into canonical platforms. */
50
+ export declare function parsePlatforms(tokens: readonly string[]): PlatformParse;
39
51
  /** Set (or clear, with undefined/empty) the session's artifact platform targets. */
40
- export declare function setSupportPlatforms(platforms: readonly MarketplacePlatform[] | undefined): void;
41
- /** The session's configured targets, or undefined when `--support-platform` was not given. */
42
- export declare function getSupportPlatforms(): MarketplacePlatform[] | undefined;
52
+ export declare function setPlatforms(platforms: readonly MarketplacePlatform[] | undefined): void;
43
53
  /**
44
- * Resolve the platforms an authoring write should target: an explicit per-call
45
- * selection wins, then the session's `--support-platform` targets, then
46
- * {@link DEFAULT_AUTHORING_PLATFORMS}.
54
+ * The session's configured targets for **workspace scaffolds**, or undefined when
55
+ * `--platform` was not given callers treat undefined as "use the hoocode-native
56
+ * location" rather than substituting a default. `agents` is a legitimate value
57
+ * here: a scaffolded skill or command is consumed in place, not distributed.
58
+ */
59
+ export declare function getWorkspacePlatforms(): MarketplacePlatform[] | undefined;
60
+ /**
61
+ * Resolve the platforms a **plugin** should be produced for: an explicit per-call
62
+ * selection wins, then the session's `--platform` targets, then
63
+ * {@link DEFAULT_PLUGIN_PLATFORMS}.
64
+ *
65
+ * `agents` can never be the answer. An explicit `agents` throws — a caller asking
66
+ * for a plugin in a format no marketplace accepts has made a mistake and should
67
+ * hear about it. A session-level `agents` is *filtered* instead, because
68
+ * `--platform` also drives workspace scaffolds, where `agents` is a perfectly
69
+ * good choice; a user who set it for that reason should not have plugin authoring
70
+ * blow up in their face. If filtering leaves nothing, the default applies.
47
71
  */
48
- export declare function resolveAuthoringPlatforms(explicit?: readonly MarketplacePlatform[]): MarketplacePlatform[];
72
+ export declare function resolvePluginPlatforms(explicit?: readonly MarketplacePlatform[]): PluginPlatform[];
49
73
  //# sourceMappingURL=platform-targets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-targets.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/platform-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,mBAAmB,EAAe,CAAC;AAEtF,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAcrF;AAED,MAAM,WAAW,oBAAoB;IACpC,kEAAkE;IAClE,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,oFAAoF;IACpF,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,oBAAoB,CAWrF;AAKD,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,GAAG,SAAS,GAAG,IAAI,CAE/F;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,IAAI,mBAAmB,EAAE,GAAG,SAAS,CAEvE;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,GAAG,mBAAmB,EAAE,CAG1G","sourcesContent":["/**\n * Session-wide artifact platform targeting (`--support-platform`).\n *\n * hoocode reads resources from every vendor convention, but when it *writes*\n * artifacts authored plugins via ProposePlugin, workspace scaffolds via\n * /new-skill //new-agent //new-command it needs a target layout. This module\n * owns that choice for the whole process:\n *\n * token vocabulary `agents` (alias `native`), `claude`,\n * `github` (aliases `copilot`, `gh`)\n * session state set once at startup from the `--support-platform` flag\n * or the `supportPlatform` setting (main.ts)\n * resolution explicit per-call platforms session targets →\n * {@link DEFAULT_AUTHORING_PLATFORMS}\n *\n * Kept beside the format registry (and importing only `types.ts`) so the\n * platform vocabulary and the adapters stay a one-directory concern: adding a\n * vendor means a new adapter file plus a token here, nothing else.\n */\n\nimport type { MarketplacePlatform } from \"./types.js\";\n\n/**\n * Default authoring target when neither the call nor the session picked any: the\n * portable native format. Authored artifacts are meant to be reusable, so they\n * default to one vendor-neutral layout (a strict superset hoocode reads directly)\n * rather than forking into vendor copies. Vendor layouts are an opt-in interop\n * concern, requested only via the `--support-platform` session flag.\n */\nexport const DEFAULT_AUTHORING_PLATFORMS: readonly MarketplacePlatform[] = [\"agents\"];\n\n/** Canonicalize one platform token, folding the user-facing aliases. */\nexport function normalizePlatformToken(token: string): MarketplacePlatform | undefined {\n\tswitch (token.trim().toLowerCase()) {\n\t\tcase \"agents\":\n\t\tcase \"native\":\n\t\t\treturn \"agents\";\n\t\tcase \"claude\":\n\t\t\treturn \"claude\";\n\t\tcase \"github\":\n\t\tcase \"copilot\":\n\t\tcase \"gh\":\n\t\t\treturn \"github\";\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport interface SupportPlatformParse {\n\t/** Canonical platforms, deduped, in the order first mentioned. */\n\tplatforms: MarketplacePlatform[];\n\t/** Tokens that matched no known platform (surfaced as diagnostics, never fatal). */\n\tinvalid: string[];\n}\n\n/** Parse raw `--support-platform` / `supportPlatform` tokens into canonical platforms. */\nexport function parseSupportPlatforms(tokens: readonly string[]): SupportPlatformParse {\n\tconst platforms: MarketplacePlatform[] = [];\n\tconst invalid: string[] = [];\n\tfor (const token of tokens) {\n\t\tconst trimmed = token.trim();\n\t\tif (!trimmed) continue;\n\t\tconst canonical = normalizePlatformToken(trimmed);\n\t\tif (!canonical) invalid.push(trimmed);\n\t\telse if (!platforms.includes(canonical)) platforms.push(canonical);\n\t}\n\treturn { platforms, invalid };\n}\n\n/** Session-wide targets. Undefined = not configured (fall back to defaults). */\nlet sessionSupportPlatforms: MarketplacePlatform[] | undefined;\n\n/** Set (or clear, with undefined/empty) the session's artifact platform targets. */\nexport function setSupportPlatforms(platforms: readonly MarketplacePlatform[] | undefined): void {\n\tsessionSupportPlatforms = platforms && platforms.length > 0 ? [...platforms] : undefined;\n}\n\n/** The session's configured targets, or undefined when `--support-platform` was not given. */\nexport function getSupportPlatforms(): MarketplacePlatform[] | undefined {\n\treturn sessionSupportPlatforms ? [...sessionSupportPlatforms] : undefined;\n}\n\n/**\n * Resolve the platforms an authoring write should target: an explicit per-call\n * selection wins, then the session's `--support-platform` targets, then\n * {@link DEFAULT_AUTHORING_PLATFORMS}.\n */\nexport function resolveAuthoringPlatforms(explicit?: readonly MarketplacePlatform[]): MarketplacePlatform[] {\n\tif (explicit && explicit.length > 0) return [...explicit];\n\treturn [...(sessionSupportPlatforms ?? DEFAULT_AUTHORING_PLATFORMS)];\n}\n"]}
1
+ {"version":3,"file":"platform-targets.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/platform-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,0FAA0F;AAC1F,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;AAEpE,gEAAgE;AAChE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,QAAQ,IAAI,cAAc,CAE1F;AAED;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,cAAc,EAAe,CAAC;AAE9E,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAcrF;AAED,MAAM,WAAW,aAAa;IAC7B,kEAAkE;IAClE,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,oFAAoF;IACpF,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAWvE;AAKD,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,GAAG,SAAS,GAAG,IAAI,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,mBAAmB,EAAE,GAAG,SAAS,CAEzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,SAAS,mBAAmB,EAAE,GAAG,cAAc,EAAE,CAclG","sourcesContent":["/**\n * Session-wide artifact platform targeting (`--platform`).\n *\n * hoocode reads resources from every vendor convention, but when it *writes*\n * artifacts it needs a target layout. The two kinds of write have **different\n * rules**, which is why this module exposes two resolvers rather than one:\n *\n * Plugins distribution units. A plugin only means anything as a\n * member of a platform ecosystem (there is no `.agents`\n * marketplace), so production is platform-specific and\n * `agents` is never a valid target.\n * → {@link resolvePluginPlatforms}\n * Workspace scaffolds `/new-skill`, `/new-agent`, `/new-command`. These are\n * consumed in place, never shipped, and `.agents/` is a\n * real cross-vendor convention for them.\n * → {@link getWorkspacePlatforms}\n *\n * token vocabulary `agents` (alias `native`), `claude`,\n * `github` (aliases `copilot`, `gh`)\n * session state set once at startup from the `--platform` flag or the\n * `platform` setting (main.ts)\n *\n * See docs/plugin-system-architecture.md §1 for the reasoning.\n *\n * Kept beside the format registry (and importing only `types.ts`) so the\n * platform vocabulary and the adapters stay a one-directory concern: adding a\n * vendor means a new adapter file plus a token here, nothing else.\n */\n\nimport type { MarketplacePlatform } from \"./types.js\";\n\n/** The platforms a *plugin* can be produced for. `agents` is excluded by construction. */\nexport type PluginPlatform = Exclude<MarketplacePlatform, \"agents\">;\n\n/** True for the platform tokens that own a plugin ecosystem. */\nexport function isPluginPlatform(platform: MarketplacePlatform): platform is PluginPlatform {\n\treturn platform !== \"agents\";\n}\n\n/**\n * Default plugin production target when neither the call nor the session picked\n * one. Claude is the default because it is the only platform whose local loop\n * closes: an authored plugin lands in `~/.claude/skills/<id>/` and is live on the\n * next session, whereas Copilot has no in-place drop-in at all.\n */\nexport const DEFAULT_PLUGIN_PLATFORMS: readonly PluginPlatform[] = [\"claude\"];\n\n/** Canonicalize one platform token, folding the user-facing aliases. */\nexport function normalizePlatformToken(token: string): MarketplacePlatform | undefined {\n\tswitch (token.trim().toLowerCase()) {\n\t\tcase \"agents\":\n\t\tcase \"native\":\n\t\t\treturn \"agents\";\n\t\tcase \"claude\":\n\t\t\treturn \"claude\";\n\t\tcase \"github\":\n\t\tcase \"copilot\":\n\t\tcase \"gh\":\n\t\t\treturn \"github\";\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport interface PlatformParse {\n\t/** Canonical platforms, deduped, in the order first mentioned. */\n\tplatforms: MarketplacePlatform[];\n\t/** Tokens that matched no known platform (surfaced as diagnostics, never fatal). */\n\tinvalid: string[];\n}\n\n/** Parse raw `--platform` / `platform` tokens into canonical platforms. */\nexport function parsePlatforms(tokens: readonly string[]): PlatformParse {\n\tconst platforms: MarketplacePlatform[] = [];\n\tconst invalid: string[] = [];\n\tfor (const token of tokens) {\n\t\tconst trimmed = token.trim();\n\t\tif (!trimmed) continue;\n\t\tconst canonical = normalizePlatformToken(trimmed);\n\t\tif (!canonical) invalid.push(trimmed);\n\t\telse if (!platforms.includes(canonical)) platforms.push(canonical);\n\t}\n\treturn { platforms, invalid };\n}\n\n/** Session-wide targets. Undefined = not configured (fall back to per-consumer defaults). */\nlet sessionPlatforms: MarketplacePlatform[] | undefined;\n\n/** Set (or clear, with undefined/empty) the session's artifact platform targets. */\nexport function setPlatforms(platforms: readonly MarketplacePlatform[] | undefined): void {\n\tsessionPlatforms = platforms && platforms.length > 0 ? [...platforms] : undefined;\n}\n\n/**\n * The session's configured targets for **workspace scaffolds**, or undefined when\n * `--platform` was not given — callers treat undefined as \"use the hoocode-native\n * location\" rather than substituting a default. `agents` is a legitimate value\n * here: a scaffolded skill or command is consumed in place, not distributed.\n */\nexport function getWorkspacePlatforms(): MarketplacePlatform[] | undefined {\n\treturn sessionPlatforms ? [...sessionPlatforms] : undefined;\n}\n\n/**\n * Resolve the platforms a **plugin** should be produced for: an explicit per-call\n * selection wins, then the session's `--platform` targets, then\n * {@link DEFAULT_PLUGIN_PLATFORMS}.\n *\n * `agents` can never be the answer. An explicit `agents` throws — a caller asking\n * for a plugin in a format no marketplace accepts has made a mistake and should\n * hear about it. A session-level `agents` is *filtered* instead, because\n * `--platform` also drives workspace scaffolds, where `agents` is a perfectly\n * good choice; a user who set it for that reason should not have plugin authoring\n * blow up in their face. If filtering leaves nothing, the default applies.\n */\nexport function resolvePluginPlatforms(explicit?: readonly MarketplacePlatform[]): PluginPlatform[] {\n\tif (explicit && explicit.length > 0) {\n\t\tconst rejected = explicit.filter((p) => !isPluginPlatform(p));\n\t\tif (rejected.length > 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot author a plugin for platform \"${rejected.join(\", \")}\": plugins are distribution units and ` +\n\t\t\t\t\t\"the native `agents` layout belongs to no marketplace, so it can never be published or installed. \" +\n\t\t\t\t\t\"Target `claude` or `github`, or author a workspace skill instead.\",\n\t\t\t);\n\t\t}\n\t\treturn explicit.filter(isPluginPlatform);\n\t}\n\tconst fromSession = (sessionPlatforms ?? []).filter(isPluginPlatform);\n\treturn fromSession.length > 0 ? fromSession : [...DEFAULT_PLUGIN_PLATFORMS];\n}\n"]}
@@ -1,30 +1,42 @@
1
1
  /**
2
- * Session-wide artifact platform targeting (`--support-platform`).
2
+ * Session-wide artifact platform targeting (`--platform`).
3
3
  *
4
4
  * hoocode reads resources from every vendor convention, but when it *writes*
5
- * artifacts authored plugins via ProposePlugin, workspace scaffolds via
6
- * /new-skill //new-agent //new-command it needs a target layout. This module
7
- * owns that choice for the whole process:
5
+ * artifacts it needs a target layout. The two kinds of write have **different
6
+ * rules**, which is why this module exposes two resolvers rather than one:
7
+ *
8
+ * Plugins distribution units. A plugin only means anything as a
9
+ * member of a platform ecosystem (there is no `.agents`
10
+ * marketplace), so production is platform-specific and
11
+ * `agents` is never a valid target.
12
+ * → {@link resolvePluginPlatforms}
13
+ * Workspace scaffolds `/new-skill`, `/new-agent`, `/new-command`. These are
14
+ * consumed in place, never shipped, and `.agents/` is a
15
+ * real cross-vendor convention for them.
16
+ * → {@link getWorkspacePlatforms}
8
17
  *
9
18
  * token vocabulary `agents` (alias `native`), `claude`,
10
19
  * `github` (aliases `copilot`, `gh`)
11
- * session state set once at startup from the `--support-platform` flag
12
- * or the `supportPlatform` setting (main.ts)
13
- * resolution explicit per-call platforms → session targets →
14
- * {@link DEFAULT_AUTHORING_PLATFORMS}
20
+ * session state set once at startup from the `--platform` flag or the
21
+ * `platform` setting (main.ts)
22
+ *
23
+ * See docs/plugin-system-architecture.md §1 for the reasoning.
15
24
  *
16
25
  * Kept beside the format registry (and importing only `types.ts`) so the
17
26
  * platform vocabulary and the adapters stay a one-directory concern: adding a
18
27
  * vendor means a new adapter file plus a token here, nothing else.
19
28
  */
29
+ /** True for the platform tokens that own a plugin ecosystem. */
30
+ export function isPluginPlatform(platform) {
31
+ return platform !== "agents";
32
+ }
20
33
  /**
21
- * Default authoring target when neither the call nor the session picked any: the
22
- * portable native format. Authored artifacts are meant to be reusable, so they
23
- * default to one vendor-neutral layout (a strict superset hoocode reads directly)
24
- * rather than forking into vendor copies. Vendor layouts are an opt-in interop
25
- * concern, requested only via the `--support-platform` session flag.
34
+ * Default plugin production target when neither the call nor the session picked
35
+ * one. Claude is the default because it is the only platform whose local loop
36
+ * closes: an authored plugin lands in `~/.claude/skills/<id>/` and is live on the
37
+ * next session, whereas Copilot has no in-place drop-in at all.
26
38
  */
27
- export const DEFAULT_AUTHORING_PLATFORMS = ["agents"];
39
+ export const DEFAULT_PLUGIN_PLATFORMS = ["claude"];
28
40
  /** Canonicalize one platform token, folding the user-facing aliases. */
29
41
  export function normalizePlatformToken(token) {
30
42
  switch (token.trim().toLowerCase()) {
@@ -41,8 +53,8 @@ export function normalizePlatformToken(token) {
41
53
  return undefined;
42
54
  }
43
55
  }
44
- /** Parse raw `--support-platform` / `supportPlatform` tokens into canonical platforms. */
45
- export function parseSupportPlatforms(tokens) {
56
+ /** Parse raw `--platform` / `platform` tokens into canonical platforms. */
57
+ export function parsePlatforms(tokens) {
46
58
  const platforms = [];
47
59
  const invalid = [];
48
60
  for (const token of tokens) {
@@ -57,24 +69,44 @@ export function parseSupportPlatforms(tokens) {
57
69
  }
58
70
  return { platforms, invalid };
59
71
  }
60
- /** Session-wide targets. Undefined = not configured (fall back to defaults). */
61
- let sessionSupportPlatforms;
72
+ /** Session-wide targets. Undefined = not configured (fall back to per-consumer defaults). */
73
+ let sessionPlatforms;
62
74
  /** Set (or clear, with undefined/empty) the session's artifact platform targets. */
63
- export function setSupportPlatforms(platforms) {
64
- sessionSupportPlatforms = platforms && platforms.length > 0 ? [...platforms] : undefined;
75
+ export function setPlatforms(platforms) {
76
+ sessionPlatforms = platforms && platforms.length > 0 ? [...platforms] : undefined;
65
77
  }
66
- /** The session's configured targets, or undefined when `--support-platform` was not given. */
67
- export function getSupportPlatforms() {
68
- return sessionSupportPlatforms ? [...sessionSupportPlatforms] : undefined;
78
+ /**
79
+ * The session's configured targets for **workspace scaffolds**, or undefined when
80
+ * `--platform` was not given — callers treat undefined as "use the hoocode-native
81
+ * location" rather than substituting a default. `agents` is a legitimate value
82
+ * here: a scaffolded skill or command is consumed in place, not distributed.
83
+ */
84
+ export function getWorkspacePlatforms() {
85
+ return sessionPlatforms ? [...sessionPlatforms] : undefined;
69
86
  }
70
87
  /**
71
- * Resolve the platforms an authoring write should target: an explicit per-call
72
- * selection wins, then the session's `--support-platform` targets, then
73
- * {@link DEFAULT_AUTHORING_PLATFORMS}.
88
+ * Resolve the platforms a **plugin** should be produced for: an explicit per-call
89
+ * selection wins, then the session's `--platform` targets, then
90
+ * {@link DEFAULT_PLUGIN_PLATFORMS}.
91
+ *
92
+ * `agents` can never be the answer. An explicit `agents` throws — a caller asking
93
+ * for a plugin in a format no marketplace accepts has made a mistake and should
94
+ * hear about it. A session-level `agents` is *filtered* instead, because
95
+ * `--platform` also drives workspace scaffolds, where `agents` is a perfectly
96
+ * good choice; a user who set it for that reason should not have plugin authoring
97
+ * blow up in their face. If filtering leaves nothing, the default applies.
74
98
  */
75
- export function resolveAuthoringPlatforms(explicit) {
76
- if (explicit && explicit.length > 0)
77
- return [...explicit];
78
- return [...(sessionSupportPlatforms ?? DEFAULT_AUTHORING_PLATFORMS)];
99
+ export function resolvePluginPlatforms(explicit) {
100
+ if (explicit && explicit.length > 0) {
101
+ const rejected = explicit.filter((p) => !isPluginPlatform(p));
102
+ if (rejected.length > 0) {
103
+ throw new Error(`Cannot author a plugin for platform "${rejected.join(", ")}": plugins are distribution units and ` +
104
+ "the native `agents` layout belongs to no marketplace, so it can never be published or installed. " +
105
+ "Target `claude` or `github`, or author a workspace skill instead.");
106
+ }
107
+ return explicit.filter(isPluginPlatform);
108
+ }
109
+ const fromSession = (sessionPlatforms ?? []).filter(isPluginPlatform);
110
+ return fromSession.length > 0 ? fromSession : [...DEFAULT_PLUGIN_PLATFORMS];
79
111
  }
80
112
  //# sourceMappingURL=platform-targets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"platform-targets.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/platform-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAmC,CAAC,QAAQ,CAAC,CAAC;AAEtF,wEAAwE;AACxE,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAmC;IACtF,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QACpC,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ;YACZ,OAAO,QAAQ,CAAC;QACjB,KAAK,QAAQ;YACZ,OAAO,QAAQ,CAAC;QACjB,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,IAAI;YACR,OAAO,QAAQ,CAAC;QACjB;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACD;AASD,0FAA0F;AAC1F,MAAM,UAAU,qBAAqB,CAAC,MAAyB,EAAwB;IACtF,MAAM,SAAS,GAA0B,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,SAAS,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aACjC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAAA,CAC9B;AAED,gFAAgF;AAChF,IAAI,uBAA0D,CAAC;AAE/D,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,SAAqD,EAAQ;IAChG,uBAAuB,GAAG,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACzF;AAED,8FAA8F;AAC9F,MAAM,UAAU,mBAAmB,GAAsC;IACxE,OAAO,uBAAuB,CAAC,CAAC,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAC1E;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAyC,EAAyB;IAC3G,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,2BAA2B,CAAC,CAAC,CAAC;AAAA,CACrE","sourcesContent":["/**\n * Session-wide artifact platform targeting (`--support-platform`).\n *\n * hoocode reads resources from every vendor convention, but when it *writes*\n * artifacts authored plugins via ProposePlugin, workspace scaffolds via\n * /new-skill //new-agent //new-command it needs a target layout. This module\n * owns that choice for the whole process:\n *\n * token vocabulary `agents` (alias `native`), `claude`,\n * `github` (aliases `copilot`, `gh`)\n * session state set once at startup from the `--support-platform` flag\n * or the `supportPlatform` setting (main.ts)\n * resolution explicit per-call platforms session targets →\n * {@link DEFAULT_AUTHORING_PLATFORMS}\n *\n * Kept beside the format registry (and importing only `types.ts`) so the\n * platform vocabulary and the adapters stay a one-directory concern: adding a\n * vendor means a new adapter file plus a token here, nothing else.\n */\n\nimport type { MarketplacePlatform } from \"./types.js\";\n\n/**\n * Default authoring target when neither the call nor the session picked any: the\n * portable native format. Authored artifacts are meant to be reusable, so they\n * default to one vendor-neutral layout (a strict superset hoocode reads directly)\n * rather than forking into vendor copies. Vendor layouts are an opt-in interop\n * concern, requested only via the `--support-platform` session flag.\n */\nexport const DEFAULT_AUTHORING_PLATFORMS: readonly MarketplacePlatform[] = [\"agents\"];\n\n/** Canonicalize one platform token, folding the user-facing aliases. */\nexport function normalizePlatformToken(token: string): MarketplacePlatform | undefined {\n\tswitch (token.trim().toLowerCase()) {\n\t\tcase \"agents\":\n\t\tcase \"native\":\n\t\t\treturn \"agents\";\n\t\tcase \"claude\":\n\t\t\treturn \"claude\";\n\t\tcase \"github\":\n\t\tcase \"copilot\":\n\t\tcase \"gh\":\n\t\t\treturn \"github\";\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport interface SupportPlatformParse {\n\t/** Canonical platforms, deduped, in the order first mentioned. */\n\tplatforms: MarketplacePlatform[];\n\t/** Tokens that matched no known platform (surfaced as diagnostics, never fatal). */\n\tinvalid: string[];\n}\n\n/** Parse raw `--support-platform` / `supportPlatform` tokens into canonical platforms. */\nexport function parseSupportPlatforms(tokens: readonly string[]): SupportPlatformParse {\n\tconst platforms: MarketplacePlatform[] = [];\n\tconst invalid: string[] = [];\n\tfor (const token of tokens) {\n\t\tconst trimmed = token.trim();\n\t\tif (!trimmed) continue;\n\t\tconst canonical = normalizePlatformToken(trimmed);\n\t\tif (!canonical) invalid.push(trimmed);\n\t\telse if (!platforms.includes(canonical)) platforms.push(canonical);\n\t}\n\treturn { platforms, invalid };\n}\n\n/** Session-wide targets. Undefined = not configured (fall back to defaults). */\nlet sessionSupportPlatforms: MarketplacePlatform[] | undefined;\n\n/** Set (or clear, with undefined/empty) the session's artifact platform targets. */\nexport function setSupportPlatforms(platforms: readonly MarketplacePlatform[] | undefined): void {\n\tsessionSupportPlatforms = platforms && platforms.length > 0 ? [...platforms] : undefined;\n}\n\n/** The session's configured targets, or undefined when `--support-platform` was not given. */\nexport function getSupportPlatforms(): MarketplacePlatform[] | undefined {\n\treturn sessionSupportPlatforms ? [...sessionSupportPlatforms] : undefined;\n}\n\n/**\n * Resolve the platforms an authoring write should target: an explicit per-call\n * selection wins, then the session's `--support-platform` targets, then\n * {@link DEFAULT_AUTHORING_PLATFORMS}.\n */\nexport function resolveAuthoringPlatforms(explicit?: readonly MarketplacePlatform[]): MarketplacePlatform[] {\n\tif (explicit && explicit.length > 0) return [...explicit];\n\treturn [...(sessionSupportPlatforms ?? DEFAULT_AUTHORING_PLATFORMS)];\n}\n"]}
1
+ {"version":3,"file":"platform-targets.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/platform-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,gEAAgE;AAChE,MAAM,UAAU,gBAAgB,CAAC,QAA6B,EAA8B;IAC3F,OAAO,QAAQ,KAAK,QAAQ,CAAC;AAAA,CAC7B;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA8B,CAAC,QAAQ,CAAC,CAAC;AAE9E,wEAAwE;AACxE,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAmC;IACtF,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QACpC,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ;YACZ,OAAO,QAAQ,CAAC;QACjB,KAAK,QAAQ;YACZ,OAAO,QAAQ,CAAC;QACjB,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,IAAI;YACR,OAAO,QAAQ,CAAC;QACjB;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACD;AASD,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,MAAyB,EAAiB;IACxE,MAAM,SAAS,GAA0B,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,SAAS,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aACjC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAAA,CAC9B;AAED,6FAA6F;AAC7F,IAAI,gBAAmD,CAAC;AAExD,oFAAoF;AACpF,MAAM,UAAU,YAAY,CAAC,SAAqD,EAAQ;IACzF,gBAAgB,GAAG,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAClF;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,GAAsC;IAC1E,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAC5D;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAyC,EAAoB;IACnG,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACd,wCAAwC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,wCAAwC;gBAClG,mGAAmG;gBACnG,mEAAmE,CACpE,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;IACD,MAAM,WAAW,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACtE,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC;AAAA,CAC5E","sourcesContent":["/**\n * Session-wide artifact platform targeting (`--platform`).\n *\n * hoocode reads resources from every vendor convention, but when it *writes*\n * artifacts it needs a target layout. The two kinds of write have **different\n * rules**, which is why this module exposes two resolvers rather than one:\n *\n * Plugins distribution units. A plugin only means anything as a\n * member of a platform ecosystem (there is no `.agents`\n * marketplace), so production is platform-specific and\n * `agents` is never a valid target.\n * → {@link resolvePluginPlatforms}\n * Workspace scaffolds `/new-skill`, `/new-agent`, `/new-command`. These are\n * consumed in place, never shipped, and `.agents/` is a\n * real cross-vendor convention for them.\n * → {@link getWorkspacePlatforms}\n *\n * token vocabulary `agents` (alias `native`), `claude`,\n * `github` (aliases `copilot`, `gh`)\n * session state set once at startup from the `--platform` flag or the\n * `platform` setting (main.ts)\n *\n * See docs/plugin-system-architecture.md §1 for the reasoning.\n *\n * Kept beside the format registry (and importing only `types.ts`) so the\n * platform vocabulary and the adapters stay a one-directory concern: adding a\n * vendor means a new adapter file plus a token here, nothing else.\n */\n\nimport type { MarketplacePlatform } from \"./types.js\";\n\n/** The platforms a *plugin* can be produced for. `agents` is excluded by construction. */\nexport type PluginPlatform = Exclude<MarketplacePlatform, \"agents\">;\n\n/** True for the platform tokens that own a plugin ecosystem. */\nexport function isPluginPlatform(platform: MarketplacePlatform): platform is PluginPlatform {\n\treturn platform !== \"agents\";\n}\n\n/**\n * Default plugin production target when neither the call nor the session picked\n * one. Claude is the default because it is the only platform whose local loop\n * closes: an authored plugin lands in `~/.claude/skills/<id>/` and is live on the\n * next session, whereas Copilot has no in-place drop-in at all.\n */\nexport const DEFAULT_PLUGIN_PLATFORMS: readonly PluginPlatform[] = [\"claude\"];\n\n/** Canonicalize one platform token, folding the user-facing aliases. */\nexport function normalizePlatformToken(token: string): MarketplacePlatform | undefined {\n\tswitch (token.trim().toLowerCase()) {\n\t\tcase \"agents\":\n\t\tcase \"native\":\n\t\t\treturn \"agents\";\n\t\tcase \"claude\":\n\t\t\treturn \"claude\";\n\t\tcase \"github\":\n\t\tcase \"copilot\":\n\t\tcase \"gh\":\n\t\t\treturn \"github\";\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nexport interface PlatformParse {\n\t/** Canonical platforms, deduped, in the order first mentioned. */\n\tplatforms: MarketplacePlatform[];\n\t/** Tokens that matched no known platform (surfaced as diagnostics, never fatal). */\n\tinvalid: string[];\n}\n\n/** Parse raw `--platform` / `platform` tokens into canonical platforms. */\nexport function parsePlatforms(tokens: readonly string[]): PlatformParse {\n\tconst platforms: MarketplacePlatform[] = [];\n\tconst invalid: string[] = [];\n\tfor (const token of tokens) {\n\t\tconst trimmed = token.trim();\n\t\tif (!trimmed) continue;\n\t\tconst canonical = normalizePlatformToken(trimmed);\n\t\tif (!canonical) invalid.push(trimmed);\n\t\telse if (!platforms.includes(canonical)) platforms.push(canonical);\n\t}\n\treturn { platforms, invalid };\n}\n\n/** Session-wide targets. Undefined = not configured (fall back to per-consumer defaults). */\nlet sessionPlatforms: MarketplacePlatform[] | undefined;\n\n/** Set (or clear, with undefined/empty) the session's artifact platform targets. */\nexport function setPlatforms(platforms: readonly MarketplacePlatform[] | undefined): void {\n\tsessionPlatforms = platforms && platforms.length > 0 ? [...platforms] : undefined;\n}\n\n/**\n * The session's configured targets for **workspace scaffolds**, or undefined when\n * `--platform` was not given — callers treat undefined as \"use the hoocode-native\n * location\" rather than substituting a default. `agents` is a legitimate value\n * here: a scaffolded skill or command is consumed in place, not distributed.\n */\nexport function getWorkspacePlatforms(): MarketplacePlatform[] | undefined {\n\treturn sessionPlatforms ? [...sessionPlatforms] : undefined;\n}\n\n/**\n * Resolve the platforms a **plugin** should be produced for: an explicit per-call\n * selection wins, then the session's `--platform` targets, then\n * {@link DEFAULT_PLUGIN_PLATFORMS}.\n *\n * `agents` can never be the answer. An explicit `agents` throws — a caller asking\n * for a plugin in a format no marketplace accepts has made a mistake and should\n * hear about it. A session-level `agents` is *filtered* instead, because\n * `--platform` also drives workspace scaffolds, where `agents` is a perfectly\n * good choice; a user who set it for that reason should not have plugin authoring\n * blow up in their face. If filtering leaves nothing, the default applies.\n */\nexport function resolvePluginPlatforms(explicit?: readonly MarketplacePlatform[]): PluginPlatform[] {\n\tif (explicit && explicit.length > 0) {\n\t\tconst rejected = explicit.filter((p) => !isPluginPlatform(p));\n\t\tif (rejected.length > 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot author a plugin for platform \"${rejected.join(\", \")}\": plugins are distribution units and ` +\n\t\t\t\t\t\"the native `agents` layout belongs to no marketplace, so it can never be published or installed. \" +\n\t\t\t\t\t\"Target `claude` or `github`, or author a workspace skill instead.\",\n\t\t\t);\n\t\t}\n\t\treturn explicit.filter(isPluginPlatform);\n\t}\n\tconst fromSession = (sessionPlatforms ?? []).filter(isPluginPlatform);\n\treturn fromSession.length > 0 ? fromSession : [...DEFAULT_PLUGIN_PLATFORMS];\n}\n"]}
@@ -28,7 +28,17 @@ export interface RawManifest {
28
28
  mcpServers?: Record<string, unknown> | string;
29
29
  /** Native-only. */
30
30
  providers?: unknown;
31
+ /** Copilot: extension directories, `{ paths, exclusive }`. Parsed for preservation only. */
32
+ extensions?: unknown;
33
+ /** Copilot: LSP config path or inline definitions. Parsed for preservation only. */
34
+ lspServers?: unknown;
35
+ /** Any key the adapter does not model. */
36
+ [key: string]: unknown;
31
37
  }
38
+ /** Manifest keys not modelled here, preserved verbatim so a re-emit cannot drop them. */
39
+ export declare function unknownManifestFields(raw: RawManifest): Record<string, unknown> | undefined;
40
+ /** Which unsupported surfaces are present under `root`. */
41
+ export declare function detectUnsupportedSurfaces(root: string): string[] | undefined;
32
42
  export declare function readJson<T>(file: string): T | null;
33
43
  export declare function dirIfExists(root: string, name: string): string | undefined;
34
44
  /**
@@ -75,4 +85,16 @@ export declare function toolsYamlList(tools: string | undefined): string | undef
75
85
  * `<root>/commands/<name>.md`. Copilot's differs and lives in its adapter.
76
86
  */
77
87
  export declare function claudeStyleWorkspace(root: string): WorkspaceLayout;
88
+ /** The manifest keys the adapters model, as a sorted list. Input to the drift check (§2.2). */
89
+ export declare function modelledManifestKeys(): string[];
90
+ /**
91
+ * Surfaces we know about and deliberately do not load, as the **relative paths**
92
+ * they actually occupy rather than their display labels.
93
+ *
94
+ * The distinction bit once already: the labels are written for humans
95
+ * (`"monitors/"`), the vendor reference writes the real path
96
+ * (`monitors/monitors.json`), and a drift check comparing the two reported a
97
+ * known surface as a new finding.
98
+ */
99
+ export declare function knownUnsupportedSurfaces(): string[];
78
100
  //# sourceMappingURL=shared.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,gIAAgI;IAChI,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,iBAAiB,GAAG;QAAE,KAAK,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;IAC1D,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAC9C,mBAAmB;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAMlD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ9G;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,MAAM,GAAG,SAAS,CAE7E;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAmBrG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,EACjD,IAAI,EAAE,MAAM,EACZ,WAAW,SAAc,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAOrC;AA2BD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7F;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED,kGAAkG;AAClG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWpF;AAED,+EAA+E;AAC/E,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO3E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAgBlE","sourcesContent":["/**\n * Shared, format-agnostic helpers used by the plugin format adapters.\n *\n * Kept deliberately free of any runtime dependency on `../manifest.js` (it only\n * imports *types* from there) so the adapter registry can be wired without a\n * module cycle: `manifest.ts → formats/index.ts → formats/<adapter>.ts →\n * shared.ts`, with the back-reference to manifest types erased at compile time.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { PluginHooksConfig } from \"../manifest.js\";\nimport type { AuthoredHook, WorkspaceLayout } from \"./types.js\";\n\n/** Raw manifest shape accepted by the `plugin.json`-style formats (agents/claude/copilot). */\nexport interface RawManifest {\n\tname?: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string | { name?: string };\n\t/** Optional capability-dir override(s): a path (or list of paths) relative to the plugin root, e.g. `\"skills\": \"./skills/\"`. */\n\tskills?: unknown;\n\tcommands?: unknown;\n\tagents?: unknown;\n\tthemes?: unknown;\n\thooks?: PluginHooksConfig | { hooks?: PluginHooksConfig };\n\t/** Inline server map, or a path (Claude Code form, e.g. `\"./.mcp.json\"`) relative to the plugin root. */\n\tmcpServers?: Record<string, unknown> | string;\n\t/** Native-only. */\n\tproviders?: unknown;\n}\n\nexport function readJson<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function dirIfExists(root: string, name: string): string | undefined {\n\tconst p = path.join(root, name);\n\treturn fs.existsSync(p) && fs.statSync(p).isDirectory() ? p : undefined;\n}\n\n/**\n * Resolve a capability directory: an explicit manifest override — a path (or\n * list of paths, first existing wins) relative to the plugin root, e.g.\n * `\"skills\": \"./skills/\"` — beats the conventional directory.\n */\nexport function resolveCapabilityDir(root: string, override: unknown, conventional: string): string | undefined {\n\tconst candidates = Array.isArray(override) ? override : [override];\n\tfor (const c of candidates) {\n\t\tif (typeof c !== \"string\" || !c.trim()) continue;\n\t\tconst p = path.resolve(root, c.trim());\n\t\tif (fs.existsSync(p) && fs.statSync(p).isDirectory()) return p;\n\t}\n\treturn dirIfExists(root, conventional);\n}\n\n/** Coerce an author field (string or `{ name }`) to a plain string. */\nexport function parseAuthor(author: RawManifest[\"author\"]): string | undefined {\n\treturn typeof author === \"string\" ? author : author?.name;\n}\n\n/** Accept either `{ hooks: {...} }` or a bare event map, falling back to `hooks/hooks.json`. */\nexport function normalizeHooks(raw: RawManifest[\"hooks\"], root: string): PluginHooksConfig | undefined {\n\tlet config: PluginHooksConfig | undefined;\n\tif (raw && typeof raw === \"object\") {\n\t\tconfig = \"hooks\" in raw && raw.hooks ? (raw.hooks as PluginHooksConfig) : (raw as PluginHooksConfig);\n\t}\n\t// Fall back to the conventional hooks/hooks.json file.\n\tif (!config) {\n\t\tconst hooksFile = path.join(root, \"hooks\", \"hooks.json\");\n\t\tif (fs.existsSync(hooksFile)) {\n\t\t\tconst fileRaw = readJson<RawManifest[\"hooks\"]>(hooksFile);\n\t\t\tif (fileRaw && typeof fileRaw === \"object\") {\n\t\t\t\tconfig =\n\t\t\t\t\t\"hooks\" in fileRaw && fileRaw.hooks\n\t\t\t\t\t\t? (fileRaw.hooks as PluginHooksConfig)\n\t\t\t\t\t\t: (fileRaw as PluginHooksConfig);\n\t\t\t}\n\t\t}\n\t}\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\n/**\n * Resolve inline `mcpServers`, a manifest-supplied file path, or a `.mcp.json`\n * file at `root`.\n *\n * `raw` may be:\n * - a string path (Claude Code form: `\"mcpServers\": \"./.mcp.json\"`), resolved\n * relative to the plugin root and read as a server file;\n * - an inline server map (returned as-is);\n * - undefined, falling back to `<root>/<mcpFileName>`.\n *\n * Server files use `{ mcpServers }` (Claude/native) or `{ servers }` (Code/Copilot);\n * both keys are accepted.\n */\nexport function normalizeMcp(\n\traw: Record<string, unknown> | string | undefined,\n\troot: string,\n\tmcpFileName = \".mcp.json\",\n): Record<string, unknown> | undefined {\n\t// Claude Code form: a path to a server file, relative to the plugin root.\n\tif (typeof raw === \"string\") {\n\t\treturn raw.trim() ? readMcpFile(path.resolve(root, raw.trim())) : undefined;\n\t}\n\tif (raw && Object.keys(raw).length > 0) return raw;\n\treturn readMcpFile(path.join(root, mcpFileName));\n}\n\n/** Read a server file, accepting both `{ mcpServers }` and `{ servers }` keys. */\nfunction readMcpFile(mcpFile: string): Record<string, unknown> | undefined {\n\tif (!fs.existsSync(mcpFile)) return undefined;\n\tconst fileRaw = readJson<{ mcpServers?: Record<string, unknown>; servers?: Record<string, unknown> }>(mcpFile);\n\tconst servers = fileRaw?.mcpServers ?? fileRaw?.servers;\n\treturn servers && Object.keys(servers).length > 0 ? servers : undefined;\n}\n\n// ============================================================================\n// Emit helpers (used by the writer / scaffolding half of each adapter)\n// ============================================================================\n\n/** Serialize a small set of frontmatter fields to YAML. Values are strings only. */\nfunction emitFrontmatter(fields: Record<string, string | undefined>): string {\n\tconst lines: string[] = [\"---\"];\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (value === undefined || value === \"\") continue;\n\t\t// Quote values that could be misparsed as YAML (contain a colon-space or start punctuation).\n\t\tconst needsQuote = /[:#]|^[\\s>|@`&*!%]/.test(value) || value.includes(\"\\n\");\n\t\tlines.push(`${key}: ${needsQuote ? JSON.stringify(value) : value}`);\n\t}\n\tlines.push(\"---\");\n\treturn lines.join(\"\\n\");\n}\n\n/** A markdown capability file: frontmatter block + body. */\nexport function emitMarkdown(fields: Record<string, string | undefined>, body: string): string {\n\treturn `${emitFrontmatter(fields)}\\n\\n${body.trimEnd()}\\n`;\n}\n\n/** Pretty-print a JSON manifest with a trailing newline (matches repo convention). */\nexport function emitJson(value: unknown): string {\n\treturn `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/** Convert authored hooks into the on-disk `hooks.json` event-map shape shared by all formats. */\nexport function authoredHooksToConfig(hooks: AuthoredHook[]): Record<string, unknown> {\n\tconst byEvent: Record<string, Array<{ matcher?: string; hooks: unknown[] }>> = {};\n\tfor (const h of hooks) {\n\t\tconst group = byEvent[h.event] ?? [];\n\t\tbyEvent[h.event] = group;\n\t\tgroup.push({\n\t\t\t...(h.matcher ? { matcher: h.matcher } : {}),\n\t\t\thooks: [{ type: \"command\", command: h.command, ...(h.timeout ? { timeout: h.timeout } : {}) }],\n\t\t});\n\t}\n\treturn byEvent;\n}\n\n/** Slug-safe filename component (no path separators, conservative charset). */\nexport function slug(name: string): string {\n\treturn name\n\t\t.trim()\n\t\t.replace(/[^a-zA-Z0-9._-]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 80);\n}\n\n/**\n * Render a comma-separated tool allowlist (Claude Code convention, e.g.\n * \"read, grep\") as a YAML flow sequence (`['read', 'grep']`) for formats whose\n * frontmatter takes tools as a list (Copilot custom agents / prompt files).\n */\nexport function toolsYamlList(tools: string | undefined): string | undefined {\n\tconst items = (tools ?? \"\")\n\t\t.split(/[,\\s]+/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\tif (items.length === 0) return undefined;\n\treturn `[${items.map((t) => `'${t.replace(/'/g, \"''\")}'`).join(\", \")}]`;\n}\n\n/**\n * The workspace layout shared by the `.agents/` and `.claude/` conventions:\n * `<root>/skills/<name>/SKILL.md`, `<root>/agents/<name>.md`,\n * `<root>/commands/<name>.md`. Copilot's differs and lives in its adapter.\n */\nexport function claudeStyleWorkspace(root: string): WorkspaceLayout {\n\treturn {\n\t\troot,\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(root, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(root, \"agents\", `${slug(a.name)}.md`),\n\t\t\tcontent: emitMarkdown({ name: a.name, description: a.description, tools: a.tools, model: a.model }, a.body),\n\t\t}),\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(root, \"commands\", `${slug(c.name)}.md`),\n\t\t\tcontent: emitMarkdown({ name: c.name, description: c.description }, c.body),\n\t\t}),\n\t};\n}\n"]}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,gIAAgI;IAChI,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,iBAAiB,GAAG;QAAE,KAAK,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;IAC1D,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAC9C,mBAAmB;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oFAAoF;IACpF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0CAA0C;IAC1C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AA0BD,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAM3F;AAkBD,2DAA2D;AAC3D,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAG5E;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAMlD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ9G;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,MAAM,GAAG,SAAS,CAE7E;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAmBrG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,SAAS,EACjD,IAAI,EAAE,MAAM,EACZ,WAAW,SAAc,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAOrC;AAmCD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7F;AAED,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED,kGAAkG;AAClG,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWpF;AAED,+EAA+E;AAC/E,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO3E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAgBlE;AAED,gGAA+F;AAC/F,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAE/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CAEnD","sourcesContent":["/**\n * Shared, format-agnostic helpers used by the plugin format adapters.\n *\n * Kept deliberately free of any runtime dependency on `../manifest.js` (it only\n * imports *types* from there) so the adapter registry can be wired without a\n * module cycle: `manifest.ts → formats/index.ts → formats/<adapter>.ts →\n * shared.ts`, with the back-reference to manifest types erased at compile time.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { PluginHooksConfig } from \"../manifest.js\";\nimport type { AuthoredHook, WorkspaceLayout } from \"./types.js\";\n\n/** Raw manifest shape accepted by the `plugin.json`-style formats (agents/claude/copilot). */\nexport interface RawManifest {\n\tname?: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string | { name?: string };\n\t/** Optional capability-dir override(s): a path (or list of paths) relative to the plugin root, e.g. `\"skills\": \"./skills/\"`. */\n\tskills?: unknown;\n\tcommands?: unknown;\n\tagents?: unknown;\n\tthemes?: unknown;\n\thooks?: PluginHooksConfig | { hooks?: PluginHooksConfig };\n\t/** Inline server map, or a path (Claude Code form, e.g. `\"./.mcp.json\"`) relative to the plugin root. */\n\tmcpServers?: Record<string, unknown> | string;\n\t/** Native-only. */\n\tproviders?: unknown;\n\t/** Copilot: extension directories, `{ paths, exclusive }`. Parsed for preservation only. */\n\textensions?: unknown;\n\t/** Copilot: LSP config path or inline definitions. Parsed for preservation only. */\n\tlspServers?: unknown;\n\t/** Any key the adapter does not model. */\n\t[key: string]: unknown;\n}\n\n/**\n * Manifest keys every adapter models; anything else is preserved via\n * `unknownFields`.\n *\n * Surfaced through {@link declaredVocabulary} so the Tier 2 drift check has\n * something to diff the vendor references against. A checker that inferred our\n * coverage by reading the parser would be checking its own inference; a declared\n * set is a claim the codebase makes and the check can falsify.\n */\nconst MODELLED_MANIFEST_KEYS = new Set([\n\t\"name\",\n\t\"version\",\n\t\"description\",\n\t\"author\",\n\t\"skills\",\n\t\"commands\",\n\t\"agents\",\n\t\"themes\",\n\t\"hooks\",\n\t\"mcpServers\",\n\t\"providers\",\n\t\"$schema\",\n]);\n\n/** Manifest keys not modelled here, preserved verbatim so a re-emit cannot drop them. */\nexport function unknownManifestFields(raw: RawManifest): Record<string, unknown> | undefined {\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(raw)) {\n\t\tif (!MODELLED_MANIFEST_KEYS.has(key)) out[key] = value;\n\t}\n\treturn Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * On-disk component surfaces hoocode does not load, checked against both vendors'\n * file-location tables. The files are left alone; naming them is what makes the\n * gap visible (see NormalizedPlugin.unsupportedSurfaces).\n */\nconst UNSUPPORTED_SURFACES: ReadonlyArray<{ rel: string; label: string }> = [\n\t{ rel: \"workflows\", label: \"workflows/\" },\n\t{ rel: \"output-styles\", label: \"output-styles/\" },\n\t{ rel: \"monitors/monitors.json\", label: \"monitors/\" },\n\t{ rel: \"bin\", label: \"bin/\" },\n\t{ rel: \"settings.json\", label: \"settings.json\" },\n\t{ rel: \".lsp.json\", label: \".lsp.json\" },\n\t{ rel: \"lsp.json\", label: \"lsp.json\" },\n\t{ rel: path.join(\".github\", \"lsp.json\"), label: \".github/lsp.json\" },\n];\n\n/** Which unsupported surfaces are present under `root`. */\nexport function detectUnsupportedSurfaces(root: string): string[] | undefined {\n\tconst found = UNSUPPORTED_SURFACES.filter((s) => fs.existsSync(path.join(root, s.rel))).map((s) => s.label);\n\treturn found.length > 0 ? found : undefined;\n}\n\nexport function readJson<T>(file: string): T | null {\n\ttry {\n\t\treturn JSON.parse(fs.readFileSync(file, \"utf8\")) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function dirIfExists(root: string, name: string): string | undefined {\n\tconst p = path.join(root, name);\n\treturn fs.existsSync(p) && fs.statSync(p).isDirectory() ? p : undefined;\n}\n\n/**\n * Resolve a capability directory: an explicit manifest override — a path (or\n * list of paths, first existing wins) relative to the plugin root, e.g.\n * `\"skills\": \"./skills/\"` — beats the conventional directory.\n */\nexport function resolveCapabilityDir(root: string, override: unknown, conventional: string): string | undefined {\n\tconst candidates = Array.isArray(override) ? override : [override];\n\tfor (const c of candidates) {\n\t\tif (typeof c !== \"string\" || !c.trim()) continue;\n\t\tconst p = path.resolve(root, c.trim());\n\t\tif (fs.existsSync(p) && fs.statSync(p).isDirectory()) return p;\n\t}\n\treturn dirIfExists(root, conventional);\n}\n\n/** Coerce an author field (string or `{ name }`) to a plain string. */\nexport function parseAuthor(author: RawManifest[\"author\"]): string | undefined {\n\treturn typeof author === \"string\" ? author : author?.name;\n}\n\n/** Accept either `{ hooks: {...} }` or a bare event map, falling back to `hooks/hooks.json`. */\nexport function normalizeHooks(raw: RawManifest[\"hooks\"], root: string): PluginHooksConfig | undefined {\n\tlet config: PluginHooksConfig | undefined;\n\tif (raw && typeof raw === \"object\") {\n\t\tconfig = \"hooks\" in raw && raw.hooks ? (raw.hooks as PluginHooksConfig) : (raw as PluginHooksConfig);\n\t}\n\t// Fall back to the conventional hooks/hooks.json file.\n\tif (!config) {\n\t\tconst hooksFile = path.join(root, \"hooks\", \"hooks.json\");\n\t\tif (fs.existsSync(hooksFile)) {\n\t\t\tconst fileRaw = readJson<RawManifest[\"hooks\"]>(hooksFile);\n\t\t\tif (fileRaw && typeof fileRaw === \"object\") {\n\t\t\t\tconfig =\n\t\t\t\t\t\"hooks\" in fileRaw && fileRaw.hooks\n\t\t\t\t\t\t? (fileRaw.hooks as PluginHooksConfig)\n\t\t\t\t\t\t: (fileRaw as PluginHooksConfig);\n\t\t\t}\n\t\t}\n\t}\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\n/**\n * Resolve inline `mcpServers`, a manifest-supplied file path, or a `.mcp.json`\n * file at `root`.\n *\n * `raw` may be:\n * - a string path (Claude Code form: `\"mcpServers\": \"./.mcp.json\"`), resolved\n * relative to the plugin root and read as a server file;\n * - an inline server map (returned as-is);\n * - undefined, falling back to `<root>/<mcpFileName>`.\n *\n * Server files use `{ mcpServers }` (Claude/native) or `{ servers }` (Code/Copilot);\n * both keys are accepted.\n */\nexport function normalizeMcp(\n\traw: Record<string, unknown> | string | undefined,\n\troot: string,\n\tmcpFileName = \".mcp.json\",\n): Record<string, unknown> | undefined {\n\t// Claude Code form: a path to a server file, relative to the plugin root.\n\tif (typeof raw === \"string\") {\n\t\treturn raw.trim() ? readMcpFile(path.resolve(root, raw.trim())) : undefined;\n\t}\n\tif (raw && Object.keys(raw).length > 0) return raw;\n\treturn readMcpFile(path.join(root, mcpFileName));\n}\n\n/** Read a server file, accepting both `{ mcpServers }` and `{ servers }` keys. */\nfunction readMcpFile(mcpFile: string): Record<string, unknown> | undefined {\n\tif (!fs.existsSync(mcpFile)) return undefined;\n\tconst fileRaw = readJson<{ mcpServers?: Record<string, unknown>; servers?: Record<string, unknown> }>(mcpFile);\n\tconst servers = fileRaw?.mcpServers ?? fileRaw?.servers;\n\treturn servers && Object.keys(servers).length > 0 ? servers : undefined;\n}\n\n// ============================================================================\n// Emit helpers (used by the writer / scaffolding half of each adapter)\n// ============================================================================\n\n/** Serialize a small set of frontmatter fields to YAML. Values are strings only. */\n/**\n * Render frontmatter, or nothing at all when every field is empty.\n *\n * Emitting bare `---\\n---` looks harmless and is not: YAML parses an empty\n * document as `null`, and Claude Code rejects a component whose frontmatter is\n * not a mapping. A command authored without a description hit exactly that, and\n * the artifact was invalid in the ecosystem it was written for while\n * round-tripping happily through our own reader.\n */\nfunction emitFrontmatter(fields: Record<string, string | undefined>): string {\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(fields)) {\n\t\tif (value === undefined || value === \"\") continue;\n\t\t// Quote values that could be misparsed as YAML (contain a colon-space or start punctuation).\n\t\tconst needsQuote = /[:#]|^[\\s>|@`&*!%]/.test(value) || value.includes(\"\\n\");\n\t\tlines.push(`${key}: ${needsQuote ? JSON.stringify(value) : value}`);\n\t}\n\treturn lines.length > 0 ? [\"---\", ...lines, \"---\"].join(\"\\n\") : \"\";\n}\n\n/** A markdown capability file: frontmatter block + body. */\nexport function emitMarkdown(fields: Record<string, string | undefined>, body: string): string {\n\tconst frontmatter = emitFrontmatter(fields);\n\treturn frontmatter ? `${frontmatter}\\n\\n${body.trimEnd()}\\n` : `${body.trimEnd()}\\n`;\n}\n\n/** Pretty-print a JSON manifest with a trailing newline (matches repo convention). */\nexport function emitJson(value: unknown): string {\n\treturn `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/** Convert authored hooks into the on-disk `hooks.json` event-map shape shared by all formats. */\nexport function authoredHooksToConfig(hooks: AuthoredHook[]): Record<string, unknown> {\n\tconst byEvent: Record<string, Array<{ matcher?: string; hooks: unknown[] }>> = {};\n\tfor (const h of hooks) {\n\t\tconst group = byEvent[h.event] ?? [];\n\t\tbyEvent[h.event] = group;\n\t\tgroup.push({\n\t\t\t...(h.matcher ? { matcher: h.matcher } : {}),\n\t\t\thooks: [{ type: \"command\", command: h.command, ...(h.timeout ? { timeout: h.timeout } : {}) }],\n\t\t});\n\t}\n\treturn byEvent;\n}\n\n/** Slug-safe filename component (no path separators, conservative charset). */\nexport function slug(name: string): string {\n\treturn name\n\t\t.trim()\n\t\t.replace(/[^a-zA-Z0-9._-]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 80);\n}\n\n/**\n * Render a comma-separated tool allowlist (Claude Code convention, e.g.\n * \"read, grep\") as a YAML flow sequence (`['read', 'grep']`) for formats whose\n * frontmatter takes tools as a list (Copilot custom agents / prompt files).\n */\nexport function toolsYamlList(tools: string | undefined): string | undefined {\n\tconst items = (tools ?? \"\")\n\t\t.split(/[,\\s]+/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\tif (items.length === 0) return undefined;\n\treturn `[${items.map((t) => `'${t.replace(/'/g, \"''\")}'`).join(\", \")}]`;\n}\n\n/**\n * The workspace layout shared by the `.agents/` and `.claude/` conventions:\n * `<root>/skills/<name>/SKILL.md`, `<root>/agents/<name>.md`,\n * `<root>/commands/<name>.md`. Copilot's differs and lives in its adapter.\n */\nexport function claudeStyleWorkspace(root: string): WorkspaceLayout {\n\treturn {\n\t\troot,\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(root, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(root, \"agents\", `${slug(a.name)}.md`),\n\t\t\tcontent: emitMarkdown({ name: a.name, description: a.description, tools: a.tools, model: a.model }, a.body),\n\t\t}),\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(root, \"commands\", `${slug(c.name)}.md`),\n\t\t\tcontent: emitMarkdown({ name: c.name, description: c.description }, c.body),\n\t\t}),\n\t};\n}\n\n/** The manifest keys the adapters model, as a sorted list. Input to the drift check (§2.2). */\nexport function modelledManifestKeys(): string[] {\n\treturn [...MODELLED_MANIFEST_KEYS].sort();\n}\n\n/**\n * Surfaces we know about and deliberately do not load, as the **relative paths**\n * they actually occupy rather than their display labels.\n *\n * The distinction bit once already: the labels are written for humans\n * (`\"monitors/\"`), the vendor reference writes the real path\n * (`monitors/monitors.json`), and a drift check comparing the two reported a\n * known surface as a new finding.\n */\nexport function knownUnsupportedSurfaces(): string[] {\n\treturn UNSUPPORTED_SURFACES.map((s) => s.rel.replace(/\\\\/g, \"/\")).sort();\n}\n"]}
@@ -8,6 +8,58 @@
8
8
  */
9
9
  import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
+ /**
12
+ * Manifest keys every adapter models; anything else is preserved via
13
+ * `unknownFields`.
14
+ *
15
+ * Surfaced through {@link declaredVocabulary} so the Tier 2 drift check has
16
+ * something to diff the vendor references against. A checker that inferred our
17
+ * coverage by reading the parser would be checking its own inference; a declared
18
+ * set is a claim the codebase makes and the check can falsify.
19
+ */
20
+ const MODELLED_MANIFEST_KEYS = new Set([
21
+ "name",
22
+ "version",
23
+ "description",
24
+ "author",
25
+ "skills",
26
+ "commands",
27
+ "agents",
28
+ "themes",
29
+ "hooks",
30
+ "mcpServers",
31
+ "providers",
32
+ "$schema",
33
+ ]);
34
+ /** Manifest keys not modelled here, preserved verbatim so a re-emit cannot drop them. */
35
+ export function unknownManifestFields(raw) {
36
+ const out = {};
37
+ for (const [key, value] of Object.entries(raw)) {
38
+ if (!MODELLED_MANIFEST_KEYS.has(key))
39
+ out[key] = value;
40
+ }
41
+ return Object.keys(out).length > 0 ? out : undefined;
42
+ }
43
+ /**
44
+ * On-disk component surfaces hoocode does not load, checked against both vendors'
45
+ * file-location tables. The files are left alone; naming them is what makes the
46
+ * gap visible (see NormalizedPlugin.unsupportedSurfaces).
47
+ */
48
+ const UNSUPPORTED_SURFACES = [
49
+ { rel: "workflows", label: "workflows/" },
50
+ { rel: "output-styles", label: "output-styles/" },
51
+ { rel: "monitors/monitors.json", label: "monitors/" },
52
+ { rel: "bin", label: "bin/" },
53
+ { rel: "settings.json", label: "settings.json" },
54
+ { rel: ".lsp.json", label: ".lsp.json" },
55
+ { rel: "lsp.json", label: "lsp.json" },
56
+ { rel: path.join(".github", "lsp.json"), label: ".github/lsp.json" },
57
+ ];
58
+ /** Which unsupported surfaces are present under `root`. */
59
+ export function detectUnsupportedSurfaces(root) {
60
+ const found = UNSUPPORTED_SURFACES.filter((s) => fs.existsSync(path.join(root, s.rel))).map((s) => s.label);
61
+ return found.length > 0 ? found : undefined;
62
+ }
11
63
  export function readJson(file) {
12
64
  try {
13
65
  return JSON.parse(fs.readFileSync(file, "utf8"));
@@ -95,8 +147,17 @@ function readMcpFile(mcpFile) {
95
147
  // Emit helpers (used by the writer / scaffolding half of each adapter)
96
148
  // ============================================================================
97
149
  /** Serialize a small set of frontmatter fields to YAML. Values are strings only. */
150
+ /**
151
+ * Render frontmatter, or nothing at all when every field is empty.
152
+ *
153
+ * Emitting bare `---\n---` looks harmless and is not: YAML parses an empty
154
+ * document as `null`, and Claude Code rejects a component whose frontmatter is
155
+ * not a mapping. A command authored without a description hit exactly that, and
156
+ * the artifact was invalid in the ecosystem it was written for while
157
+ * round-tripping happily through our own reader.
158
+ */
98
159
  function emitFrontmatter(fields) {
99
- const lines = ["---"];
160
+ const lines = [];
100
161
  for (const [key, value] of Object.entries(fields)) {
101
162
  if (value === undefined || value === "")
102
163
  continue;
@@ -104,12 +165,12 @@ function emitFrontmatter(fields) {
104
165
  const needsQuote = /[:#]|^[\s>|@`&*!%]/.test(value) || value.includes("\n");
105
166
  lines.push(`${key}: ${needsQuote ? JSON.stringify(value) : value}`);
106
167
  }
107
- lines.push("---");
108
- return lines.join("\n");
168
+ return lines.length > 0 ? ["---", ...lines, "---"].join("\n") : "";
109
169
  }
110
170
  /** A markdown capability file: frontmatter block + body. */
111
171
  export function emitMarkdown(fields, body) {
112
- return `${emitFrontmatter(fields)}\n\n${body.trimEnd()}\n`;
172
+ const frontmatter = emitFrontmatter(fields);
173
+ return frontmatter ? `${frontmatter}\n\n${body.trimEnd()}\n` : `${body.trimEnd()}\n`;
113
174
  }
114
175
  /** Pretty-print a JSON manifest with a trailing newline (matches repo convention). */
115
176
  export function emitJson(value) {
@@ -172,4 +233,20 @@ export function claudeStyleWorkspace(root) {
172
233
  }),
173
234
  };
174
235
  }
236
+ /** The manifest keys the adapters model, as a sorted list. Input to the drift check (§2.2). */
237
+ export function modelledManifestKeys() {
238
+ return [...MODELLED_MANIFEST_KEYS].sort();
239
+ }
240
+ /**
241
+ * Surfaces we know about and deliberately do not load, as the **relative paths**
242
+ * they actually occupy rather than their display labels.
243
+ *
244
+ * The distinction bit once already: the labels are written for humans
245
+ * (`"monitors/"`), the vendor reference writes the real path
246
+ * (`monitors/monitors.json`), and a drift check comparing the two reported a
247
+ * known surface as a new finding.
248
+ */
249
+ export function knownUnsupportedSurfaces() {
250
+ return UNSUPPORTED_SURFACES.map((s) => s.rel.replace(/\\/g, "/")).sort();
251
+ }
175
252
  //# sourceMappingURL=shared.js.map