{"version":3,"file":"shared.d.ts","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 { CANVAS_ENTRY_FILE } from \"../../../canvas/discovery.js\";\nimport type { PluginCanvasExtension, 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/**\n\t * Copilot: canvas-extension directory (a path, or list of paths, relative to\n\t * the plugin root — e.g. `\"extensions\": \"extensions\"`). The Agent Plugins spec\n\t * gives the same key a second, unrelated meaning: a map of vendor metadata\n\t * namespaces (`{ \"com.github.copilot\": { logo } }`). Both readings are handled\n\t * — see {@link detectCanvasExtensions} — and the map form is preserved\n\t * verbatim through `unknownFields`.\n\t */\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 * Vendor content namespace directory. The Agent Plugins layout both vendors are\n * converging on keeps portable capabilities at the plugin root (`skills/`) and\n * puts vendor-specific ones under a reverse-DNS directory, so Copilot's agents\n * and canvas extensions ship as `com.github.copilot/<capability>/`.\n * `github/awesome-copilot` publishes its whole catalog this way.\n */\nexport const COPILOT_CONTENT_DIR = \"com.github.copilot\";\n\n/** Conventional canvas-extension container, relative to a plugin root. */\nconst CANVAS_EXTENSIONS_DIR = \"extensions\";\n\n/**\n * Canvas-extension containers a plugin may ship, starting with the manifest's own\n * `extensions` path (Copilot's key, e.g. `\"extensions\": \"extensions\"`). Both are\n * real: `extensions/` is what a standalone canvas plugin uses, and the namespaced\n * one is what a materialized `awesome-copilot` entry carries.\n */\nfunction canvasContainerDirs(root: string, override: unknown): string[] {\n\tconst dirs: string[] = [];\n\t// `extensions` is also the key the Agent Plugins spec uses for a *map* of\n\t// vendor metadata (`{ \"com.github.copilot\": { logo } }`). `resolveCapabilityDir`\n\t// ignores anything that is not a string or string array, so the two readings\n\t// of one key cannot collide — and the map form stays in `unknownFields`, which\n\t// is what keeps a re-emit from dropping it.\n\tconst declared = resolveCapabilityDir(root, override, CANVAS_EXTENSIONS_DIR);\n\tif (declared) dirs.push(declared);\n\tconst namespaced = dirIfExists(root, path.join(COPILOT_CONTENT_DIR, CANVAS_EXTENSIONS_DIR));\n\tif (namespaced && !dirs.includes(namespaced)) dirs.push(namespaced);\n\treturn dirs;\n}\n\n/**\n * Canvas extensions a plugin ships (see `docs/canvas-extensions-design.md` §4.3).\n *\n * Discovery keys off `<dir>/extension.mjs` and nothing else, exactly as\n * `core/canvas/discovery.ts` does for the workspace and user search roots — the\n * entry file is the whole contract, so a plugin's canvases are found by the same\n * rule as everyone else's.\n *\n * A plugin whose *root* carries `extension.mjs` is itself one canvas extension,\n * which is how a single-canvas repository is laid out; its id is the plugin id,\n * since there is no directory name below the root to take one from.\n */\nexport function detectCanvasExtensions(\n\troot: string,\n\tpluginId: string,\n\toverride: unknown,\n): PluginCanvasExtension[] | undefined {\n\tconst found: PluginCanvasExtension[] = [];\n\tconst seen = new Set<string>();\n\tconst add = (id: string, dir: string): void => {\n\t\tif (seen.has(id)) return;\n\t\tif (!fs.existsSync(path.join(dir, CANVAS_ENTRY_FILE))) return;\n\t\tseen.add(id);\n\t\tfound.push({ id, dir });\n\t};\n\n\tadd(pluginId, root);\n\tfor (const container of canvasContainerDirs(root, override)) {\n\t\tlet entries: fs.Dirent[];\n\t\ttry {\n\t\t\tentries = fs.readdirSync(container, { withFileTypes: true });\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n\t\t\tif (!entry.isDirectory() && !entry.isSymbolicLink()) continue;\n\t\t\tadd(entry.name, path.join(container, entry.name));\n\t\t}\n\t}\n\treturn found.length > 0 ? found : 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, SearchCodebase\") as a YAML flow sequence (`['read', 'SearchCodebase']`) 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"]}