{"version":3,"file":"agent-frontmatter.d.ts","sourceRoot":"","sources":["../../src/core/agent-frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAQ3D,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAE5F,wFAAwF;AACxF,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAQ/C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWhE,CAAC;AA+BF,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,WAAW,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kGAAkG;IAClG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8FAA8F;IAC9F,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAqDD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC7B,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,QAAQ,CAAC,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,kBAAkB,EAAE,CAAA;CAAE,CA0BxD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAK5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,kBAAkB,EAAE,CAAA;CAAE,CA6ItE","sourcesContent":["/**\n * Agent frontmatter: schema, validation, and Claude Code compatibility shim.\n *\n * Agent definitions are Markdown files with YAML frontmatter, mirroring the\n * Claude Code subagent format so `.claude/agents/*.md` files import natively:\n *\n *   ---\n *   name: explore\n *   description: When and why to use this agent (drives auto-delegation).\n *   tools: Read, Grep, Glob, Bash   # optional allowlist; omit = inherit all\n *   model: sonnet                   # optional; sonnet|opus|haiku|inherit|pattern\n *   ---\n *   <system prompt body>\n *\n * The body becomes the subagent system prompt. Validation is non-fatal: we emit\n * diagnostics (warnings) and still load the agent when possible, matching the\n * behavior of skills.ts.\n */\n\nimport { basename } from \"path\";\nimport { parseFrontmatter } from \"../utils/frontmatter.js\";\nimport type { ResourceDiagnostic } from \"./diagnostics.js\";\n\n/** Max name length, aligned with skills. */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length, aligned with skills. */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\n/** Where an agent definition came from. Used for precedence and diagnostics. */\nexport type AgentSource = \"builtin\" | \"user\" | \"project\" | \"claude-user\" | \"claude-project\";\n\n/** Sentinel meaning \"use the parent session's model\" (Claude Code `model: inherit`). */\nexport const MODEL_INHERIT = \"inherit\";\n\n/**\n * The built-in hoocode tools that an agent's `tools` allowlist is normalized\n * against; unknown tools are dropped with a diagnostic. Includes the opt-in web\n * tools (webfetch/websearch) so Claude Code's WebFetch/WebSearch map through.\n */\nexport const HOOCODE_TOOL_NAMES: readonly string[] = [\n\t\"bash\",\n\t\"edit\",\n\t\"read\",\n\t\"SearchCodebase\",\n\t\"webfetch\",\n\t\"websearch\",\n\t\"write\",\n];\n\n/**\n * Canonical registered names of the two opt-in tools. These are case-sensitive\n * identifiers the system prompt and tool gating match exactly, so downstream\n * callers should reference these constants instead of hardcoding the strings\n * (a mis-cased `\"task\"` silently disables the agents/skills prompt sections).\n */\nexport const TASK_TOOL_NAME = \"Task\";\nexport const TODO_WRITE_TOOL_NAME = \"TodoWrite\";\n\n/**\n * D7 — Claude Code compatibility shim.\n *\n * Maps Claude Code tool names (case-insensitive) to their hoocode equivalents.\n * Claude tools without a hoocode counterpart (MultiEdit, Task, TodoWrite,\n * NotebookEdit, MCP tools, ...) are intentionally absent and get dropped during\n * normalization.\n *\n * `grep`/`glob`/`find` all land on `SearchCodebase`, the single code-discovery\n * tool that replaced the old grep/find/ls trio. `ls` has no counterpart at all —\n * directory listing is a shell job now — so it is deliberately absent and gets\n * dropped with a diagnostic rather than silently widening an agent to `bash`.\n *\n * Lookups are lower-cased, so every hoocode tool needs its own lower-case key\n * here to survive normalization; the `searchcodebase` entry is that case-folding\n * for `SearchCodebase`, not a legacy alias. `search` is deliberately absent: the\n * tool was renamed with no backward lookup, so an agent still naming it is\n * dropped with a diagnostic.\n */\nexport const CLAUDE_TOOL_ALIASES: Readonly<Record<string, string>> = {\n\tread: \"read\",\n\twrite: \"write\",\n\tedit: \"edit\",\n\tbash: \"bash\",\n\tsearchcodebase: \"SearchCodebase\",\n\tgrep: \"SearchCodebase\",\n\tglob: \"SearchCodebase\",\n\tfind: \"SearchCodebase\",\n\twebfetch: \"webfetch\",\n\twebsearch: \"websearch\",\n};\n\n/** Raw frontmatter shape before validation/normalization. */\ninterface AgentFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t/** Claude Code uses a comma-separated string; a YAML list is also accepted. */\n\ttools?: string | string[];\n\t/** Tools to subtract from the agent's set (allow + deny), Claude Code compatible. */\n\tdisallowedTools?: string | string[];\n\t/** sonnet | opus | haiku | inherit | a model id/pattern. */\n\tmodel?: string;\n\t/** hoocode extension (not part of the Claude Code format): turn cap. */\n\tmaxTurns?: number;\n\t/** Claude Code extension: run this agent detached (non-blocking) so the parent polls for its result. */\n\tbackground?: boolean;\n\t/**\n\t * hoocode extension: opt a agent into delegating via the Task tool (subject to the\n\t * tree-wide nesting cap). `true` = any subagent type; a comma-separated string or\n\t * YAML list = only those types. Opt-in per agent so the deliberate \"Task is not a\n\t * normal tool\" boundary stays intact for everyone else.\n\t */\n\tdelegate?: boolean | string | string[];\n\t/**\n\t * When true, this agent inherits the parent's full conversation (a fork) instead\n\t * of starting from a fresh context, reusing the parent's prompt cache.\n\t */\n\tfork?: boolean;\n\t[key: string]: unknown;\n}\n\n/** A validated, normalized agent definition. */\nexport interface AgentDefinition {\n\tname: string;\n\tdescription: string;\n\t/**\n\t * Resolved hoocode tool allowlist. `undefined` means \"inherit all parent\n\t * tools\" (Claude Code behavior when `tools` is omitted).\n\t */\n\ttools?: string[];\n\t/** Resolved denylist subtracted from the agent's tool set. */\n\tdisallowedTools?: string[];\n\t/**\n\t * Model alias/pattern, the `inherit` sentinel, or `undefined` for the\n\t * subagent default.\n\t */\n\tmodel?: string;\n\t/** System prompt body (frontmatter stripped). */\n\tprompt: string;\n\t/** Origin of this definition. */\n\tsource: AgentSource;\n\t/** Absolute path of the source file, when loaded from disk. */\n\tfilePath?: string;\n\t/** hoocode extension: optional per-agent turn cap. */\n\tmaxTurns?: number;\n\t/** When true, dispatch is non-blocking: the parent receives a handle and polls for the result. */\n\tbackground?: boolean;\n\t/** When true, this agent may delegate via the Task tool, subject to the nesting cap. */\n\tdelegate?: boolean;\n\t/** Restricts delegation to these subagent types (undefined = any when `delegate` is true). */\n\tdelegateTo?: string[];\n\t/** When true, the agent inherits the parent's full conversation (a fork). */\n\tfork?: boolean;\n}\n\nconst KNOWN_MODEL_ALIASES = new Set([\"sonnet\", \"opus\", \"haiku\", \"inherit\", \"fast\", \"standard\", \"capable\"]);\n\n/** Validate an agent name. Returns warning messages (empty when valid). */\nfunction validateName(name: string): string[] {\n\tconst errors: string[] = [];\n\tif (!name) {\n\t\terrors.push(\"name is required\");\n\t\treturn errors;\n\t}\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(\"name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)\");\n\t}\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(\"name must not start or end with a hyphen\");\n\t}\n\tif (name.includes(\"--\")) {\n\t\terrors.push(\"name must not contain consecutive hyphens\");\n\t}\n\treturn errors;\n}\n\n/** Validate a description. Returns warning messages (empty when valid). */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(\"description is required\");\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\treturn errors;\n}\n\nfunction validateModel(value: string): string[] {\n\tconst trimmed = value.trim();\n\tif (!trimmed) return [];\n\tif (KNOWN_MODEL_ALIASES.has(trimmed)) return [];\n\tif (/^claude-/.test(trimmed)) return [];\n\treturn [\n\t\t`model \"${trimmed}\" is not a recognized alias (sonnet | opus | haiku | inherit | fast | standard | capable) or full model ID; the agent may not load correctly`,\n\t];\n}\n\n/** Split a `tools` frontmatter value (string or list) into raw token names. */\nfunction splitToolsValue(value: string | string[]): string[] {\n\tconst tokens = Array.isArray(value) ? value : value.split(\",\");\n\treturn tokens.map((t) => t.trim()).filter((t) => t.length > 0);\n}\n\n/**\n * Normalize a raw `tools` allowlist into hoocode tool names via the Claude Code\n * alias map. Returns the deduped, resolved list plus diagnostics for any tokens\n * that could not be mapped.\n *\n * Emits a warning when `value` is a YAML list rather than a comma-separated\n * string — the Claude Code standard format is `tools: read, bash` (string).\n */\nexport function normalizeTools(\n\tvalue: string | string[],\n\tfilePath?: string,\n): { tools: string[]; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\tif (Array.isArray(value)) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tmessage:\n\t\t\t\t'tools: use a comma-separated string (\"tools: read, bash\") instead of a YAML list for Claude Code compatibility',\n\t\t\tpath: filePath,\n\t\t});\n\t}\n\tconst resolved: string[] = [];\n\tfor (const raw of splitToolsValue(value)) {\n\t\tconst mapped = CLAUDE_TOOL_ALIASES[raw.toLowerCase()];\n\t\tif (!mapped) {\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `tool \"${raw}\" has no hoocode equivalent and was dropped from the allowlist`,\n\t\t\t\tpath: filePath,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!resolved.includes(mapped)) {\n\t\t\tresolved.push(mapped);\n\t\t}\n\t}\n\treturn { tools: resolved, diagnostics };\n}\n\n/**\n * Normalize a `model` frontmatter value. `inherit` is preserved as a sentinel;\n * any other non-empty string is passed through to the model resolver as-is\n * (so Claude aliases like `sonnet`/`opus`/`haiku` resolve via pattern match).\n */\nexport function normalizeModel(value: string | undefined): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (!trimmed) return undefined;\n\treturn trimmed;\n}\n\n/**\n * Parse and validate a single agent definition from raw Markdown content.\n *\n * `fallbackName` is used when frontmatter omits `name` (e.g. the filename, or\n * the embedded-template key). Returns `agent: null` only when the definition\n * is unusable (missing description). Other problems surface as diagnostics.\n */\nexport function parseAgentDefinition(\n\trawContent: string,\n\toptions: { source: AgentSource; filePath?: string; fallbackName?: string },\n): { agent: AgentDefinition | null; diagnostics: ResourceDiagnostic[] } {\n\tconst { source, filePath } = options;\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tlet frontmatter: AgentFrontmatter;\n\tlet body: string;\n\ttry {\n\t\tconst parsed = parseFrontmatter<AgentFrontmatter>(rawContent);\n\t\tfrontmatter = parsed.frontmatter;\n\t\tbody = parsed.body;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse agent frontmatter\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { agent: null, diagnostics };\n\t}\n\n\tconst fallbackName = options.fallbackName ?? (filePath ? basename(filePath, \".md\") : \"\");\n\tconst name = (frontmatter.name ?? fallbackName).trim();\n\n\tfor (const error of validateName(name)) {\n\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t}\n\n\tconst description = typeof frontmatter.description === \"string\" ? frontmatter.description.trim() : \"\";\n\tfor (const error of validateDescription(description)) {\n\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t}\n\n\t// Description is mandatory: it drives delegation. Without it the agent is unusable.\n\tif (!description) {\n\t\treturn { agent: null, diagnostics };\n\t}\n\t// A usable name is required as the registry key.\n\tif (!name || !/^[a-z0-9-]+$/.test(name)) {\n\t\treturn { agent: null, diagnostics };\n\t}\n\n\tlet tools: string[] | undefined;\n\tif (frontmatter.tools !== undefined) {\n\t\tconst normalized = normalizeTools(frontmatter.tools, filePath);\n\t\tdiagnostics.push(...normalized.diagnostics);\n\t\ttools = normalized.tools;\n\t}\n\n\tlet disallowedTools: string[] | undefined;\n\tif (frontmatter.disallowedTools !== undefined) {\n\t\tconst normalized = normalizeTools(frontmatter.disallowedTools, filePath);\n\t\tdiagnostics.push(...normalized.diagnostics);\n\t\tdisallowedTools = normalized.tools.length > 0 ? normalized.tools : undefined;\n\t}\n\n\tconst model = normalizeModel(frontmatter.model);\n\tif (model !== undefined) {\n\t\tfor (const error of validateModel(model)) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\t}\n\n\tconst maxTurns =\n\t\ttypeof frontmatter.maxTurns === \"number\" && Number.isInteger(frontmatter.maxTurns) && frontmatter.maxTurns > 0\n\t\t\t? frontmatter.maxTurns\n\t\t\t: undefined;\n\n\tlet background: boolean | undefined;\n\tif (frontmatter.background !== undefined) {\n\t\tif (typeof frontmatter.background !== \"boolean\") {\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `background must be a boolean (true or false), got \"${frontmatter.background}\" — field ignored`,\n\t\t\t\tpath: filePath,\n\t\t\t});\n\t\t} else {\n\t\t\tbackground = frontmatter.background === true ? true : undefined;\n\t\t}\n\t}\n\n\t// `delegate` accepts a boolean (delegate to any agent) or a comma-separated\n\t// string / YAML list of agent type names (delegate only to those).\n\tlet fork: boolean | undefined;\n\tif (frontmatter.fork !== undefined) {\n\t\tif (typeof frontmatter.fork !== \"boolean\") {\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `fork must be a boolean (true or false), got \"${frontmatter.fork}\" — field ignored`,\n\t\t\t\tpath: filePath,\n\t\t\t});\n\t\t} else {\n\t\t\tfork = frontmatter.fork === true ? true : undefined;\n\t\t}\n\t}\n\n\tlet delegate: boolean | undefined;\n\tlet delegateTo: string[] | undefined;\n\tif (frontmatter.delegate !== undefined) {\n\t\tconst value = frontmatter.delegate;\n\t\tif (value === true) {\n\t\t\tdelegate = true;\n\t\t} else if (value === false) {\n\t\t\tdelegate = undefined;\n\t\t} else if (typeof value === \"string\" || Array.isArray(value)) {\n\t\t\tconst names = (Array.isArray(value) ? value.join(\",\") : value)\n\t\t\t\t.split(\",\")\n\t\t\t\t.map((s) => s.trim().toLowerCase())\n\t\t\t\t.filter((s) => /^[a-z0-9-]+$/.test(s));\n\t\t\tif (names.length > 0) {\n\t\t\t\tdelegate = true;\n\t\t\t\tdelegateTo = [...new Set(names)];\n\t\t\t} else {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"warning\",\n\t\t\t\t\tmessage: `delegate list \"${value}\" contained no valid agent names — field ignored`,\n\t\t\t\t\tpath: filePath,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tmessage: `delegate must be a boolean or a list of agent names, got \"${value}\" — field ignored`,\n\t\t\t\tpath: filePath,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tagent: {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\ttools,\n\t\t\tdisallowedTools,\n\t\t\tmodel,\n\t\t\tprompt: body.trim(),\n\t\t\tsource,\n\t\t\tfilePath,\n\t\t\tmaxTurns,\n\t\t\tbackground,\n\t\t\tdelegate,\n\t\t\tdelegateTo,\n\t\t\tfork,\n\t\t},\n\t\tdiagnostics,\n\t};\n}\n"]}