{"version":3,"file":"prompt-templates.d.ts","sourceRoot":"","sources":["../../src/harness/prompt-templates.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAiB,KAAK,cAAc,EAAwB,MAAM,YAAY,CAAC;AAEzG,MAAM,MAAM,4BAA4B,GAAG,kBAAkB,GAAG,aAAa,GAAG,aAAa,GAAG,cAAc,CAAC;AAE/G,uDAAuD;AACvD,MAAM,WAAW,wBAAwB;IACxC,gEAAgE;IAChE,IAAI,EAAE,SAAS,CAAC;IAChB,8BAA8B;IAC9B,IAAI,EAAE,4BAA4B,CAAC;IACnC,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACb;AAQD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,YAAY,EACjB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GACtB,OAAO,CAAC;IAAE,eAAe,EAAE,cAAc,EAAE,CAAC;IAAC,WAAW,EAAE,wBAAwB,EAAE,CAAA;CAAE,CAAC,CA6BzF;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,eAAe,SAAS,cAAc,GAAG,cAAc,EAChH,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,EAChD,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,KAAK,eAAe,GACtF,OAAO,CAAC;IACV,eAAe,EAAE,KAAK,CAAC;QAAE,cAAc,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,WAAW,EAAE,KAAK,CAAC,wBAAwB,GAAG;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACnE,CAAC,CAgBD;AAiID,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAuB7D;AAED,uHAAuH;AACvH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAatE;AAED,qEAAqE;AACrE,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,MAAM,CAEpG","sourcesContent":["import { parse } from \"yaml\";\nimport { type ExecutionEnv, type FileInfo, type PromptTemplate, type Result, toError } from \"./types.ts\";\n\nexport type PromptTemplateDiagnosticCode = \"file_info_failed\" | \"list_failed\" | \"read_failed\" | \"parse_failed\";\n\n/** Warning produced while loading prompt templates. */\nexport interface PromptTemplateDiagnostic {\n\t/** Diagnostic severity. Currently only warnings are emitted. */\n\ttype: \"warning\";\n\t/** Stable diagnostic code. */\n\tcode: PromptTemplateDiagnosticCode;\n\t/** Human-readable diagnostic message. */\n\tmessage: string;\n\t/** Path associated with the diagnostic. */\n\tpath: string;\n}\n\ninterface PromptTemplateFrontmatter {\n\tdescription?: string;\n\t\"argument-hint\"?: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Load prompt templates from one or more paths.\n *\n * Directory inputs load direct `.md` children non-recursively. File inputs load explicit `.md` files. Missing paths and\n * non-markdown files are skipped. Read and parse failures are returned as diagnostics.\n */\nexport async function loadPromptTemplates(\n\tenv: ExecutionEnv,\n\tpaths: string | string[],\n): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst promptTemplates: PromptTemplate[] = [];\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tfor (const path of Array.isArray(paths) ? paths : [paths]) {\n\t\tconst infoResult = await env.fileInfo(path);\n\t\tif (!infoResult.ok) {\n\t\t\tif (infoResult.error.code !== \"not_found\") {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"warning\",\n\t\t\t\t\tcode: \"file_info_failed\",\n\t\t\t\t\tmessage: infoResult.error.message,\n\t\t\t\t\tpath,\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst info = infoResult.value;\n\t\tconst kind = await resolveKind(env, info, diagnostics);\n\t\tif (kind === \"directory\") {\n\t\t\tconst result = await loadTemplatesFromDir(env, info.path);\n\t\t\tpromptTemplates.push(...result.promptTemplates);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t} else if (kind === \"file\" && info.name.endsWith(\".md\")) {\n\t\t\tconst result = await loadTemplateFromFile(env, info.path);\n\t\t\tif (result.promptTemplate) promptTemplates.push(result.promptTemplate);\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\n/**\n * Load prompt templates from source-tagged paths.\n *\n * Source values are preserved exactly and attached to every loaded prompt template and diagnostic. The agent package does\n * not interpret source values; applications define their own provenance shape.\n */\nexport async function loadSourcedPromptTemplates<TSource, TPromptTemplate extends PromptTemplate = PromptTemplate>(\n\tenv: ExecutionEnv,\n\tinputs: Array<{ path: string; source: TSource }>,\n\tmapPromptTemplate?: (promptTemplate: PromptTemplate, source: TSource) => TPromptTemplate,\n): Promise<{\n\tpromptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }>;\n\tdiagnostics: Array<PromptTemplateDiagnostic & { source: TSource }>;\n}> {\n\tconst promptTemplates: Array<{ promptTemplate: TPromptTemplate; source: TSource }> = [];\n\tconst diagnostics: Array<PromptTemplateDiagnostic & { source: TSource }> = [];\n\tfor (const input of inputs) {\n\t\tconst result = await loadPromptTemplates(env, input.path);\n\t\tfor (const promptTemplate of result.promptTemplates) {\n\t\t\tpromptTemplates.push({\n\t\t\t\tpromptTemplate: mapPromptTemplate\n\t\t\t\t\t? mapPromptTemplate(promptTemplate, input.source)\n\t\t\t\t\t: (promptTemplate as TPromptTemplate),\n\t\t\t\tsource: input.source,\n\t\t\t});\n\t\t}\n\t\tfor (const diagnostic of result.diagnostics) diagnostics.push({ ...diagnostic, source: input.source });\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\nasync function loadTemplatesFromDir(\n\tenv: ExecutionEnv,\n\tdir: string,\n): Promise<{ promptTemplates: PromptTemplate[]; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst promptTemplates: PromptTemplate[] = [];\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tconst entriesResult = await env.listDir(dir);\n\tif (!entriesResult.ok) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tcode: \"list_failed\",\n\t\t\tmessage: entriesResult.error.message,\n\t\t\tpath: dir,\n\t\t});\n\t\treturn { promptTemplates, diagnostics };\n\t}\n\tconst entries = entriesResult.value;\n\n\tfor (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n\t\tconst kind = await resolveKind(env, entry, diagnostics);\n\t\tif (kind !== \"file\" || !entry.name.endsWith(\".md\")) continue;\n\t\tconst result = await loadTemplateFromFile(env, entry.path);\n\t\tif (result.promptTemplate) promptTemplates.push(result.promptTemplate);\n\t\tdiagnostics.push(...result.diagnostics);\n\t}\n\treturn { promptTemplates, diagnostics };\n}\n\nasync function loadTemplateFromFile(\n\tenv: ExecutionEnv,\n\tfilePath: string,\n): Promise<{ promptTemplate: PromptTemplate | null; diagnostics: PromptTemplateDiagnostic[] }> {\n\tconst diagnostics: PromptTemplateDiagnostic[] = [];\n\tconst rawContent = await env.readTextFile(filePath);\n\tif (!rawContent.ok) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tcode: \"read_failed\",\n\t\t\tmessage: rawContent.error.message,\n\t\t\tpath: filePath,\n\t\t});\n\t\treturn { promptTemplate: null, diagnostics };\n\t}\n\n\tconst parsed = parseFrontmatter<PromptTemplateFrontmatter>(rawContent.value);\n\tif (!parsed.ok) {\n\t\tdiagnostics.push({\n\t\t\ttype: \"warning\",\n\t\t\tcode: \"parse_failed\",\n\t\t\tmessage: parsed.error.message,\n\t\t\tpath: filePath,\n\t\t});\n\t\treturn { promptTemplate: null, diagnostics };\n\t}\n\n\tconst { frontmatter, body } = parsed.value;\n\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\tlet description = typeof frontmatter.description === \"string\" ? frontmatter.description : \"\";\n\tif (!description && firstLine) {\n\t\tdescription = firstLine.slice(0, 60);\n\t\tif (firstLine.length > 60) description += \"...\";\n\t}\n\treturn {\n\t\tpromptTemplate: {\n\t\t\tname: basenameEnvPath(filePath).replace(/\\.md$/i, \"\"),\n\t\t\tdescription,\n\t\t\tcontent: body,\n\t\t},\n\t\tdiagnostics,\n\t};\n}\n\nasync function resolveKind(\n\tenv: ExecutionEnv,\n\tinfo: FileInfo,\n\tdiagnostics: PromptTemplateDiagnostic[],\n): Promise<\"file\" | \"directory\" | undefined> {\n\tif (info.kind === \"file\" || info.kind === \"directory\") return info.kind;\n\tconst canonicalPath = await env.canonicalPath(info.path);\n\tif (!canonicalPath.ok) {\n\t\tif (canonicalPath.error.code !== \"not_found\") {\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tcode: \"file_info_failed\",\n\t\t\t\tmessage: canonicalPath.error.message,\n\t\t\t\tpath: info.path,\n\t\t\t});\n\t\t}\n\t\treturn undefined;\n\t}\n\tconst target = await env.fileInfo(canonicalPath.value);\n\tif (!target.ok) {\n\t\tif (target.error.code !== \"not_found\") {\n\t\t\tdiagnostics.push({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tcode: \"file_info_failed\",\n\t\t\t\tmessage: target.error.message,\n\t\t\t\tpath: info.path,\n\t\t\t});\n\t\t}\n\t\treturn undefined;\n\t}\n\treturn target.value.kind === \"file\" || target.value.kind === \"directory\" ? target.value.kind : undefined;\n}\n\nfunction parseFrontmatter<T extends Record<string, unknown>>(\n\tcontent: string,\n): Result<{ frontmatter: T; body: string }, Error> {\n\ttry {\n\t\tconst normalized = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\t\tif (!normalized.startsWith(\"---\")) return { ok: true, value: { frontmatter: {} as T, body: normalized } };\n\t\tconst endIndex = normalized.indexOf(\"\\n---\", 3);\n\t\tif (endIndex === -1) return { ok: true, value: { frontmatter: {} as T, body: normalized } };\n\t\tconst yamlString = normalized.slice(4, endIndex);\n\t\tconst body = normalized.slice(endIndex + 4).trim();\n\t\treturn { ok: true, value: { frontmatter: (parse(yamlString) ?? {}) as T, body } };\n\t} catch (error) {\n\t\treturn { ok: false, error: toError(error) };\n\t}\n}\n\nfunction basenameEnvPath(path: string): string {\n\tconst normalized = path.replace(/\\/+$/, \"\");\n\tconst slashIndex = normalized.lastIndexOf(\"/\");\n\treturn slashIndex === -1 ? normalized : normalized.slice(slashIndex + 1);\n}\n\n/** Parse an argument string using simple shell-style single and double quotes. */\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\t\tif (inQuote) {\n\t\t\tif (char === inQuote) inQuote = null;\n\t\t\telse current += char;\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\tif (current) args.push(current);\n\treturn args;\n}\n\n/** Substitute prompt template placeholders (`$1`, `$@`, `$ARGUMENTS`, `${@:N}`, `${@:N:L}`) with command arguments. */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\tresult = result.replace(/\\$(\\d+)/g, (_, num: string) => args[parseInt(num, 10) - 1] ?? \"\");\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr: string, lengthStr?: string) => {\n\t\tlet start = parseInt(startStr, 10) - 1;\n\t\tif (start < 0) start = 0;\n\t\tif (lengthStr) return args.slice(start, start + parseInt(lengthStr, 10)).join(\" \");\n\t\treturn args.slice(start).join(\" \");\n\t});\n\tconst allArgs = args.join(\" \");\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\tresult = result.replace(/\\$@/g, allArgs);\n\treturn result;\n}\n\n/** Format a prompt template invocation with positional arguments. */\nexport function formatPromptTemplateInvocation(template: PromptTemplate, args: string[] = []): string {\n\treturn substituteArgs(template.content, args);\n}\n"]}