@kolisachint/hoocode-agent 0.4.138 → 0.4.139

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.139] - 2026-07-17
4
+
5
+ ### Fixed
6
+
7
+ - GitHub Copilot plugin authoring (`ProposePlugin` / `UpdatePlugin` with
8
+ `--support-platform github`) now emits custom agents as
9
+ `agents/<name>.agent.md` with a YAML-list `tools` frontmatter, matching the
10
+ current GitHub Copilot plugin spec (docs.github.com custom agents /
11
+ plugins-creating; verified 2026-07) so Copilot recognizes them. Previously
12
+ they were written as bare `agents/<name>.md` with a Claude-style comma-string
13
+ `tools`, which Copilot does not load as agents. The reader still accepts the
14
+ bare `.md` layout (and the legacy `.github/chatmodes/` fallback), so
15
+ previously authored plugins keep loading.
16
+ - GitHub Copilot plugin authoring now maps a plugin's commands to Copilot
17
+ prompt files — `.github/prompts/<name>.prompt.md` — instead of Claude-style
18
+ `commands/<name>.md`, matching the Copilot/VS Code prompt-file convention and
19
+ this adapter's workspace command layout. The reader loads them from
20
+ `.github/prompts/` (and still honors a manifest `commands` path or a
21
+ `commands/` dir). Relatedly, command-name derivation now strips a `.prompt.md`
22
+ suffix, so a `greet.prompt.md` prompt file loads as the command `greet`
23
+ (previously `greet.prompt`).
24
+
3
25
  ## [0.4.138] - 2026-07-17
4
26
 
5
27
  ### Changed
@@ -15,8 +15,13 @@
15
15
  * reads those natively anyway.) The capability tree matches the Claude layout:
16
16
  *
17
17
  * skills skills/<name>/SKILL.md (or the manifest's `skills` path override)
18
- * commands commands/<name>.md
19
- * subagents agents/<name>.md (Copilot also emits/reads `.agent.md` names)
18
+ * commands .github/prompts/<name>.prompt.md (Copilot's prompt-file convention — the
19
+ * analog of a slash command; a manifest
20
+ * `commands` path or `commands/` dir is read too)
21
+ * subagents agents/<name>.agent.md (Copilot's custom-agent convention — the
22
+ * `.agent.md` suffix and a YAML-list `tools`
23
+ * frontmatter are what Copilot recognizes; the
24
+ * reader still accepts bare `.md` names)
20
25
  * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })
21
26
  * MCP servers .mcp.json ({ mcpServers } | { servers })
22
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAmBH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA2ChF,eAAO,MAAM,aAAa,EAAE,mBAwI3B,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 commands/<name>.md\n * subagents agents/<name>.md (Copilot also emits/reads `.agent.md` names)\n * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })\n * MCP servers .mcp.json ({ mcpServers } | { servers })\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\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order: `.github/plugin/plugin.json` first — hoocode's\n * preferred Copilot home, matching the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) — then the Copilot CLI\n * reference's other locations (root `plugin.json`, `.plugin/plugin.json`) and\n * hoocode's legacy `.github/copilot-plugin.json`. (`.claude-plugin/plugin.json`,\n * also probed by the CLI, belongs to the Claude adapter.)\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(\".plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/** Authored manifest location: the preferred `.github/plugin/` home. */\nconst emitManifestRelPath = path.join(MARKER_DIR, \"plugin\", \"plugin.json\");\n\n// Legacy authored layout (read-only fallbacks).\nconst LEGACY_PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\nconst LEGACY_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\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\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\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\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, LEGACY_PROMPTS_DIR),\n\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Copilot CLI plugins put hooks config at root `hooks.json`; normalizeHooks\n\t\t\t// covers the Claude-mirror `hooks/hooks.json`, then the legacy location.\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, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, LEGACY_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\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\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\tfiles.push({\n\t\t\t\tpath: path.join(\"commands\", `${slug(c.name)}.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\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.md`),\n\t\t\t\tcontent: emitMarkdown({ name: a.name, description: a.description, tools: a.tools, model: a.model }, a.body),\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"]}
1
+ {"version":3,"file":"copilot.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAmBH,OAAO,KAAK,EAA4B,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAiDhF,eAAO,MAAM,aAAa,EAAE,mBAoJ3B,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 *\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\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order: `.github/plugin/plugin.json` first — hoocode's\n * preferred Copilot home, matching the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) — then the Copilot CLI\n * reference's other locations (root `plugin.json`, `.plugin/plugin.json`) and\n * hoocode's legacy `.github/copilot-plugin.json`. (`.claude-plugin/plugin.json`,\n * also probed by the CLI, belongs to the Claude adapter.)\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(\".plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/** Authored manifest location: the preferred `.github/plugin/` home. */\nconst emitManifestRelPath = path.join(MARKER_DIR, \"plugin\", \"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 fallbacks).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\nconst LEGACY_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\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\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\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: resolveCapabilityDir(root, raw.agents, \"agents\") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Copilot CLI plugins put hooks config at root `hooks.json`; normalizeHooks\n\t\t\t// covers the Claude-mirror `hooks/hooks.json`, then the legacy location.\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, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, LEGACY_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\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\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"]}
@@ -15,8 +15,13 @@
15
15
  * reads those natively anyway.) The capability tree matches the Claude layout:
16
16
  *
17
17
  * skills skills/<name>/SKILL.md (or the manifest's `skills` path override)
18
- * commands commands/<name>.md
19
- * subagents agents/<name>.md (Copilot also emits/reads `.agent.md` names)
18
+ * commands .github/prompts/<name>.prompt.md (Copilot's prompt-file convention — the
19
+ * analog of a slash command; a manifest
20
+ * `commands` path or `commands/` dir is read too)
21
+ * subagents agents/<name>.agent.md (Copilot's custom-agent convention — the
22
+ * `.agent.md` suffix and a YAML-list `tools`
23
+ * frontmatter are what Copilot recognizes; the
24
+ * reader still accepts bare `.md` names)
20
25
  * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })
21
26
  * MCP servers .mcp.json ({ mcpServers } | { servers })
22
27
  *
@@ -56,8 +61,14 @@ const MANIFEST_REL_PATHS = [
56
61
  ];
57
62
  /** Authored manifest location: the preferred `.github/plugin/` home. */
58
63
  const emitManifestRelPath = path.join(MARKER_DIR, "plugin", "plugin.json");
64
+ /**
65
+ * Copilot prompt-file home. `.github/prompts/<name>.prompt.md` is the current
66
+ * Copilot/VS Code prompt-file convention, and hoocode maps a plugin's commands
67
+ * to it (both the writer and this reader), so it is the authored location for
68
+ * commands — not merely a legacy fallback.
69
+ */
70
+ const PROMPTS_DIR = path.join(MARKER_DIR, "prompts");
59
71
  // Legacy authored layout (read-only fallbacks).
60
- const LEGACY_PROMPTS_DIR = path.join(MARKER_DIR, "prompts");
61
72
  const LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, "chatmodes");
62
73
  const LEGACY_MCP_FILE = path.join(MARKER_DIR, "mcp.json");
63
74
  const LEGACY_HOOKS_FILE = path.join(MARKER_DIR, "hooks", "hooks.json");
@@ -138,7 +149,9 @@ export const copilotFormat = {
138
149
  // Claude-mirror layout (manifest overrides honored), with the legacy
139
150
  // prompts/chatmodes locations as read-only fallbacks.
140
151
  skillsDir: resolveCapabilityDir(root, raw.skills, "skills"),
141
- commandsDir: resolveCapabilityDir(root, raw.commands, "commands") ?? dirIfExists(root, LEGACY_PROMPTS_DIR),
152
+ // Commands map to Copilot prompt files at `.github/prompts/`; a manifest
153
+ // `commands` override or a conventional `commands/` dir still wins if present.
154
+ commandsDir: resolveCapabilityDir(root, raw.commands, "commands") ?? dirIfExists(root, PROMPTS_DIR),
142
155
  agentsDir: resolveCapabilityDir(root, raw.agents, "agents") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),
143
156
  themesDir: resolveCapabilityDir(root, raw.themes, "themes"),
144
157
  // Copilot CLI plugins put hooks config at root `hooks.json`; normalizeHooks
@@ -174,15 +187,22 @@ export const copilotFormat = {
174
187
  });
175
188
  }
176
189
  for (const c of draft.commands ?? []) {
190
+ // Copilot's analog of a slash command is a prompt file:
191
+ // `.github/prompts/<name>.prompt.md` (docs.github.com prompt files;
192
+ // verified 2026-07) — same target as this adapter's workspace.emitCommand.
177
193
  files.push({
178
- path: path.join("commands", `${slug(c.name)}.md`),
194
+ path: path.join(PROMPTS_DIR, `${slug(c.name)}.prompt.md`),
179
195
  content: emitMarkdown({ description: c.description }, c.body),
180
196
  });
181
197
  }
182
198
  for (const a of draft.agents ?? []) {
199
+ // Copilot recognizes custom agents by the `.agent.md` suffix, and takes
200
+ // `tools` as a YAML list (not the Claude comma string) — matching this
201
+ // adapter's workspace.emitAgent and the current GitHub Copilot docs
202
+ // (docs.github.com custom agents / plugins-creating; verified 2026-07).
183
203
  files.push({
184
- path: path.join("agents", `${slug(a.name)}.md`),
185
- content: emitMarkdown({ name: a.name, description: a.description, tools: a.tools, model: a.model }, a.body),
204
+ path: path.join("agents", `${slug(a.name)}.agent.md`),
205
+ content: emitMarkdown({ name: a.name, description: a.description, tools: toolsYamlList(a.tools), model: a.model }, a.body),
186
206
  });
187
207
  }
188
208
  if (draft.mcpServers?.length) {
@@ -1 +1 @@
1
- {"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"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","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 commands/<name>.md\n * subagents agents/<name>.md (Copilot also emits/reads `.agent.md` names)\n * hooks hooks.json or hooks/hooks.json ({ description?, hooks: { Event: [...] } })\n * MCP servers .mcp.json ({ mcpServers } | { servers })\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\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order: `.github/plugin/plugin.json` first — hoocode's\n * preferred Copilot home, matching the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) — then the Copilot CLI\n * reference's other locations (root `plugin.json`, `.plugin/plugin.json`) and\n * hoocode's legacy `.github/copilot-plugin.json`. (`.claude-plugin/plugin.json`,\n * also probed by the CLI, belongs to the Claude adapter.)\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(\".plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/** Authored manifest location: the preferred `.github/plugin/` home. */\nconst emitManifestRelPath = path.join(MARKER_DIR, \"plugin\", \"plugin.json\");\n\n// Legacy authored layout (read-only fallbacks).\nconst LEGACY_PROMPTS_DIR = path.join(MARKER_DIR, \"prompts\");\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\nconst LEGACY_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\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\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\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\tcommandsDir: resolveCapabilityDir(root, raw.commands, \"commands\") ?? dirIfExists(root, LEGACY_PROMPTS_DIR),\n\t\t\tagentsDir: resolveCapabilityDir(root, raw.agents, \"agents\") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Copilot CLI plugins put hooks config at root `hooks.json`; normalizeHooks\n\t\t\t// covers the Claude-mirror `hooks/hooks.json`, then the legacy location.\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, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, LEGACY_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\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\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\tfiles.push({\n\t\t\t\tpath: path.join(\"commands\", `${slug(c.name)}.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\tfiles.push({\n\t\t\t\tpath: path.join(\"agents\", `${slug(a.name)}.md`),\n\t\t\t\tcontent: emitMarkdown({ name: a.name, description: a.description, tools: a.tools, model: a.model }, a.body),\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"]}
1
+ {"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../../../../src/core/extensions/plugins/formats/copilot.ts"],"names":[],"mappings":"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","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 *\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\tdirIfExists,\n\temitJson,\n\temitMarkdown,\n\tnormalizeHooks,\n\tnormalizeMcp,\n\tparseAuthor,\n\ttype RawManifest,\n\treadJson,\n\tresolveCapabilityDir,\n\tslug,\n\ttoolsYamlList,\n} from \"./shared.js\";\nimport type { EmittedFile, PluginDraft, PluginFormatAdapter } from \"./types.js\";\n\nconst MARKER_DIR = \".github\";\n/**\n * Manifest probe order: `.github/plugin/plugin.json` first — hoocode's\n * preferred Copilot home, matching the real-world plugins indexed by\n * github/copilot-plugins (e.g. microsoft/work-iq) — then the Copilot CLI\n * reference's other locations (root `plugin.json`, `.plugin/plugin.json`) and\n * hoocode's legacy `.github/copilot-plugin.json`. (`.claude-plugin/plugin.json`,\n * also probed by the CLI, belongs to the Claude adapter.)\n */\nconst MANIFEST_REL_PATHS = [\n\tpath.join(MARKER_DIR, \"plugin\", \"plugin.json\"),\n\t\"plugin.json\",\n\tpath.join(\".plugin\", \"plugin.json\"),\n\tpath.join(MARKER_DIR, \"copilot-plugin.json\"),\n] as const;\n/** Authored manifest location: the preferred `.github/plugin/` home. */\nconst emitManifestRelPath = path.join(MARKER_DIR, \"plugin\", \"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 fallbacks).\nconst LEGACY_CHATMODES_DIR = path.join(MARKER_DIR, \"chatmodes\");\nconst LEGACY_MCP_FILE = path.join(MARKER_DIR, \"mcp.json\");\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\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\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: resolveCapabilityDir(root, raw.agents, \"agents\") ?? dirIfExists(root, LEGACY_CHATMODES_DIR),\n\t\t\tthemesDir: resolveCapabilityDir(root, raw.themes, \"themes\"),\n\t\t\t// Copilot CLI plugins put hooks config at root `hooks.json`; normalizeHooks\n\t\t\t// covers the Claude-mirror `hooks/hooks.json`, then the legacy location.\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, LEGACY_HOOKS_FILE),\n\t\t\tmcpServers: normalizeMcp(raw.mcpServers, root) ?? normalizeMcp(undefined, root, LEGACY_MCP_FILE),\n\t\t\t// Providers are a native-only concept.\n\t\t\tproviders: undefined,\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\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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-templates.d.ts","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"AAKA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAkCtE;AA+ED,MAAM,WAAW,0BAA0B;IAC1C,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,0CAA0C;IAC1C,eAAe,EAAE,OAAO,CAAC;CACzB;AAeD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,EAAE,CAiGzF;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,wBAAwB;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,uBAAuB,CAgB1G;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,MAAM,CAEtF","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, isAbsolute, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.js\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport type PromptTemplateType = \"user\" | \"system\" | \"context\";\n\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\t/** How the expanded template is injected into the conversation. */\n\ttype: PromptTemplateType;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n *\n * Note: Replacement happens on the template string only. Argument values\n * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $1, $2, etc. with positional args FIRST (before wildcards)\n\t// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\t// Replace ${@:start} or ${@:start:length} with sliced args (bash-style)\n\t// Process BEFORE simple $@ to avoid conflicts\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr, lengthStr) => {\n\t\tlet start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)\n\t\t// Treat 0 as 1 (bash convention: args start at 1)\n\t\tif (start < 0) start = 0;\n\n\t\tif (lengthStr) {\n\t\t\tconst length = parseInt(lengthStr, 10);\n\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t}\n\t\treturn args.slice(start).join(\" \");\n\t});\n\n\t// Pre-compute all args joined (optimization)\n\tconst allArgs = args.join(\" \");\n\n\t// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\n\t// Replace $@ with all args joined (existing syntax)\n\tresult = result.replace(/\\$@/g, allArgs);\n\n\treturn result;\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\tconst name = basename(filePath).replace(/\\.md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\tconst type: PromptTemplateType =\n\t\t\tfrontmatter.type === \"system\" ? \"system\" : frontmatter.type === \"context\" ? \"context\" : \"user\";\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\ttype,\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths: string[];\n\t/** Explicit slash-command paths (files or directories). */\n\tslashCommandPaths?: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults: boolean;\n}\n\nfunction normalizePath(input: string): string {\n\tconst trimmed = input.trim();\n\tif (trimmed === \"~\") return homedir();\n\tif (trimmed.startsWith(\"~/\")) return join(homedir(), trimmed.slice(2));\n\tif (trimmed.startsWith(\"~\")) return join(homedir(), trimmed.slice(1));\n\treturn trimmed;\n}\n\nfunction resolvePromptPath(p: string, cwd: string): string {\n\tconst normalized = normalizePath(p);\n\treturn isAbsolute(normalized) ? normalized : resolve(cwd, normalized);\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = options.cwd;\n\tconst resolvedAgentDir = options.agentDir;\n\tconst promptPaths = options.promptPaths;\n\tconst slashCommandPaths = options.slashCommandPaths ?? [];\n\tconst includeDefaults = options.includeDefaults;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = options.agentDir ? join(options.agentDir, \"prompts\") : resolvedAgentDir;\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\tconst globalSlashCommandsDir = options.agentDir ? join(options.agentDir, \"commands\") : resolvedAgentDir;\n\tconst projectSlashCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"commands\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir) || isUnderPath(resolvedPath, globalSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, globalPromptsDir) ? globalPromptsDir : globalSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir) || isUnderPath(resolvedPath, projectSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, projectPromptsDir) ? projectPromptsDir : projectSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\t// Load slash command dirs first so they win over prompt dirs on name collision\n\t\ttemplates.push(...loadTemplatesFromDir(globalSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit slash-command paths (before prompt paths so commands win on collision)\n\tfor (const rawPath of slashCommandPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\t// 4. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Result of attempting to expand a prompt template.\n */\nexport interface PromptTemplateExpansion {\n\t/** The expanded text (or original text if not a template). */\n\ttext: string;\n\t/** The matched template, if any. */\n\ttemplate?: PromptTemplate;\n\t/** Parsed arguments. */\n\targs: string[];\n\t/** Raw argument string (everything after the command name). */\n\targsString: string;\n}\n\n/**\n * Try to expand a prompt template if it matches a template name.\n * Returns expansion metadata so callers can handle `type: \"system\"` or `type: \"context\"`.\n */\nexport function tryExpandPromptTemplate(text: string, templates: PromptTemplate[]): PromptTemplateExpansion {\n\tif (!text.startsWith(\"/\")) {\n\t\treturn { text, args: [], argsString: \"\" };\n\t}\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn { text: substituteArgs(template.content, args), template, args, argsString };\n\t}\n\n\treturn { text, args: [], argsString: \"\" };\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\treturn tryExpandPromptTemplate(text, templates).text;\n}\n"]}
1
+ {"version":3,"file":"prompt-templates.d.ts","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"AAKA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAkCtE;AAiFD,MAAM,WAAW,0BAA0B;IAC1C,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,0CAA0C;IAC1C,eAAe,EAAE,OAAO,CAAC;CACzB;AAeD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,EAAE,CAiGzF;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACvC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,wBAAwB;IACxB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,uBAAuB,CAgB1G;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,MAAM,CAEtF","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, isAbsolute, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.js\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport type PromptTemplateType = \"user\" | \"system\" | \"context\";\n\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\t/** How the expanded template is injected into the conversation. */\n\ttype: PromptTemplateType;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n *\n * Note: Replacement happens on the template string only. Argument values\n * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $1, $2, etc. with positional args FIRST (before wildcards)\n\t// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\t// Replace ${@:start} or ${@:start:length} with sliced args (bash-style)\n\t// Process BEFORE simple $@ to avoid conflicts\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr, lengthStr) => {\n\t\tlet start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)\n\t\t// Treat 0 as 1 (bash convention: args start at 1)\n\t\tif (start < 0) start = 0;\n\n\t\tif (lengthStr) {\n\t\t\tconst length = parseInt(lengthStr, 10);\n\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t}\n\t\treturn args.slice(start).join(\" \");\n\t});\n\n\t// Pre-compute all args joined (optimization)\n\tconst allArgs = args.join(\" \");\n\n\t// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\n\t// Replace $@ with all args joined (existing syntax)\n\tresult = result.replace(/\\$@/g, allArgs);\n\n\treturn result;\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\t// Strip a Copilot `.prompt.md` suffix as well as a plain `.md`, so both\n\t\t// `greet.prompt.md` (prompt files) and `greet.md` yield the command `greet`.\n\t\tconst name = basename(filePath).replace(/\\.(prompt\\.)?md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\tconst type: PromptTemplateType =\n\t\t\tfrontmatter.type === \"system\" ? \"system\" : frontmatter.type === \"context\" ? \"context\" : \"user\";\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\ttype,\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths: string[];\n\t/** Explicit slash-command paths (files or directories). */\n\tslashCommandPaths?: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults: boolean;\n}\n\nfunction normalizePath(input: string): string {\n\tconst trimmed = input.trim();\n\tif (trimmed === \"~\") return homedir();\n\tif (trimmed.startsWith(\"~/\")) return join(homedir(), trimmed.slice(2));\n\tif (trimmed.startsWith(\"~\")) return join(homedir(), trimmed.slice(1));\n\treturn trimmed;\n}\n\nfunction resolvePromptPath(p: string, cwd: string): string {\n\tconst normalized = normalizePath(p);\n\treturn isAbsolute(normalized) ? normalized : resolve(cwd, normalized);\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = options.cwd;\n\tconst resolvedAgentDir = options.agentDir;\n\tconst promptPaths = options.promptPaths;\n\tconst slashCommandPaths = options.slashCommandPaths ?? [];\n\tconst includeDefaults = options.includeDefaults;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = options.agentDir ? join(options.agentDir, \"prompts\") : resolvedAgentDir;\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\tconst globalSlashCommandsDir = options.agentDir ? join(options.agentDir, \"commands\") : resolvedAgentDir;\n\tconst projectSlashCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"commands\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir) || isUnderPath(resolvedPath, globalSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, globalPromptsDir) ? globalPromptsDir : globalSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir) || isUnderPath(resolvedPath, projectSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, projectPromptsDir) ? projectPromptsDir : projectSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\t// Load slash command dirs first so they win over prompt dirs on name collision\n\t\ttemplates.push(...loadTemplatesFromDir(globalSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit slash-command paths (before prompt paths so commands win on collision)\n\tfor (const rawPath of slashCommandPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\t// 4. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Result of attempting to expand a prompt template.\n */\nexport interface PromptTemplateExpansion {\n\t/** The expanded text (or original text if not a template). */\n\ttext: string;\n\t/** The matched template, if any. */\n\ttemplate?: PromptTemplate;\n\t/** Parsed arguments. */\n\targs: string[];\n\t/** Raw argument string (everything after the command name). */\n\targsString: string;\n}\n\n/**\n * Try to expand a prompt template if it matches a template name.\n * Returns expansion metadata so callers can handle `type: \"system\"` or `type: \"context\"`.\n */\nexport function tryExpandPromptTemplate(text: string, templates: PromptTemplate[]): PromptTemplateExpansion {\n\tif (!text.startsWith(\"/\")) {\n\t\treturn { text, args: [], argsString: \"\" };\n\t}\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn { text: substituteArgs(template.content, args), template, args, argsString };\n\t}\n\n\treturn { text, args: [], argsString: \"\" };\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\treturn tryExpandPromptTemplate(text, templates).text;\n}\n"]}
@@ -84,7 +84,9 @@ function loadTemplateFromFile(filePath, sourceInfo) {
84
84
  try {
85
85
  const rawContent = readFileSync(filePath, "utf-8");
86
86
  const { frontmatter, body } = parseFrontmatter(rawContent);
87
- const name = basename(filePath).replace(/\.md$/, "");
87
+ // Strip a Copilot `.prompt.md` suffix as well as a plain `.md`, so both
88
+ // `greet.prompt.md` (prompt files) and `greet.md` yield the command `greet`.
89
+ const name = basename(filePath).replace(/\.(prompt\.)?md$/, "");
88
90
  // Get description from frontmatter or first non-empty line
89
91
  let description = frontmatter.description || "";
90
92
  if (!description) {
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-templates.js","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, isAbsolute, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.js\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport type PromptTemplateType = \"user\" | \"system\" | \"context\";\n\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\t/** How the expanded template is injected into the conversation. */\n\ttype: PromptTemplateType;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n *\n * Note: Replacement happens on the template string only. Argument values\n * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $1, $2, etc. with positional args FIRST (before wildcards)\n\t// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\t// Replace ${@:start} or ${@:start:length} with sliced args (bash-style)\n\t// Process BEFORE simple $@ to avoid conflicts\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr, lengthStr) => {\n\t\tlet start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)\n\t\t// Treat 0 as 1 (bash convention: args start at 1)\n\t\tif (start < 0) start = 0;\n\n\t\tif (lengthStr) {\n\t\t\tconst length = parseInt(lengthStr, 10);\n\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t}\n\t\treturn args.slice(start).join(\" \");\n\t});\n\n\t// Pre-compute all args joined (optimization)\n\tconst allArgs = args.join(\" \");\n\n\t// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\n\t// Replace $@ with all args joined (existing syntax)\n\tresult = result.replace(/\\$@/g, allArgs);\n\n\treturn result;\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\tconst name = basename(filePath).replace(/\\.md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\tconst type: PromptTemplateType =\n\t\t\tfrontmatter.type === \"system\" ? \"system\" : frontmatter.type === \"context\" ? \"context\" : \"user\";\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\ttype,\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths: string[];\n\t/** Explicit slash-command paths (files or directories). */\n\tslashCommandPaths?: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults: boolean;\n}\n\nfunction normalizePath(input: string): string {\n\tconst trimmed = input.trim();\n\tif (trimmed === \"~\") return homedir();\n\tif (trimmed.startsWith(\"~/\")) return join(homedir(), trimmed.slice(2));\n\tif (trimmed.startsWith(\"~\")) return join(homedir(), trimmed.slice(1));\n\treturn trimmed;\n}\n\nfunction resolvePromptPath(p: string, cwd: string): string {\n\tconst normalized = normalizePath(p);\n\treturn isAbsolute(normalized) ? normalized : resolve(cwd, normalized);\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = options.cwd;\n\tconst resolvedAgentDir = options.agentDir;\n\tconst promptPaths = options.promptPaths;\n\tconst slashCommandPaths = options.slashCommandPaths ?? [];\n\tconst includeDefaults = options.includeDefaults;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = options.agentDir ? join(options.agentDir, \"prompts\") : resolvedAgentDir;\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\tconst globalSlashCommandsDir = options.agentDir ? join(options.agentDir, \"commands\") : resolvedAgentDir;\n\tconst projectSlashCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"commands\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir) || isUnderPath(resolvedPath, globalSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, globalPromptsDir) ? globalPromptsDir : globalSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir) || isUnderPath(resolvedPath, projectSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, projectPromptsDir) ? projectPromptsDir : projectSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\t// Load slash command dirs first so they win over prompt dirs on name collision\n\t\ttemplates.push(...loadTemplatesFromDir(globalSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit slash-command paths (before prompt paths so commands win on collision)\n\tfor (const rawPath of slashCommandPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\t// 4. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Result of attempting to expand a prompt template.\n */\nexport interface PromptTemplateExpansion {\n\t/** The expanded text (or original text if not a template). */\n\ttext: string;\n\t/** The matched template, if any. */\n\ttemplate?: PromptTemplate;\n\t/** Parsed arguments. */\n\targs: string[];\n\t/** Raw argument string (everything after the command name). */\n\targsString: string;\n}\n\n/**\n * Try to expand a prompt template if it matches a template name.\n * Returns expansion metadata so callers can handle `type: \"system\"` or `type: \"context\"`.\n */\nexport function tryExpandPromptTemplate(text: string, templates: PromptTemplate[]): PromptTemplateExpansion {\n\tif (!text.startsWith(\"/\")) {\n\t\treturn { text, args: [], argsString: \"\" };\n\t}\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn { text: substituteArgs(template.content, args), template, args, argsString };\n\t}\n\n\treturn { text, args: [], argsString: \"\" };\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\treturn tryExpandPromptTemplate(text, templates).text;\n}\n"]}
1
+ {"version":3,"file":"prompt-templates.js","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, isAbsolute, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.js\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport type PromptTemplateType = \"user\" | \"system\" | \"context\";\n\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\t/** How the expanded template is injected into the conversation. */\n\ttype: PromptTemplateType;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n *\n * Note: Replacement happens on the template string only. Argument values\n * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $1, $2, etc. with positional args FIRST (before wildcards)\n\t// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\t// Replace ${@:start} or ${@:start:length} with sliced args (bash-style)\n\t// Process BEFORE simple $@ to avoid conflicts\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr, lengthStr) => {\n\t\tlet start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)\n\t\t// Treat 0 as 1 (bash convention: args start at 1)\n\t\tif (start < 0) start = 0;\n\n\t\tif (lengthStr) {\n\t\t\tconst length = parseInt(lengthStr, 10);\n\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t}\n\t\treturn args.slice(start).join(\" \");\n\t});\n\n\t// Pre-compute all args joined (optimization)\n\tconst allArgs = args.join(\" \");\n\n\t// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\n\t// Replace $@ with all args joined (existing syntax)\n\tresult = result.replace(/\\$@/g, allArgs);\n\n\treturn result;\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\t// Strip a Copilot `.prompt.md` suffix as well as a plain `.md`, so both\n\t\t// `greet.prompt.md` (prompt files) and `greet.md` yield the command `greet`.\n\t\tconst name = basename(filePath).replace(/\\.(prompt\\.)?md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\tconst type: PromptTemplateType =\n\t\t\tfrontmatter.type === \"system\" ? \"system\" : frontmatter.type === \"context\" ? \"context\" : \"user\";\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\ttype,\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths: string[];\n\t/** Explicit slash-command paths (files or directories). */\n\tslashCommandPaths?: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults: boolean;\n}\n\nfunction normalizePath(input: string): string {\n\tconst trimmed = input.trim();\n\tif (trimmed === \"~\") return homedir();\n\tif (trimmed.startsWith(\"~/\")) return join(homedir(), trimmed.slice(2));\n\tif (trimmed.startsWith(\"~\")) return join(homedir(), trimmed.slice(1));\n\treturn trimmed;\n}\n\nfunction resolvePromptPath(p: string, cwd: string): string {\n\tconst normalized = normalizePath(p);\n\treturn isAbsolute(normalized) ? normalized : resolve(cwd, normalized);\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = options.cwd;\n\tconst resolvedAgentDir = options.agentDir;\n\tconst promptPaths = options.promptPaths;\n\tconst slashCommandPaths = options.slashCommandPaths ?? [];\n\tconst includeDefaults = options.includeDefaults;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = options.agentDir ? join(options.agentDir, \"prompts\") : resolvedAgentDir;\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\tconst globalSlashCommandsDir = options.agentDir ? join(options.agentDir, \"commands\") : resolvedAgentDir;\n\tconst projectSlashCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"commands\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir) || isUnderPath(resolvedPath, globalSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, globalPromptsDir) ? globalPromptsDir : globalSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir) || isUnderPath(resolvedPath, projectSlashCommandsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: isUnderPath(resolvedPath, projectPromptsDir) ? projectPromptsDir : projectSlashCommandsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\t// Load slash command dirs first so they win over prompt dirs on name collision\n\t\ttemplates.push(...loadTemplatesFromDir(globalSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectSlashCommandsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit slash-command paths (before prompt paths so commands win on collision)\n\tfor (const rawPath of slashCommandPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\t// 4. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePromptPath(rawPath, resolvedCwd);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Result of attempting to expand a prompt template.\n */\nexport interface PromptTemplateExpansion {\n\t/** The expanded text (or original text if not a template). */\n\ttext: string;\n\t/** The matched template, if any. */\n\ttemplate?: PromptTemplate;\n\t/** Parsed arguments. */\n\targs: string[];\n\t/** Raw argument string (everything after the command name). */\n\targsString: string;\n}\n\n/**\n * Try to expand a prompt template if it matches a template name.\n * Returns expansion metadata so callers can handle `type: \"system\"` or `type: \"context\"`.\n */\nexport function tryExpandPromptTemplate(text: string, templates: PromptTemplate[]): PromptTemplateExpansion {\n\tif (!text.startsWith(\"/\")) {\n\t\treturn { text, args: [], argsString: \"\" };\n\t}\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn { text: substituteArgs(template.content, args), template, args, argsString };\n\t}\n\n\treturn { text, args: [], argsString: \"\" };\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\treturn tryExpandPromptTemplate(text, templates).text;\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.2.135",
4
+ "version": "0.2.136",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.2.135",
4
+ "version": "0.2.136",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.2.135",
4
+ "version": "0.2.136",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.2.135",
4
+ "version": "0.2.136",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent",
3
- "version": "0.4.138",
3
+ "version": "0.4.139",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "hoocodeConfig": {
@@ -45,9 +45,9 @@
45
45
  "prepublishOnly": "npm run clean && npm run build"
46
46
  },
47
47
  "dependencies": {
48
- "@kolisachint/hoocode-agent-core": "^0.4.138",
49
- "@kolisachint/hoocode-ai": "^0.4.138",
50
- "@kolisachint/hoocode-tui": "^0.4.138",
48
+ "@kolisachint/hoocode-agent-core": "^0.4.139",
49
+ "@kolisachint/hoocode-ai": "^0.4.139",
50
+ "@kolisachint/hoocode-tui": "^0.4.139",
51
51
  "@silvia-odwyer/photon-node": "^0.3.4",
52
52
  "chalk": "^5.5.0",
53
53
  "cli-highlight": "^2.1.11",