{"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAuBH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAmEhF;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAsB3C;AAED,eAAO,MAAM,aAAa,EAAE,mBAuK3B,CAAC","sourcesContent":["/**\n * GitHub Copilot format.\n *\n * hoocode's preferred Copilot home is the `.github/plugin/` marker directory —\n * the convention used by the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) and the documented location\n * for GitHub-hosted marketplaces. The official Copilot CLI plugin reference\n * (docs.github.com copilot/reference/copilot-cli-reference/cli-plugin-reference,\n * \"File locations\"; verified 2026-07) accepts several locations for both files\n * (`.plugin/`, plugin root, `.github/plugin/`, `.claude-plugin/`), so this\n * adapter reads them all — `.github/plugin/` first, then root, then `.plugin/`,\n * then the legacy hoocode layout — and writes `.github/plugin/`.\n * (`.claude-plugin/` belongs to the Claude adapter, which wins precedence, so a\n * `.claude-plugin`-only directory correctly parses as a Claude plugin; Copilot\n * reads those natively anyway.) The capability tree matches the Claude layout:\n *\n *   skills            skills/<name>/SKILL.md            (or the manifest's `skills` path override)\n *   commands          .github/prompts/<name>.prompt.md  (Copilot's prompt-file convention — the\n *                                                        analog of a slash command; a manifest\n *                                                        `commands` path or `commands/` dir is read too)\n *   subagents         agents/<name>.agent.md            (Copilot's custom-agent convention — the\n *                                                        `.agent.md` suffix and a YAML-list `tools`\n *                                                        frontmatter are what Copilot recognizes; the\n *                                                        reader still accepts bare `.md` names)\n *   hooks             hooks.json or hooks/hooks.json    ({ description?, hooks: { Event: [...] } })\n *   MCP servers       .mcp.json                         ({ mcpServers } | { servers })\n *   canvases          extensions/<name>/extension.mjs   (or the manifest's `extensions` path override;\n *                                                        opened on demand by /canvas, never loaded)\n *\n * Plus the Agent Plugins vendor content namespace, which is how\n * `github/awesome-copilot` publishes its whole catalog — portable skills stay at\n * `skills/`, and Copilot-specific capabilities move under a reverse-DNS\n * directory:\n *\n *   subagents         com.github.copilot/agents/<name>.md\n *   canvases          com.github.copilot/extensions/<name>/extension.mjs\n *\n * Workspace-level (non-plugin) conventions, per the current GitHub Copilot\n * customization docs (docs.github.com — agent skills, custom agents, prompt\n * files; verified 2026-07):\n *\n *   skills            .github/skills/<name>/SKILL.md    (agentskills.io open standard)\n *   custom agents     .github/agents/<name>.agent.md    (frontmatter: name, description,\n *                                                        tools as a YAML list, model, ...)\n *   prompt files      .github/prompts/<name>.prompt.md  (VS Code / Copilot prompt files)\n *\n * Earlier hoocode releases authored a different Copilot mapping\n * (`.github/copilot-plugin.json` + `.github/prompts/*.prompt.md` +\n * `.github/chatmodes/*.chatmode.md`); the reader still accepts that layout as a\n * legacy fallback so previously authored plugins keep loading. Copilot\n * conventions move quickly; this file is the single place that encodes them,\n * so tracking an upstream change never reaches beyond this adapter.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { NormalizedPlugin } from \"../manifest.js\";\nimport {\n\tauthoredHooksToConfig,\n\tCOPILOT_CONTENT_DIR,\n\tdetectCanvasExtensions,\n\tdetectUnsupportedSurfaces,\n\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n\tunknownManifestFields,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order, matching the Copilot CLI reference exactly:\n * `.plugin/` → root → `.github/plugin/` → (`.claude-plugin/`, which belongs to\n * the Claude adapter), with hoocode's legacy `.github/copilot-plugin.json` last.\n *\n * The order is the vendor's, not ours: a plugin carrying more than one manifest\n * must resolve to the same one under hoocode as under Copilot CLI, or the two\n * read different metadata from the same directory.\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(\".plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/**\n * Authored manifest location: root `plugin.json`, which is what the Copilot CLI\n * plugin-creating guide teaches and probe position #2. An earlier version emitted\n * `.github/plugin/plugin.json` while the comment here claimed root was canonical;\n * the comment was right. (`.github/plugin/` is still *read*, third in the probe\n * order above, since real marketplace-indexed plugins use it.)\n */\nconst emitManifestRelPath = \"plugin.json\";\n\n/**\n * Copilot prompt-file home. `.github/prompts/<name>.prompt.md` is the current\n * Copilot/VS Code prompt-file convention, and hoocode maps a plugin's commands\n * to it (both the writer and this reader), so it is the authored location for\n * commands — not merely a legacy fallback.\n */\nconst PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\n// Legacy authored layout (read-only fallback).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\n/**\n * Copilot's subagent home inside the vendor content namespace. This is the\n * layout `github/awesome-copilot` publishes: portable skills stay at `skills/`\n * and everything Copilot-specific is emitted under `com.github.copilot/`\n * (`eng/materialize-plugins.mjs`). Without it every agent in that catalog parses\n * as nothing.\n */\nconst CONTENT_AGENTS_DIR = path.join(COPILOT_CONTENT_DIR, \"agents\");\n// Documented alternates, not legacy: the reference lists both locations for each.\nconst ALT_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\nconst ALT_HOOKS_FILE = path.join(\"hooks\", \"hooks.json\");\n// hoocode's own older authored layout, still read so those plugins keep loading.\nconst LEGACY_HOOKS_FILE = path.join(MARKER_DIR, \"hooks\", \"hooks.json\");\n\n/** Read a hooks JSON file (either `{ hooks: {...} }` or a bare event map). */\nfunction readHooksFile(root: string, rel: string): NormalizedPlugin[\"hooks\"] {\n\tconst file = path.join(root, rel);\n\tif (!fs.existsSync(file)) return undefined;\n\tconst raw = readJson<{ hooks?: Record<string, unknown> } | Record<string, unknown>>(file);\n\tif (!raw || typeof raw !== \"object\") return undefined;\n\tconst config = (\"hooks\" in raw && raw.hooks ? raw.hooks : raw) as NormalizedPlugin[\"hooks\"];\n\treturn config && Object.keys(config).length > 0 ? config : undefined;\n}\n\nfunction manifestPathFor(root: string): string | undefined {\n\tfor (const rel of MANIFEST_REL_PATHS) {\n\t\tif (readJson(path.join(root, rel)) != null) return path.join(root, rel);\n\t}\n\treturn undefined;\n}\n\n/**\n * Every plugin-relative path this adapter reads, for the Tier 2 drift check\n * (§2.2). Assembled from the constants above rather than restated, so a path\n * added to the reader cannot be missing from the declaration.\n */\nexport function copilotReadPaths(): string[] {\n\treturn [\n\t\t...MANIFEST_REL_PATHS,\n\t\tPROMPTS_DIR,\n\t\tLEGACY_CHATMODES_DIR,\n\t\tALT_MCP_FILE,\n\t\tALT_HOOKS_FILE,\n\t\tLEGACY_HOOKS_FILE,\n\t\t\"skills\",\n\t\t\"agents\",\n\t\tCONTENT_AGENTS_DIR,\n\t\t// Canvas extensions: the container, the namespaced container, and the entry\n\t\t// file that is the whole detection contract.\n\t\t\"extensions\",\n\t\tpath.join(COPILOT_CONTENT_DIR, \"extensions\"),\n\t\t\"extension.mjs\",\n\t\t// The `<name>.agent.md` subagent convention, which the reference writes as\n\t\t// a suffix rather than a path.\n\t\t\".agent.md\",\n\t\t\"hooks.json\",\n\t\t\".mcp.json\",\n\t].map((p) => p.replace(/\\\\/g, \"/\"));\n}\n\nexport const copilotFormat: PluginFormatAdapter = {\n\tid: \"copilot\",\n\tplatform: \"github\",\n\tprecedence: 2,\n\tlabel: \"GitHub Copilot (.github)\",\n\t// .github/plugin/ first (hoocode's preferred Copilot home and the documented\n\t// location for GitHub-hosted marketplaces), then the legacy .github/ spot,\n\t// then the Copilot CLI's other probe locations (root, .plugin/).\n\tmarketplaceFiles: [\n\t\tpath.join(MARKER_DIR, \"plugin\", \"marketplace.json\"),\n\t\tpath.join(MARKER_DIR, \"marketplace.json\"),\n\t\t\"marketplace.json\",\n\t\tpath.join(\".plugin\", \"marketplace.json\"),\n\t],\n\n\tworkspace: {\n\t\troot: MARKER_DIR,\n\t\t// Copilot reads repo skills from .github/skills (also .claude/skills and\n\t\t// .agents/skills, which the sibling adapters cover).\n\t\temitSkill: (s) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t}),\n\t\t// Custom agents take `tools` as a YAML list (not the Claude comma string).\n\t\temitAgent: (a) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"agents\", `${slug(a.name)}.agent.md`),\n\t\t\tcontent: emitMarkdown(\n\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\ta.body,\n\t\t\t),\n\t\t}),\n\t\t// The closest Copilot equivalent of a slash command is a prompt file.\n\t\temitCommand: (c) => ({\n\t\t\tpath: path.join(MARKER_DIR, \"prompts\", `${slug(c.name)}.prompt.md`),\n\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t}),\n\t},\n\n\thasManifest(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tdetectPlugin(root: string): boolean {\n\t\treturn manifestPathFor(root) !== undefined;\n\t},\n\n\tparsePlugin(root: string): NormalizedPlugin | null {\n\t\tconst manifestPath = manifestPathFor(root);\n\t\tif (!manifestPath) return null;\n\t\tconst raw = readJson<RawManifest>(manifestPath);\n\t\tif (!raw) return null;\n\n\t\tconst id = (raw.name ?? path.basename(root)).trim();\n\t\tif (!id) return null;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tversion: raw.version,\n\t\t\tdescription: raw.description,\n\t\t\tauthor: parseAuthor(raw.author),\n\t\t\troot,\n\t\t\tmanifestPath,\n\t\t\tformat: \"copilot\",\n\t\t\t// Single-format view; the registry widens this to every format present.\n\t\t\tsupportPlatform: [\"github\"],\n\t\t\t// Claude-mirror layout (manifest overrides honored), with the legacy\n\t\t\t// prompts/chatmodes locations as read-only fallbacks.\n\t\t\tskillsDir: resolveCapabilityDir(root, raw.skills, \"skills\"),\n\t\t\t// Commands map to Copilot prompt files at `.github/prompts/`; a manifest\n\t\t\t// `commands` override or a conventional `commands/` dir still wins if present.\n\t\t\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, PROMPTS_DIR),\n\t\t\tagentsDir:\n\t\t\t\tresolveCapabilityDir(root, raw.agents, \"agents\") ??\n\t\t\t\tdirIfExists(root, CONTENT_AGENTS_DIR) ??\n\t\t\t\tdirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Manifest first, then the reference's two locations (root `hooks.json`\n\t\t\t// and `hooks/hooks.json`), then hoocode's legacy `.github/hooks/`.\n\t\t\thooks:\n\t\t\t\tnormalizeHooks(raw.hooks, root) ??\n\t\t\t\treadHooksFile(root, \"hooks.json\") ??\n\t\t\t\treadHooksFile(root, ALT_HOOKS_FILE) ??\n\t\t\t\treadHooksFile(root, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, ALT_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\n\t\t\t// Canvas extensions: `extensions/` (or the manifest's own path) and the\n\t\t\t// namespaced `com.github.copilot/extensions/`. Not loaded into the\n\t\t\t// session — `/canvas` opens them on demand — but resolved here so the\n\t\t\t// plugin can report what it brought.\n\t\t\tcanvasExtensions: detectCanvasExtensions(root, id, raw.extensions),\n\t\t\t// `lspServers` is a real Copilot manifest key we do not model, and\n\t\t\t// `extensions` carries a vendor-metadata map alongside the canvas path\n\t\t\t// read above; both are carried here so an edit cannot drop them.\n\t\t\tunknownFields: unknownManifestFields(raw),\n\t\t\tunsupportedSurfaces: detectUnsupportedSurfaces(root),\n\t\t};\n\t},\n\n\temit(draft: PluginDraft): EmittedFile[] {\n\t\t// Only the manifest is Copilot-specific — the canonical location is root\n\t\t// `plugin.json` (Copilot CLI spec) — and the capability tree mirrors the\n\t\t// Claude layout, so a plugin authored for both platforms is one tree with a\n\t\t// root manifest for Copilot and a .claude-plugin/ manifest for Claude.\n\t\tconst files: EmittedFile[] = [];\n\n\t\tfiles.push({\n\t\t\tpath: emitManifestRelPath,\n\t\t\tcontent: emitJson({\n\t\t\t\t...(draft.unknownFields ?? {}),\n\t\t\t\tname: draft.id,\n\t\t\t\t...(draft.version ? { version: draft.version } : {}),\n\t\t\t\t...(draft.description ? { description: draft.description } : {}),\n\t\t\t\t// Spec: `author` is an object with a required `name`.\n\t\t\t\t...(draft.author ? { author: { name: draft.author } } : {}),\n\t\t\t}),\n\t\t});\n\n\t\tfor (const s of draft.skills ?? []) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"skills\", slug(s.name), \"SKILL.md\"),\n\t\t\t\tcontent: emitMarkdown({ name: s.name, description: s.description }, s.body),\n\t\t\t});\n\t\t}\n\t\tfor (const c of draft.commands ?? []) {\n\t\t\t// Copilot's analog of a slash command is a prompt file:\n\t\t\t// `.github/prompts/<name>.prompt.md` (docs.github.com prompt files;\n\t\t\t// verified 2026-07) — same target as this adapter's workspace.emitCommand.\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(PROMPTS_DIR, `${slug(c.name)}.prompt.md`),\n\t\t\t\tcontent: emitMarkdown({ description: c.description }, c.body),\n\t\t\t});\n\t\t}\n\t\tfor (const a of draft.agents ?? []) {\n\t\t\t// Copilot recognizes custom agents by the `.agent.md` suffix, and takes\n\t\t\t// `tools` as a YAML list (not the Claude comma string) — matching this\n\t\t\t// adapter's workspace.emitAgent and the current GitHub Copilot docs\n\t\t\t// (docs.github.com custom agents / plugins-creating; verified 2026-07).\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.agent.md`),\n\t\t\t\tcontent: emitMarkdown(\n\t\t\t\t\t{ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model },\n\t\t\t\t\ta.body,\n\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\tif (draft.mcpServers?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: \".mcp.json\",\n\t\t\t\tcontent: emitJson({\n\t\t\t\t\tmcpServers: Object.fromEntries(\n\t\t\t\t\t\tdraft.mcpServers.map((s) => [\n\t\t\t\t\t\t\ts.name,\n\t\t\t\t\t\t\t{ command: s.command, ...(s.args ? { args: s.args } : {}), ...(s.env ? { env: s.env } : {}) },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t});\n\t\t}\n\t\tif (draft.hooks?.length) {\n\t\t\tfiles.push({\n\t\t\t\tpath: path.join(\"hooks\", \"hooks.json\"),\n\t\t\t\tcontent: emitJson({ hooks: authoredHooksToConfig(draft.hooks) }),\n\t\t\t});\n\t\t}\n\t\treturn files;\n\t},\n};\n"]}