@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 +1 @@
1
- {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/shared.ts"],"names":[],"mappings":"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","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.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/shared.ts"],"names":[],"mappings":"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","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"]}
@@ -92,6 +92,13 @@ export interface PluginDraft {
92
92
  agents?: AuthoredAgent[];
93
93
  hooks?: AuthoredHook[];
94
94
  mcpServers?: AuthoredMcpServer[];
95
+ /**
96
+ * Manifest keys carried through from an existing plugin that this codebase
97
+ * does not model (see {@link NormalizedPlugin.unknownFields}). Emitters write
98
+ * them back verbatim so editing a plugin never strips a vendor key we happen
99
+ * not to understand. Never set when authoring from scratch.
100
+ */
101
+ unknownFields?: Record<string, unknown>;
95
102
  }
96
103
  /**
97
104
  * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —
@@ -128,8 +135,10 @@ export interface PluginFormatAdapter {
128
135
  * and `.github/marketplace.json`) list every location they accept.
129
136
  */
130
137
  readonly marketplaceFiles: readonly string[];
131
- /** True if `root` carries this format's plugin manifest. */
138
+ /** True if `root` is a plugin of this format including, where the vendor allows it, without a manifest. */
132
139
  detectPlugin(root: string): boolean;
140
+ /** True only if `root` carries this format's manifest *file*, ignoring manifest-optional rules. */
141
+ hasManifest(root: string): boolean;
133
142
  /** Parse `root` as a plugin of this format. Returns null when unparseable. */
134
143
  parsePlugin(root: string): NormalizedPlugin | null;
135
144
  /** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin format adapter contract.\n *\n * A `PluginFormatAdapter` owns *everything* format-specific for one vendor\n * layout — how a plugin directory is detected and parsed (the reader), how a\n * marketplace index file is located, and how a plugin is scaffolded from a\n * normalized draft (the writer). The rest of the plugin subsystem is written\n * against {@link NormalizedPlugin} / {@link PluginDraft} and never branches on a\n * format again.\n *\n * This is the extension point the plugin system is designed around: Claude Code\n * and GitHub Copilot conventions evolve independently, so each lives in its own\n * adapter file ({@link ./claude.ts}, {@link ./copilot.ts}, {@link ./agents.ts}).\n * Supporting a new vendor — or tracking a change in an existing one — means\n * editing (or adding) exactly one adapter and registering it; no other module\n * changes.\n */\n\nimport type { NormalizedPlugin } from \"../manifest.js\";\n\n/** Internal format id — matches `NormalizedPlugin.format` / `NormalizedMarketplace.format`. */\nexport type PluginFormatId = \"agents\" | \"claude\" | \"copilot\";\n\n/**\n * Public platform token surfaced to users. The Copilot format lives under\n * `.github/`, so it is surfaced as `\"github\"` rather than the internal\n * `\"copilot\"`.\n */\nexport type MarketplacePlatform = \"agents\" | \"claude\" | \"github\";\n\n/** A file produced by a format's scaffolder. `path` is relative to the plugin root (POSIX). */\nexport interface EmittedFile {\n\tpath: string;\n\tcontent: string;\n}\n\n/** An authored skill: passive instructions loaded lazily (SKILL.md-style). */\ninterface AuthoredSkill {\n\tname: string;\n\tdescription?: string;\n\t/** Instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored slash command: a passive prompt template. */\ninterface AuthoredCommand {\n\tname: string;\n\tdescription?: string;\n\t/** Prompt template body (markdown). */\n\tbody: string;\n}\n\n/** An authored subagent definition. `tools` is a comma-separated allowlist (Claude Code convention). */\ninterface AuthoredAgent {\n\tname: string;\n\tdescription?: string;\n\t/** Comma-separated `allowed-tools` string, e.g. \"read, grep, glob\". */\n\ttools?: string;\n\tmodel?: string;\n\t/** System-prompt / instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored shell hook wired to a tool event. Active code — gated on authoring. */\nexport interface AuthoredHook {\n\t/** Claude Code event name: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ... */\n\tevent: string;\n\t/** Regex matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\tcommand: string;\n\t/** Timeout in seconds. */\n\ttimeout?: number;\n}\n\n/** An authored MCP server. Active code — gated on authoring. */\nexport interface AuthoredMcpServer {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\n/**\n * Format-agnostic authoring spec consumed by {@link PluginFormatAdapter.emit}.\n * `ProposePlugin` builds one of these and asks each requested adapter to render\n * it, so the same authored capability lands correctly in Claude Code and\n * Copilot layouts without the authoring code knowing either.\n */\nexport interface PluginDraft {\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/**\n\t * Which platform layouts to scaffold this draft into (e.g. `[\"claude\",\n\t * \"github\"]`). Consumed as the default target set by `emitForPlatforms`;\n\t * omit to render every registered format.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n\tskills?: AuthoredSkill[];\n\tcommands?: AuthoredCommand[];\n\tagents?: AuthoredAgent[];\n\thooks?: AuthoredHook[];\n\tmcpServers?: AuthoredMcpServer[];\n}\n\n/**\n * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —\n * where a standalone skill, subagent, or command lives inside a repository for\n * that vendor (e.g. Copilot reads `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, `.github/prompts/<name>.prompt.md`).\n * Unlike {@link PluginFormatAdapter.emit}, the returned paths are relative to\n * the **workspace root** (cwd), marker directory included.\n */\nexport interface WorkspaceLayout {\n\t/** Marker root the artifacts land under, e.g. \".github\", \".claude\", \".agents\". */\n\treadonly root: string;\n\temitSkill(skill: AuthoredSkill): EmittedFile;\n\temitAgent(agent: AuthoredAgent): EmittedFile;\n\temitCommand(command: AuthoredCommand): EmittedFile;\n}\n\n/** Reader + writer for a single plugin format. */\nexport interface PluginFormatAdapter {\n\t/** Internal format id (stable; used in `NormalizedPlugin.format`). */\n\treadonly id: PluginFormatId;\n\t/** Public platform token (Copilot → `\"github\"`). */\n\treadonly platform: MarketplacePlatform;\n\t/**\n\t * Precedence when several formats coexist in one directory (lower wins).\n\t * Native `.agents-plugin` beats Claude beats Copilot.\n\t */\n\treadonly precedence: number;\n\t/** Short human label for messages and scaffolding summaries. */\n\treadonly label: string;\n\t/**\n\t * Candidate locations of this format's marketplace index file, relative to a\n\t * marketplace root (POSIX), in precedence order. Formats with more than one\n\t * real-world convention (Copilot uses both `.github/plugin/marketplace.json`\n\t * and `.github/marketplace.json`) list every location they accept.\n\t */\n\treadonly marketplaceFiles: readonly string[];\n\n\t/** True if `root` carries this format's plugin manifest. */\n\tdetectPlugin(root: string): boolean;\n\t/** Parse `root` as a plugin of this format. Returns null when unparseable. */\n\tparsePlugin(root: string): NormalizedPlugin | null;\n\t/** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */\n\temit(draft: PluginDraft): EmittedFile[];\n\t/** This platform's workspace-level artifact conventions (paths relative to the workspace root). */\n\treadonly workspace: WorkspaceLayout;\n}\n"]}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Plugin format adapter contract.\n *\n * A `PluginFormatAdapter` owns *everything* format-specific for one vendor\n * layout — how a plugin directory is detected and parsed (the reader), how a\n * marketplace index file is located, and how a plugin is scaffolded from a\n * normalized draft (the writer). The rest of the plugin subsystem is written\n * against {@link NormalizedPlugin} / {@link PluginDraft} and never branches on a\n * format again.\n *\n * This is the extension point the plugin system is designed around: Claude Code\n * and GitHub Copilot conventions evolve independently, so each lives in its own\n * adapter file ({@link ./claude.ts}, {@link ./copilot.ts}, {@link ./agents.ts}).\n * Supporting a new vendor — or tracking a change in an existing one — means\n * editing (or adding) exactly one adapter and registering it; no other module\n * changes.\n */\n\nimport type { NormalizedPlugin } from \"../manifest.js\";\n\n/** Internal format id — matches `NormalizedPlugin.format` / `NormalizedMarketplace.format`. */\nexport type PluginFormatId = \"agents\" | \"claude\" | \"copilot\";\n\n/**\n * Public platform token surfaced to users. The Copilot format lives under\n * `.github/`, so it is surfaced as `\"github\"` rather than the internal\n * `\"copilot\"`.\n */\nexport type MarketplacePlatform = \"agents\" | \"claude\" | \"github\";\n\n/** A file produced by a format's scaffolder. `path` is relative to the plugin root (POSIX). */\nexport interface EmittedFile {\n\tpath: string;\n\tcontent: string;\n}\n\n/** An authored skill: passive instructions loaded lazily (SKILL.md-style). */\ninterface AuthoredSkill {\n\tname: string;\n\tdescription?: string;\n\t/** Instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored slash command: a passive prompt template. */\ninterface AuthoredCommand {\n\tname: string;\n\tdescription?: string;\n\t/** Prompt template body (markdown). */\n\tbody: string;\n}\n\n/** An authored subagent definition. `tools` is a comma-separated allowlist (Claude Code convention). */\ninterface AuthoredAgent {\n\tname: string;\n\tdescription?: string;\n\t/** Comma-separated `allowed-tools` string, e.g. \"read, grep, glob\". */\n\ttools?: string;\n\tmodel?: string;\n\t/** System-prompt / instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored shell hook wired to a tool event. Active code — gated on authoring. */\nexport interface AuthoredHook {\n\t/** Claude Code event name: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ... */\n\tevent: string;\n\t/** Regex matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\tcommand: string;\n\t/** Timeout in seconds. */\n\ttimeout?: number;\n}\n\n/** An authored MCP server. Active code — gated on authoring. */\nexport interface AuthoredMcpServer {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\n/**\n * Format-agnostic authoring spec consumed by {@link PluginFormatAdapter.emit}.\n * `ProposePlugin` builds one of these and asks each requested adapter to render\n * it, so the same authored capability lands correctly in Claude Code and\n * Copilot layouts without the authoring code knowing either.\n */\nexport interface PluginDraft {\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/**\n\t * Which platform layouts to scaffold this draft into (e.g. `[\"claude\",\n\t * \"github\"]`). Consumed as the default target set by `emitForPlatforms`;\n\t * omit to render every registered format.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n\tskills?: AuthoredSkill[];\n\tcommands?: AuthoredCommand[];\n\tagents?: AuthoredAgent[];\n\thooks?: AuthoredHook[];\n\tmcpServers?: AuthoredMcpServer[];\n\t/**\n\t * Manifest keys carried through from an existing plugin that this codebase\n\t * does not model (see {@link NormalizedPlugin.unknownFields}). Emitters write\n\t * them back verbatim so editing a plugin never strips a vendor key we happen\n\t * not to understand. Never set when authoring from scratch.\n\t */\n\tunknownFields?: Record<string, unknown>;\n}\n\n/**\n * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —\n * where a standalone skill, subagent, or command lives inside a repository for\n * that vendor (e.g. Copilot reads `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, `.github/prompts/<name>.prompt.md`).\n * Unlike {@link PluginFormatAdapter.emit}, the returned paths are relative to\n * the **workspace root** (cwd), marker directory included.\n */\nexport interface WorkspaceLayout {\n\t/** Marker root the artifacts land under, e.g. \".github\", \".claude\", \".agents\". */\n\treadonly root: string;\n\temitSkill(skill: AuthoredSkill): EmittedFile;\n\temitAgent(agent: AuthoredAgent): EmittedFile;\n\temitCommand(command: AuthoredCommand): EmittedFile;\n}\n\n/** Reader + writer for a single plugin format. */\nexport interface PluginFormatAdapter {\n\t/** Internal format id (stable; used in `NormalizedPlugin.format`). */\n\treadonly id: PluginFormatId;\n\t/** Public platform token (Copilot → `\"github\"`). */\n\treadonly platform: MarketplacePlatform;\n\t/**\n\t * Precedence when several formats coexist in one directory (lower wins).\n\t * Native `.agents-plugin` beats Claude beats Copilot.\n\t */\n\treadonly precedence: number;\n\t/** Short human label for messages and scaffolding summaries. */\n\treadonly label: string;\n\t/**\n\t * Candidate locations of this format's marketplace index file, relative to a\n\t * marketplace root (POSIX), in precedence order. Formats with more than one\n\t * real-world convention (Copilot uses both `.github/plugin/marketplace.json`\n\t * and `.github/marketplace.json`) list every location they accept.\n\t */\n\treadonly marketplaceFiles: readonly string[];\n\n\t/** True if `root` is a plugin of this format — including, where the vendor allows it, without a manifest. */\n\tdetectPlugin(root: string): boolean;\n\t/** True only if `root` carries this format's manifest *file*, ignoring manifest-optional rules. */\n\thasManifest(root: string): boolean;\n\t/** Parse `root` as a plugin of this format. Returns null when unparseable. */\n\tparsePlugin(root: string): NormalizedPlugin | null;\n\t/** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */\n\temit(draft: PluginDraft): EmittedFile[];\n\t/** This platform's workspace-level artifact conventions (paths relative to the workspace root). */\n\treadonly workspace: WorkspaceLayout;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG","sourcesContent":["/**\n * Plugin format adapter contract.\n *\n * A `PluginFormatAdapter` owns *everything* format-specific for one vendor\n * layout — how a plugin directory is detected and parsed (the reader), how a\n * marketplace index file is located, and how a plugin is scaffolded from a\n * normalized draft (the writer). The rest of the plugin subsystem is written\n * against {@link NormalizedPlugin} / {@link PluginDraft} and never branches on a\n * format again.\n *\n * This is the extension point the plugin system is designed around: Claude Code\n * and GitHub Copilot conventions evolve independently, so each lives in its own\n * adapter file ({@link ./claude.ts}, {@link ./copilot.ts}, {@link ./agents.ts}).\n * Supporting a new vendor — or tracking a change in an existing one — means\n * editing (or adding) exactly one adapter and registering it; no other module\n * changes.\n */\n\nimport type { NormalizedPlugin } from \"../manifest.js\";\n\n/** Internal format id — matches `NormalizedPlugin.format` / `NormalizedMarketplace.format`. */\nexport type PluginFormatId = \"agents\" | \"claude\" | \"copilot\";\n\n/**\n * Public platform token surfaced to users. The Copilot format lives under\n * `.github/`, so it is surfaced as `\"github\"` rather than the internal\n * `\"copilot\"`.\n */\nexport type MarketplacePlatform = \"agents\" | \"claude\" | \"github\";\n\n/** A file produced by a format's scaffolder. `path` is relative to the plugin root (POSIX). */\nexport interface EmittedFile {\n\tpath: string;\n\tcontent: string;\n}\n\n/** An authored skill: passive instructions loaded lazily (SKILL.md-style). */\ninterface AuthoredSkill {\n\tname: string;\n\tdescription?: string;\n\t/** Instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored slash command: a passive prompt template. */\ninterface AuthoredCommand {\n\tname: string;\n\tdescription?: string;\n\t/** Prompt template body (markdown). */\n\tbody: string;\n}\n\n/** An authored subagent definition. `tools` is a comma-separated allowlist (Claude Code convention). */\ninterface AuthoredAgent {\n\tname: string;\n\tdescription?: string;\n\t/** Comma-separated `allowed-tools` string, e.g. \"read, grep, glob\". */\n\ttools?: string;\n\tmodel?: string;\n\t/** System-prompt / instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored shell hook wired to a tool event. Active code — gated on authoring. */\nexport interface AuthoredHook {\n\t/** Claude Code event name: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ... */\n\tevent: string;\n\t/** Regex matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\tcommand: string;\n\t/** Timeout in seconds. */\n\ttimeout?: number;\n}\n\n/** An authored MCP server. Active code — gated on authoring. */\nexport interface AuthoredMcpServer {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\n/**\n * Format-agnostic authoring spec consumed by {@link PluginFormatAdapter.emit}.\n * `ProposePlugin` builds one of these and asks each requested adapter to render\n * it, so the same authored capability lands correctly in Claude Code and\n * Copilot layouts without the authoring code knowing either.\n */\nexport interface PluginDraft {\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/**\n\t * Which platform layouts to scaffold this draft into (e.g. `[\"claude\",\n\t * \"github\"]`). Consumed as the default target set by `emitForPlatforms`;\n\t * omit to render every registered format.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n\tskills?: AuthoredSkill[];\n\tcommands?: AuthoredCommand[];\n\tagents?: AuthoredAgent[];\n\thooks?: AuthoredHook[];\n\tmcpServers?: AuthoredMcpServer[];\n}\n\n/**\n * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —\n * where a standalone skill, subagent, or command lives inside a repository for\n * that vendor (e.g. Copilot reads `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, `.github/prompts/<name>.prompt.md`).\n * Unlike {@link PluginFormatAdapter.emit}, the returned paths are relative to\n * the **workspace root** (cwd), marker directory included.\n */\nexport interface WorkspaceLayout {\n\t/** Marker root the artifacts land under, e.g. \".github\", \".claude\", \".agents\". */\n\treadonly root: string;\n\temitSkill(skill: AuthoredSkill): EmittedFile;\n\temitAgent(agent: AuthoredAgent): EmittedFile;\n\temitCommand(command: AuthoredCommand): EmittedFile;\n}\n\n/** Reader + writer for a single plugin format. */\nexport interface PluginFormatAdapter {\n\t/** Internal format id (stable; used in `NormalizedPlugin.format`). */\n\treadonly id: PluginFormatId;\n\t/** Public platform token (Copilot → `\"github\"`). */\n\treadonly platform: MarketplacePlatform;\n\t/**\n\t * Precedence when several formats coexist in one directory (lower wins).\n\t * Native `.agents-plugin` beats Claude beats Copilot.\n\t */\n\treadonly precedence: number;\n\t/** Short human label for messages and scaffolding summaries. */\n\treadonly label: string;\n\t/**\n\t * Candidate locations of this format's marketplace index file, relative to a\n\t * marketplace root (POSIX), in precedence order. Formats with more than one\n\t * real-world convention (Copilot uses both `.github/plugin/marketplace.json`\n\t * and `.github/marketplace.json`) list every location they accept.\n\t */\n\treadonly marketplaceFiles: readonly string[];\n\n\t/** True if `root` carries this format's plugin manifest. */\n\tdetectPlugin(root: string): boolean;\n\t/** Parse `root` as a plugin of this format. Returns null when unparseable. */\n\tparsePlugin(root: string): NormalizedPlugin | null;\n\t/** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */\n\temit(draft: PluginDraft): EmittedFile[];\n\t/** This platform's workspace-level artifact conventions (paths relative to the workspace root). */\n\treadonly workspace: WorkspaceLayout;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG","sourcesContent":["/**\n * Plugin format adapter contract.\n *\n * A `PluginFormatAdapter` owns *everything* format-specific for one vendor\n * layout — how a plugin directory is detected and parsed (the reader), how a\n * marketplace index file is located, and how a plugin is scaffolded from a\n * normalized draft (the writer). The rest of the plugin subsystem is written\n * against {@link NormalizedPlugin} / {@link PluginDraft} and never branches on a\n * format again.\n *\n * This is the extension point the plugin system is designed around: Claude Code\n * and GitHub Copilot conventions evolve independently, so each lives in its own\n * adapter file ({@link ./claude.ts}, {@link ./copilot.ts}, {@link ./agents.ts}).\n * Supporting a new vendor — or tracking a change in an existing one — means\n * editing (or adding) exactly one adapter and registering it; no other module\n * changes.\n */\n\nimport type { NormalizedPlugin } from \"../manifest.js\";\n\n/** Internal format id — matches `NormalizedPlugin.format` / `NormalizedMarketplace.format`. */\nexport type PluginFormatId = \"agents\" | \"claude\" | \"copilot\";\n\n/**\n * Public platform token surfaced to users. The Copilot format lives under\n * `.github/`, so it is surfaced as `\"github\"` rather than the internal\n * `\"copilot\"`.\n */\nexport type MarketplacePlatform = \"agents\" | \"claude\" | \"github\";\n\n/** A file produced by a format's scaffolder. `path` is relative to the plugin root (POSIX). */\nexport interface EmittedFile {\n\tpath: string;\n\tcontent: string;\n}\n\n/** An authored skill: passive instructions loaded lazily (SKILL.md-style). */\ninterface AuthoredSkill {\n\tname: string;\n\tdescription?: string;\n\t/** Instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored slash command: a passive prompt template. */\ninterface AuthoredCommand {\n\tname: string;\n\tdescription?: string;\n\t/** Prompt template body (markdown). */\n\tbody: string;\n}\n\n/** An authored subagent definition. `tools` is a comma-separated allowlist (Claude Code convention). */\ninterface AuthoredAgent {\n\tname: string;\n\tdescription?: string;\n\t/** Comma-separated `allowed-tools` string, e.g. \"read, grep, glob\". */\n\ttools?: string;\n\tmodel?: string;\n\t/** System-prompt / instruction body (markdown). */\n\tbody: string;\n}\n\n/** An authored shell hook wired to a tool event. Active code — gated on authoring. */\nexport interface AuthoredHook {\n\t/** Claude Code event name: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, ... */\n\tevent: string;\n\t/** Regex matched against the tool name. Empty / \"*\" matches everything. */\n\tmatcher?: string;\n\tcommand: string;\n\t/** Timeout in seconds. */\n\ttimeout?: number;\n}\n\n/** An authored MCP server. Active code — gated on authoring. */\nexport interface AuthoredMcpServer {\n\tname: string;\n\tcommand: string;\n\targs?: string[];\n\tenv?: Record<string, string>;\n}\n\n/**\n * Format-agnostic authoring spec consumed by {@link PluginFormatAdapter.emit}.\n * `ProposePlugin` builds one of these and asks each requested adapter to render\n * it, so the same authored capability lands correctly in Claude Code and\n * Copilot layouts without the authoring code knowing either.\n */\nexport interface PluginDraft {\n\tid: string;\n\tversion?: string;\n\tdescription?: string;\n\tauthor?: string;\n\t/**\n\t * Which platform layouts to scaffold this draft into (e.g. `[\"claude\",\n\t * \"github\"]`). Consumed as the default target set by `emitForPlatforms`;\n\t * omit to render every registered format.\n\t */\n\tsupportPlatform?: MarketplacePlatform[];\n\tskills?: AuthoredSkill[];\n\tcommands?: AuthoredCommand[];\n\tagents?: AuthoredAgent[];\n\thooks?: AuthoredHook[];\n\tmcpServers?: AuthoredMcpServer[];\n\t/**\n\t * Manifest keys carried through from an existing plugin that this codebase\n\t * does not model (see {@link NormalizedPlugin.unknownFields}). Emitters write\n\t * them back verbatim so editing a plugin never strips a vendor key we happen\n\t * not to understand. Never set when authoring from scratch.\n\t */\n\tunknownFields?: Record<string, unknown>;\n}\n\n/**\n * Writer for a platform's *workspace-level* (non-plugin) artifact conventions —\n * where a standalone skill, subagent, or command lives inside a repository for\n * that vendor (e.g. Copilot reads `.github/skills/<name>/SKILL.md`,\n * `.github/agents/<name>.agent.md`, `.github/prompts/<name>.prompt.md`).\n * Unlike {@link PluginFormatAdapter.emit}, the returned paths are relative to\n * the **workspace root** (cwd), marker directory included.\n */\nexport interface WorkspaceLayout {\n\t/** Marker root the artifacts land under, e.g. \".github\", \".claude\", \".agents\". */\n\treadonly root: string;\n\temitSkill(skill: AuthoredSkill): EmittedFile;\n\temitAgent(agent: AuthoredAgent): EmittedFile;\n\temitCommand(command: AuthoredCommand): EmittedFile;\n}\n\n/** Reader + writer for a single plugin format. */\nexport interface PluginFormatAdapter {\n\t/** Internal format id (stable; used in `NormalizedPlugin.format`). */\n\treadonly id: PluginFormatId;\n\t/** Public platform token (Copilot → `\"github\"`). */\n\treadonly platform: MarketplacePlatform;\n\t/**\n\t * Precedence when several formats coexist in one directory (lower wins).\n\t * Native `.agents-plugin` beats Claude beats Copilot.\n\t */\n\treadonly precedence: number;\n\t/** Short human label for messages and scaffolding summaries. */\n\treadonly label: string;\n\t/**\n\t * Candidate locations of this format's marketplace index file, relative to a\n\t * marketplace root (POSIX), in precedence order. Formats with more than one\n\t * real-world convention (Copilot uses both `.github/plugin/marketplace.json`\n\t * and `.github/marketplace.json`) list every location they accept.\n\t */\n\treadonly marketplaceFiles: readonly string[];\n\n\t/** True if `root` is a plugin of this format — including, where the vendor allows it, without a manifest. */\n\tdetectPlugin(root: string): boolean;\n\t/** True only if `root` carries this format's manifest *file*, ignoring manifest-optional rules. */\n\thasManifest(root: string): boolean;\n\t/** Parse `root` as a plugin of this format. Returns null when unparseable. */\n\tparsePlugin(root: string): NormalizedPlugin | null;\n\t/** Render `draft` into this format's on-disk layout (paths relative to the plugin root). */\n\temit(draft: PluginDraft): EmittedFile[];\n\t/** This platform's workspace-level artifact conventions (paths relative to the workspace root). */\n\treadonly workspace: WorkspaceLayout;\n}\n"]}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Plugin eval gates G1 and G2 — the green signal an authored or installed plugin
3
+ * must produce before anything activates it.
4
+ *
5
+ * Nothing used to run between "the model writes a plugin" and "the plugin is
6
+ * live". These gates fill that gap, and they run against the **draft dir**, not
7
+ * a production home: a failure discards the draft, so a plugin that does not
8
+ * pass never reaches `~/.claude/skills/` where Claude Code would load it.
9
+ *
10
+ * G1 structural + conformant — does it parse, is the id legal, is the content
11
+ * portable, and does the *target platform* accept it
12
+ * G2 static safety — can the executable content plausibly run, and is it
13
+ * obviously dangerous
14
+ *
15
+ * G1 measures conformance against the vendor where it can. Round-tripping
16
+ * through our own parser only proves the emitter and the reader agree with each
17
+ * other; it says nothing about whether Claude Code will accept the artifact. So
18
+ * when the `claude` CLI is present, its own `plugin validate` is the authority —
19
+ * it is the exact check the vendor's review pipeline runs, and inventing a
20
+ * second opinion where the vendor ships one would just be a different set of
21
+ * bugs. Copilot has no equivalent, so `github` targets get round-trip + schema.
22
+ *
23
+ * G3 (sandboxed behavioral smoke) and G4 (trigger eval) are separate; see
24
+ * docs/plugin-system-architecture.md §4.
25
+ */
26
+ import type { PluginPlatform } from "./formats/platform-targets.js";
27
+ import { type NormalizedPlugin } from "./manifest.js";
28
+ export interface GateFinding {
29
+ gate: "G1" | "G2" | "G3" | "G4";
30
+ /**
31
+ * `error` fails the gate. `warning` is reported and passes, unless `strict`.
32
+ * `info` always passes — it exists so a *successful* check can be shown to the
33
+ * human in the confirmation prompt without `strict` treating "the hook ran
34
+ * fine" as a reason to refuse.
35
+ */
36
+ severity: "error" | "warning" | "info";
37
+ message: string;
38
+ }
39
+ export interface GateResult {
40
+ ok: boolean;
41
+ findings: GateFinding[];
42
+ /** How conformance was established, for the record and for the confirm prompt. */
43
+ conformance: "claude-plugin-validate" | "round-trip";
44
+ plugin: NormalizedPlugin | null;
45
+ }
46
+ export interface GateOptions {
47
+ /** Target platform, which decides whether a vendor validator applies. */
48
+ platform?: PluginPlatform;
49
+ /** Treat warnings as errors. Used on the publish path. */
50
+ strict?: boolean;
51
+ /** Skip the vendor validator (tests, and callers that must stay hermetic). */
52
+ skipVendorValidator?: boolean;
53
+ }
54
+ /** First executable token of a shell command, ignoring `VAR=x` prefixes. */
55
+ export declare function commandBinary(command: string): string | undefined;
56
+ /** Whether a set of findings fails the gate. `info` never does. */
57
+ export declare function findingsFail(findings: readonly GateFinding[], strict?: boolean): boolean;
58
+ /** Run G1 and G2 over a plugin directory. */
59
+ export declare function runStaticGates(dir: string, opts?: GateOptions): GateResult;
60
+ /** Fold additional findings (G3) into an existing result. */
61
+ export declare function withFindings(result: GateResult, extra: readonly GateFinding[], strict?: boolean): GateResult;
62
+ /** Render findings for a tool result or a confirmation prompt. */
63
+ export declare function formatGateFindings(result: GateResult): string;
64
+ //# sourceMappingURL=gates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/gates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,KAAK,gBAAgB,EAAkB,MAAM,eAAe,CAAC;AAEtE,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,kFAAkF;IAClF,WAAW,EAAE,wBAAwB,GAAG,YAAY,CAAC;IACrD,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAyED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AA2KD,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,UAAQ,GAAG,OAAO,CAEtF;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,UAAU,CAI9E;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,UAAQ,GAAG,UAAU,CAG1G;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAM7D","sourcesContent":["/**\n * Plugin eval gates G1 and G2 — the green signal an authored or installed plugin\n * must produce before anything activates it.\n *\n * Nothing used to run between \"the model writes a plugin\" and \"the plugin is\n * live\". These gates fill that gap, and they run against the **draft dir**, not\n * a production home: a failure discards the draft, so a plugin that does not\n * pass never reaches `~/.claude/skills/` where Claude Code would load it.\n *\n * G1 structural + conformant — does it parse, is the id legal, is the content\n * portable, and does the *target platform* accept it\n * G2 static safety — can the executable content plausibly run, and is it\n * obviously dangerous\n *\n * G1 measures conformance against the vendor where it can. Round-tripping\n * through our own parser only proves the emitter and the reader agree with each\n * other; it says nothing about whether Claude Code will accept the artifact. So\n * when the `claude` CLI is present, its own `plugin validate` is the authority —\n * it is the exact check the vendor's review pipeline runs, and inventing a\n * second opinion where the vendor ships one would just be a different set of\n * bugs. Copilot has no equivalent, so `github` targets get round-trip + schema.\n *\n * G3 (sandboxed behavioral smoke) and G4 (trigger eval) are separate; see\n * docs/plugin-system-architecture.md §4.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { classifyAllowlist } from \"./authoring.js\";\nimport type { PluginPlatform } from \"./formats/platform-targets.js\";\nimport { type NormalizedPlugin, parsePluginDir } from \"./manifest.js\";\n\nexport interface GateFinding {\n\tgate: \"G1\" | \"G2\" | \"G3\" | \"G4\";\n\t/**\n\t * `error` fails the gate. `warning` is reported and passes, unless `strict`.\n\t * `info` always passes — it exists so a *successful* check can be shown to the\n\t * human in the confirmation prompt without `strict` treating \"the hook ran\n\t * fine\" as a reason to refuse.\n\t */\n\tseverity: \"error\" | \"warning\" | \"info\";\n\tmessage: string;\n}\n\nexport interface GateResult {\n\tok: boolean;\n\tfindings: GateFinding[];\n\t/** How conformance was established, for the record and for the confirm prompt. */\n\tconformance: \"claude-plugin-validate\" | \"round-trip\";\n\tplugin: NormalizedPlugin | null;\n}\n\nexport interface GateOptions {\n\t/** Target platform, which decides whether a vendor validator applies. */\n\tplatform?: PluginPlatform;\n\t/** Treat warnings as errors. Used on the publish path. */\n\tstrict?: boolean;\n\t/** Skip the vendor validator (tests, and callers that must stay hermetic). */\n\tskipVendorValidator?: boolean;\n}\n\n/**\n * Opt out of the vendor validator process-wide.\n *\n * It costs a subprocess of a few hundred milliseconds per plugin — fine once per\n * authored plugin, ruinous across a test suite that authors dozens. Also the\n * escape hatch for a CI image that has the `claude` binary but should not spend\n * the time, or a sandbox where spawning it is undesirable. The gate falls back to\n * round-trip, and says so in `conformance` rather than pretending it validated.\n */\nconst ENV_SKIP_VENDOR_VALIDATE = \"HOOCODE_PLUGIN_SKIP_VENDOR_VALIDATE\";\n\nfunction vendorValidatorDisabled(): boolean {\n\tconst v = process.env[ENV_SKIP_VENDOR_VALIDATE];\n\treturn v === \"1\" || v === \"true\";\n}\n\nconst MAX_ID_LENGTH = 64;\nconst VALID_ID = /^[a-z0-9][a-z0-9-]*$/;\n\n/** Files worth scanning for portability and secret problems. Binary content is skipped. */\nconst TEXT_EXTENSIONS = new Set([\".md\", \".json\", \".txt\", \".yaml\", \".yml\", \".sh\", \".js\", \".ts\", \".toml\"]);\n\n/**\n * Absolute paths that only exist on the author's machine. A plugin carrying one\n * works exactly once — for whoever wrote it — which is the failure mode\n * portability guidance was meant to prevent and could not, being only advice.\n */\nconst MACHINE_PATHS = [/\\/home\\/[a-z0-9._-]+\\//i, /\\/Users\\/[a-z0-9._-]+\\//i, /\\b[A-Z]:\\\\Users\\\\/];\n\n/** Credential shapes worth refusing outright rather than shipping to a marketplace. */\nconst SECRET_PATTERNS: ReadonlyArray<{ re: RegExp; what: string }> = [\n\t{ re: /\\bsk-[A-Za-z0-9]{16,}/, what: \"an API key (sk-…)\" },\n\t{ re: /\\bghp_[A-Za-z0-9]{20,}/, what: \"a GitHub token (ghp_…)\" },\n\t{ re: /\\bAKIA[0-9A-Z]{12,}/, what: \"an AWS access key id\" },\n\t{ re: /\\bAWS_SECRET_ACCESS_KEY\\s*[=:]\\s*\\S+/, what: \"an AWS secret\" },\n\t{ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/, what: \"a private key\" },\n];\n\n/**\n * Shell constructs refused in a hook or MCP command. Not a sandbox and not a\n * general shell analysis — a determined command can evade any of these. It\n * catches the accident and the obvious, which is what a static gate can honestly\n * claim; G3 is where behavior gets checked.\n */\nconst DANGEROUS_COMMANDS: ReadonlyArray<{ re: RegExp; what: string }> = [\n\t{ re: /\\brm\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*[rR][a-zA-Z]*f|\\brm\\s+-fr\\b/, what: \"a recursive force delete\" },\n\t{ re: /\\b(curl|wget)\\b[^|]*\\|\\s*(ba)?sh\\b/, what: \"piping a download into a shell\" },\n\t{ re: /\\bmkfs(\\.[a-z0-9]+)?\\b|\\bdd\\s+[^|]*of=\\/dev\\//, what: \"a raw device write\" },\n\t{ re: />\\s*\\/dev\\/(sd|nvme|disk)/, what: \"a raw device write\" },\n\t{ re: /\\bchmod\\s+(-[a-zA-Z]+\\s+)*777\\b/, what: \"a world-writable chmod\" },\n\t{ re: /\\bsudo\\b/, what: \"a privilege escalation\" },\n];\n\nfunction walkFiles(root: string, out: string[] = []): string[] {\n\tlet entries: import(\"node:fs\").Dirent[];\n\ttry {\n\t\tentries = readdirSync(root, { withFileTypes: true });\n\t} catch {\n\t\treturn out;\n\t}\n\tfor (const entry of entries) {\n\t\tconst full = path.join(root, entry.name);\n\t\tif (entry.isDirectory()) {\n\t\t\twalkFiles(full, out);\n\t\t} else if (entry.isFile() && TEXT_EXTENSIONS.has(path.extname(entry.name))) {\n\t\t\tout.push(full);\n\t\t}\n\t}\n\treturn out;\n}\n\n/** First executable token of a shell command, ignoring `VAR=x` prefixes. */\nexport function commandBinary(command: string): string | undefined {\n\tfor (const token of command.trim().split(/\\s+/)) {\n\t\tif (!token || /^[A-Za-z_][A-Za-z0-9_]*=/.test(token)) continue;\n\t\treturn token;\n\t}\n\treturn undefined;\n}\n\n/** Whether `binary` resolves — on PATH, or as a path relative to the plugin. */\nfunction binaryResolves(binary: string, root: string): boolean {\n\t// A template variable is resolved at run time, so it cannot be checked here.\n\tif (binary.includes(\"${\")) return true;\n\tif (binary.includes(\"/\") || binary.includes(\"\\\\\")) {\n\t\treturn existsSync(path.isAbsolute(binary) ? binary : path.resolve(root, binary));\n\t}\n\t// Shell builtins never appear on PATH.\n\tif ([\"echo\", \"cd\", \"true\", \"false\", \"test\", \"set\", \"export\", \":\"].includes(binary)) return true;\n\ttry {\n\t\texecFileSync(process.platform === \"win32\" ? \"where\" : \"which\", [binary], {\n\t\t\tstdio: \"ignore\",\n\t\t\ttimeout: 5_000,\n\t\t});\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** G1: parses, legal id, portable content, and accepted by the target platform. */\nfunction structural(\n\tdir: string,\n\topts: GateOptions,\n): { findings: GateFinding[]; conformance: GateResult[\"conformance\"]; plugin: NormalizedPlugin | null } {\n\tconst findings: GateFinding[] = [];\n\tconst err = (message: string) => findings.push({ gate: \"G1\", severity: \"error\", message });\n\tconst warn = (message: string) => findings.push({ gate: \"G1\", severity: \"warning\", message });\n\n\tconst plugin = parsePluginDir(dir);\n\tif (!plugin) {\n\t\terr(`No recognizable plugin manifest at ${dir}.`);\n\t\treturn { findings, conformance: \"round-trip\", plugin: null };\n\t}\n\n\tif (plugin.id.length > MAX_ID_LENGTH) err(`Plugin id \"${plugin.id}\" exceeds ${MAX_ID_LENGTH} characters.`);\n\tif (!VALID_ID.test(plugin.id)) {\n\t\terr(`Plugin id \"${plugin.id}\" must be lowercase letters, digits and hyphens, and start with one.`);\n\t}\n\n\tfor (const file of walkFiles(dir)) {\n\t\tlet content: string;\n\t\ttry {\n\t\t\tif (statSync(file).size > 512 * 1024) continue;\n\t\t\tcontent = readFileSync(file, \"utf8\");\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tconst rel = path.relative(dir, file);\n\t\tfor (const re of MACHINE_PATHS) {\n\t\t\tconst hit = re.exec(content);\n\t\t\tif (hit) {\n\t\t\t\terr(`${rel} contains a machine-specific path (\"${hit[0]}\"); the plugin would only work for its author.`);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfor (const { re, what } of SECRET_PATTERNS) {\n\t\t\tif (re.test(content)) {\n\t\t\t\terr(`${rel} appears to contain ${what}.`);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Vendor conformance where one exists. Round-trip alone proves only that our\n\t// emitter and our reader agree.\n\tlet conformance: GateResult[\"conformance\"] = \"round-trip\";\n\tif (opts.platform === \"claude\" && !opts.skipVendorValidator && !vendorValidatorDisabled()) {\n\t\tconst vendor = runClaudeValidate(dir, opts.strict === true);\n\t\tif (vendor) {\n\t\t\tconformance = \"claude-plugin-validate\";\n\t\t\tif (!vendor.ok) err(`claude plugin validate rejected the plugin:\\n${vendor.output}`);\n\t\t\telse if (vendor.output.trim()) warn(`claude plugin validate: ${vendor.output.trim()}`);\n\t\t}\n\t}\n\n\treturn { findings, conformance, plugin };\n}\n\n/** Run the vendor validator, or undefined when the CLI is not installed. */\nfunction runClaudeValidate(dir: string, strict: boolean): { ok: boolean; output: string } | undefined {\n\tconst args = [\"plugin\", \"validate\", dir, ...(strict ? [\"--strict\"] : [])];\n\ttry {\n\t\tconst output = execFileSync(\"claude\", args, { encoding: \"utf8\", timeout: 60_000, stdio: \"pipe\" });\n\t\treturn { ok: true, output };\n\t} catch (error) {\n\t\tconst e = error as { code?: string; status?: number; stdout?: string; stderr?: string };\n\t\t// No CLI on this machine, or it predates `plugin validate` — not a failure\n\t\t// of the plugin, so the gate falls back to round-trip rather than blocking.\n\t\tif (e.code === \"ENOENT\") return undefined;\n\t\tconst output = `${e.stdout ?? \"\"}${e.stderr ?? \"\"}`;\n\t\tif (/unknown command|unrecognized|not a valid/i.test(output)) return undefined;\n\t\treturn { ok: false, output };\n\t}\n}\n\n/** G2: the executable content can plausibly run and is not obviously dangerous. */\nfunction safety(plugin: NormalizedPlugin | null): GateFinding[] {\n\tconst findings: GateFinding[] = [];\n\tif (!plugin) return findings;\n\tconst err = (message: string) => findings.push({ gate: \"G2\", severity: \"error\", message });\n\tconst warn = (message: string) => findings.push({ gate: \"G2\", severity: \"warning\", message });\n\n\tconst checkCommand = (label: string, command: string) => {\n\t\tfor (const { re, what } of DANGEROUS_COMMANDS) {\n\t\t\tif (re.test(command)) err(`${label} performs ${what}: ${command}`);\n\t\t}\n\t\tconst binary = commandBinary(command);\n\t\tif (!binary) {\n\t\t\terr(`${label} has no runnable command.`);\n\t\t\treturn;\n\t\t}\n\t\tif (!binaryResolves(binary, plugin.root)) {\n\t\t\t// A warning, not an error: the binary may be installed later, or by the\n\t\t\t// plugin's own prerequisites. Refusing outright would block a plugin that\n\t\t\t// documents its dependency honestly.\n\t\t\twarn(`${label} runs \"${binary}\", which is not on PATH or in the plugin.`);\n\t\t}\n\t};\n\n\tfor (const [event, groups] of Object.entries(plugin.hooks ?? {})) {\n\t\tfor (const group of groups) {\n\t\t\tfor (const cmd of group.hooks) checkCommand(`hook ${event}`, cmd.command);\n\t\t}\n\t}\n\tfor (const [name, raw] of Object.entries(plugin.mcpServers ?? {})) {\n\t\tconst cfg = raw as { command?: unknown };\n\t\tif (typeof cfg.command === \"string\") checkCommand(`mcp server \"${name}\"`, cfg.command);\n\t}\n\n\t// Subagent grants are classified rather than judged: the confirm gate already\n\t// decides on them, and recording the classification is what lets the human see\n\t// *why* they were asked.\n\tfor (const agentFile of listAgentFiles(plugin)) {\n\t\tconst tools = readToolsFrontmatter(agentFile);\n\t\tif (tools === undefined) continue;\n\t\tconst cls = classifyAllowlist(tools);\n\t\tif (cls.pluginTools.length > 0) {\n\t\t\terr(\n\t\t\t\t`subagent \"${path.basename(agentFile)}\" grants capability-acquisition tools: ${cls.pluginTools.join(\", \")}`,\n\t\t\t);\n\t\t} else if (cls.risk === \"mutating\") {\n\t\t\twarn(`subagent \"${path.basename(agentFile)}\" holds a mutating grant (${cls.reason}).`);\n\t\t}\n\t}\n\n\treturn findings;\n}\n\nfunction listAgentFiles(plugin: NormalizedPlugin): string[] {\n\tif (!plugin.agentsDir || !existsSync(plugin.agentsDir)) return [];\n\ttry {\n\t\treturn readdirSync(plugin.agentsDir)\n\t\t\t.filter((f) => f.endsWith(\".md\"))\n\t\t\t.map((f) => path.join(plugin.agentsDir as string, f));\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction readToolsFrontmatter(file: string): string | undefined {\n\ttry {\n\t\tconst match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(readFileSync(file, \"utf8\"));\n\t\treturn /^tools:\\s*(.+)$/m.exec(match?.[1] ?? \"\")?.[1]?.trim();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Whether a set of findings fails the gate. `info` never does. */\nexport function findingsFail(findings: readonly GateFinding[], strict = false): boolean {\n\treturn findings.some((f) => f.severity === \"error\" || (strict && f.severity === \"warning\"));\n}\n\n/** Run G1 and G2 over a plugin directory. */\nexport function runStaticGates(dir: string, opts: GateOptions = {}): GateResult {\n\tconst { findings: g1, conformance, plugin } = structural(dir, opts);\n\tconst findings = [...g1, ...safety(plugin)];\n\treturn { ok: !findingsFail(findings, opts.strict === true), findings, conformance, plugin };\n}\n\n/** Fold additional findings (G3) into an existing result. */\nexport function withFindings(result: GateResult, extra: readonly GateFinding[], strict = false): GateResult {\n\tconst findings = [...result.findings, ...extra];\n\treturn { ...result, findings, ok: !findingsFail(findings, strict) };\n}\n\n/** Render findings for a tool result or a confirmation prompt. */\nexport function formatGateFindings(result: GateResult): string {\n\tif (result.findings.length === 0) {\n\t\treturn `Checks passed (${result.conformance}).`;\n\t}\n\tconst lines = result.findings.map((f) => ` [${f.gate}/${f.severity}] ${f.message}`);\n\treturn `${result.ok ? \"Checks passed with warnings\" : \"Checks failed\"} (${result.conformance}):\\n${lines.join(\"\\n\")}`;\n}\n"]}
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Plugin eval gates G1 and G2 — the green signal an authored or installed plugin
3
+ * must produce before anything activates it.
4
+ *
5
+ * Nothing used to run between "the model writes a plugin" and "the plugin is
6
+ * live". These gates fill that gap, and they run against the **draft dir**, not
7
+ * a production home: a failure discards the draft, so a plugin that does not
8
+ * pass never reaches `~/.claude/skills/` where Claude Code would load it.
9
+ *
10
+ * G1 structural + conformant — does it parse, is the id legal, is the content
11
+ * portable, and does the *target platform* accept it
12
+ * G2 static safety — can the executable content plausibly run, and is it
13
+ * obviously dangerous
14
+ *
15
+ * G1 measures conformance against the vendor where it can. Round-tripping
16
+ * through our own parser only proves the emitter and the reader agree with each
17
+ * other; it says nothing about whether Claude Code will accept the artifact. So
18
+ * when the `claude` CLI is present, its own `plugin validate` is the authority —
19
+ * it is the exact check the vendor's review pipeline runs, and inventing a
20
+ * second opinion where the vendor ships one would just be a different set of
21
+ * bugs. Copilot has no equivalent, so `github` targets get round-trip + schema.
22
+ *
23
+ * G3 (sandboxed behavioral smoke) and G4 (trigger eval) are separate; see
24
+ * docs/plugin-system-architecture.md §4.
25
+ */
26
+ import { execFileSync } from "node:child_process";
27
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
28
+ import * as path from "node:path";
29
+ import { classifyAllowlist } from "./authoring.js";
30
+ import { parsePluginDir } from "./manifest.js";
31
+ /**
32
+ * Opt out of the vendor validator process-wide.
33
+ *
34
+ * It costs a subprocess of a few hundred milliseconds per plugin — fine once per
35
+ * authored plugin, ruinous across a test suite that authors dozens. Also the
36
+ * escape hatch for a CI image that has the `claude` binary but should not spend
37
+ * the time, or a sandbox where spawning it is undesirable. The gate falls back to
38
+ * round-trip, and says so in `conformance` rather than pretending it validated.
39
+ */
40
+ const ENV_SKIP_VENDOR_VALIDATE = "HOOCODE_PLUGIN_SKIP_VENDOR_VALIDATE";
41
+ function vendorValidatorDisabled() {
42
+ const v = process.env[ENV_SKIP_VENDOR_VALIDATE];
43
+ return v === "1" || v === "true";
44
+ }
45
+ const MAX_ID_LENGTH = 64;
46
+ const VALID_ID = /^[a-z0-9][a-z0-9-]*$/;
47
+ /** Files worth scanning for portability and secret problems. Binary content is skipped. */
48
+ const TEXT_EXTENSIONS = new Set([".md", ".json", ".txt", ".yaml", ".yml", ".sh", ".js", ".ts", ".toml"]);
49
+ /**
50
+ * Absolute paths that only exist on the author's machine. A plugin carrying one
51
+ * works exactly once — for whoever wrote it — which is the failure mode
52
+ * portability guidance was meant to prevent and could not, being only advice.
53
+ */
54
+ const MACHINE_PATHS = [/\/home\/[a-z0-9._-]+\//i, /\/Users\/[a-z0-9._-]+\//i, /\b[A-Z]:\\Users\\/];
55
+ /** Credential shapes worth refusing outright rather than shipping to a marketplace. */
56
+ const SECRET_PATTERNS = [
57
+ { re: /\bsk-[A-Za-z0-9]{16,}/, what: "an API key (sk-…)" },
58
+ { re: /\bghp_[A-Za-z0-9]{20,}/, what: "a GitHub token (ghp_…)" },
59
+ { re: /\bAKIA[0-9A-Z]{12,}/, what: "an AWS access key id" },
60
+ { re: /\bAWS_SECRET_ACCESS_KEY\s*[=:]\s*\S+/, what: "an AWS secret" },
61
+ { re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/, what: "a private key" },
62
+ ];
63
+ /**
64
+ * Shell constructs refused in a hook or MCP command. Not a sandbox and not a
65
+ * general shell analysis — a determined command can evade any of these. It
66
+ * catches the accident and the obvious, which is what a static gate can honestly
67
+ * claim; G3 is where behavior gets checked.
68
+ */
69
+ const DANGEROUS_COMMANDS = [
70
+ { re: /\brm\s+(-[a-zA-Z]*\s+)*-[a-zA-Z]*[rR][a-zA-Z]*f|\brm\s+-fr\b/, what: "a recursive force delete" },
71
+ { re: /\b(curl|wget)\b[^|]*\|\s*(ba)?sh\b/, what: "piping a download into a shell" },
72
+ { re: /\bmkfs(\.[a-z0-9]+)?\b|\bdd\s+[^|]*of=\/dev\//, what: "a raw device write" },
73
+ { re: />\s*\/dev\/(sd|nvme|disk)/, what: "a raw device write" },
74
+ { re: /\bchmod\s+(-[a-zA-Z]+\s+)*777\b/, what: "a world-writable chmod" },
75
+ { re: /\bsudo\b/, what: "a privilege escalation" },
76
+ ];
77
+ function walkFiles(root, out = []) {
78
+ let entries;
79
+ try {
80
+ entries = readdirSync(root, { withFileTypes: true });
81
+ }
82
+ catch {
83
+ return out;
84
+ }
85
+ for (const entry of entries) {
86
+ const full = path.join(root, entry.name);
87
+ if (entry.isDirectory()) {
88
+ walkFiles(full, out);
89
+ }
90
+ else if (entry.isFile() && TEXT_EXTENSIONS.has(path.extname(entry.name))) {
91
+ out.push(full);
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+ /** First executable token of a shell command, ignoring `VAR=x` prefixes. */
97
+ export function commandBinary(command) {
98
+ for (const token of command.trim().split(/\s+/)) {
99
+ if (!token || /^[A-Za-z_][A-Za-z0-9_]*=/.test(token))
100
+ continue;
101
+ return token;
102
+ }
103
+ return undefined;
104
+ }
105
+ /** Whether `binary` resolves — on PATH, or as a path relative to the plugin. */
106
+ function binaryResolves(binary, root) {
107
+ // A template variable is resolved at run time, so it cannot be checked here.
108
+ if (binary.includes("${"))
109
+ return true;
110
+ if (binary.includes("/") || binary.includes("\\")) {
111
+ return existsSync(path.isAbsolute(binary) ? binary : path.resolve(root, binary));
112
+ }
113
+ // Shell builtins never appear on PATH.
114
+ if (["echo", "cd", "true", "false", "test", "set", "export", ":"].includes(binary))
115
+ return true;
116
+ try {
117
+ execFileSync(process.platform === "win32" ? "where" : "which", [binary], {
118
+ stdio: "ignore",
119
+ timeout: 5_000,
120
+ });
121
+ return true;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
127
+ /** G1: parses, legal id, portable content, and accepted by the target platform. */
128
+ function structural(dir, opts) {
129
+ const findings = [];
130
+ const err = (message) => findings.push({ gate: "G1", severity: "error", message });
131
+ const warn = (message) => findings.push({ gate: "G1", severity: "warning", message });
132
+ const plugin = parsePluginDir(dir);
133
+ if (!plugin) {
134
+ err(`No recognizable plugin manifest at ${dir}.`);
135
+ return { findings, conformance: "round-trip", plugin: null };
136
+ }
137
+ if (plugin.id.length > MAX_ID_LENGTH)
138
+ err(`Plugin id "${plugin.id}" exceeds ${MAX_ID_LENGTH} characters.`);
139
+ if (!VALID_ID.test(plugin.id)) {
140
+ err(`Plugin id "${plugin.id}" must be lowercase letters, digits and hyphens, and start with one.`);
141
+ }
142
+ for (const file of walkFiles(dir)) {
143
+ let content;
144
+ try {
145
+ if (statSync(file).size > 512 * 1024)
146
+ continue;
147
+ content = readFileSync(file, "utf8");
148
+ }
149
+ catch {
150
+ continue;
151
+ }
152
+ const rel = path.relative(dir, file);
153
+ for (const re of MACHINE_PATHS) {
154
+ const hit = re.exec(content);
155
+ if (hit) {
156
+ err(`${rel} contains a machine-specific path ("${hit[0]}"); the plugin would only work for its author.`);
157
+ break;
158
+ }
159
+ }
160
+ for (const { re, what } of SECRET_PATTERNS) {
161
+ if (re.test(content)) {
162
+ err(`${rel} appears to contain ${what}.`);
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ // Vendor conformance where one exists. Round-trip alone proves only that our
168
+ // emitter and our reader agree.
169
+ let conformance = "round-trip";
170
+ if (opts.platform === "claude" && !opts.skipVendorValidator && !vendorValidatorDisabled()) {
171
+ const vendor = runClaudeValidate(dir, opts.strict === true);
172
+ if (vendor) {
173
+ conformance = "claude-plugin-validate";
174
+ if (!vendor.ok)
175
+ err(`claude plugin validate rejected the plugin:\n${vendor.output}`);
176
+ else if (vendor.output.trim())
177
+ warn(`claude plugin validate: ${vendor.output.trim()}`);
178
+ }
179
+ }
180
+ return { findings, conformance, plugin };
181
+ }
182
+ /** Run the vendor validator, or undefined when the CLI is not installed. */
183
+ function runClaudeValidate(dir, strict) {
184
+ const args = ["plugin", "validate", dir, ...(strict ? ["--strict"] : [])];
185
+ try {
186
+ const output = execFileSync("claude", args, { encoding: "utf8", timeout: 60_000, stdio: "pipe" });
187
+ return { ok: true, output };
188
+ }
189
+ catch (error) {
190
+ const e = error;
191
+ // No CLI on this machine, or it predates `plugin validate` — not a failure
192
+ // of the plugin, so the gate falls back to round-trip rather than blocking.
193
+ if (e.code === "ENOENT")
194
+ return undefined;
195
+ const output = `${e.stdout ?? ""}${e.stderr ?? ""}`;
196
+ if (/unknown command|unrecognized|not a valid/i.test(output))
197
+ return undefined;
198
+ return { ok: false, output };
199
+ }
200
+ }
201
+ /** G2: the executable content can plausibly run and is not obviously dangerous. */
202
+ function safety(plugin) {
203
+ const findings = [];
204
+ if (!plugin)
205
+ return findings;
206
+ const err = (message) => findings.push({ gate: "G2", severity: "error", message });
207
+ const warn = (message) => findings.push({ gate: "G2", severity: "warning", message });
208
+ const checkCommand = (label, command) => {
209
+ for (const { re, what } of DANGEROUS_COMMANDS) {
210
+ if (re.test(command))
211
+ err(`${label} performs ${what}: ${command}`);
212
+ }
213
+ const binary = commandBinary(command);
214
+ if (!binary) {
215
+ err(`${label} has no runnable command.`);
216
+ return;
217
+ }
218
+ if (!binaryResolves(binary, plugin.root)) {
219
+ // A warning, not an error: the binary may be installed later, or by the
220
+ // plugin's own prerequisites. Refusing outright would block a plugin that
221
+ // documents its dependency honestly.
222
+ warn(`${label} runs "${binary}", which is not on PATH or in the plugin.`);
223
+ }
224
+ };
225
+ for (const [event, groups] of Object.entries(plugin.hooks ?? {})) {
226
+ for (const group of groups) {
227
+ for (const cmd of group.hooks)
228
+ checkCommand(`hook ${event}`, cmd.command);
229
+ }
230
+ }
231
+ for (const [name, raw] of Object.entries(plugin.mcpServers ?? {})) {
232
+ const cfg = raw;
233
+ if (typeof cfg.command === "string")
234
+ checkCommand(`mcp server "${name}"`, cfg.command);
235
+ }
236
+ // Subagent grants are classified rather than judged: the confirm gate already
237
+ // decides on them, and recording the classification is what lets the human see
238
+ // *why* they were asked.
239
+ for (const agentFile of listAgentFiles(plugin)) {
240
+ const tools = readToolsFrontmatter(agentFile);
241
+ if (tools === undefined)
242
+ continue;
243
+ const cls = classifyAllowlist(tools);
244
+ if (cls.pluginTools.length > 0) {
245
+ err(`subagent "${path.basename(agentFile)}" grants capability-acquisition tools: ${cls.pluginTools.join(", ")}`);
246
+ }
247
+ else if (cls.risk === "mutating") {
248
+ warn(`subagent "${path.basename(agentFile)}" holds a mutating grant (${cls.reason}).`);
249
+ }
250
+ }
251
+ return findings;
252
+ }
253
+ function listAgentFiles(plugin) {
254
+ if (!plugin.agentsDir || !existsSync(plugin.agentsDir))
255
+ return [];
256
+ try {
257
+ return readdirSync(plugin.agentsDir)
258
+ .filter((f) => f.endsWith(".md"))
259
+ .map((f) => path.join(plugin.agentsDir, f));
260
+ }
261
+ catch {
262
+ return [];
263
+ }
264
+ }
265
+ function readToolsFrontmatter(file) {
266
+ try {
267
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(readFileSync(file, "utf8"));
268
+ return /^tools:\s*(.+)$/m.exec(match?.[1] ?? "")?.[1]?.trim();
269
+ }
270
+ catch {
271
+ return undefined;
272
+ }
273
+ }
274
+ /** Whether a set of findings fails the gate. `info` never does. */
275
+ export function findingsFail(findings, strict = false) {
276
+ return findings.some((f) => f.severity === "error" || (strict && f.severity === "warning"));
277
+ }
278
+ /** Run G1 and G2 over a plugin directory. */
279
+ export function runStaticGates(dir, opts = {}) {
280
+ const { findings: g1, conformance, plugin } = structural(dir, opts);
281
+ const findings = [...g1, ...safety(plugin)];
282
+ return { ok: !findingsFail(findings, opts.strict === true), findings, conformance, plugin };
283
+ }
284
+ /** Fold additional findings (G3) into an existing result. */
285
+ export function withFindings(result, extra, strict = false) {
286
+ const findings = [...result.findings, ...extra];
287
+ return { ...result, findings, ok: !findingsFail(findings, strict) };
288
+ }
289
+ /** Render findings for a tool result or a confirmation prompt. */
290
+ export function formatGateFindings(result) {
291
+ if (result.findings.length === 0) {
292
+ return `Checks passed (${result.conformance}).`;
293
+ }
294
+ const lines = result.findings.map((f) => ` [${f.gate}/${f.severity}] ${f.message}`);
295
+ return `${result.ok ? "Checks passed with warnings" : "Checks failed"} (${result.conformance}):\n${lines.join("\n")}`;
296
+ }
297
+ //# sourceMappingURL=gates.js.map