export declare const searchServiceTemplate = "import MiniSearch from \"minisearch\";\nimport { listDocs } from \"@/services/mcp/tools\";\n\ninterface IndexedDoc {\n id: string;\n slug: string;\n title: string;\n content: string;\n}\n\nexport interface SearchHit {\n slug: string;\n snippet: string;\n}\n\nlet index: MiniSearch | null = null;\nlet docs: IndexedDoc[] = [];\nlet buildPromise: Promise | null = null;\n\nconst CONTEXT_BEFORE = 60;\nconst CONTEXT_AFTER = 90;\n\n/**\n * Convert raw MDX (as extracted from the generated page templates) into plain,\n * human-readable text for search snippets: undoes the template-literal escaping\n * and strips common Markdown syntax so inline code shows as Test rather than a\n * backtick-wrapped or backslash-escaped fragment.\n */\nfunction toPlainText(text: string): string {\n return (\n text\n // Undo the template-literal escaping from the generated page files so\n // escaped backticks, dollar-braces and backslashes read as plain text.\n .replace(/\\\\([\\s\\S])/g, \"$1\")\n // Images and links -> visible text only.\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n // Bold markers.\n .replace(/\\*\\*([\\s\\S]*?)\\*\\*/g, \"$1\")\n // Line-leading markers: headings, blockquotes, list bullets.\n .replace(/^\\s{0,3}#{1,6}\\s+/gm, \"\")\n .replace(/^\\s{0,3}>\\s?/gm, \"\")\n .replace(/^\\s{0,3}[-*+]\\s+/gm, \"\")\n .replace(/^\\s{0,3}\\d+\\.\\s+/gm, \"\")\n // Inline code / escaped backticks -> plain text.\n .replace(/`/g, \"\")\n );\n}\n\nasync function ensureIndex(): Promise> {\n if (index) return index;\n if (buildPromise) {\n await buildPromise;\n return index!;\n }\n\n buildPromise = (async () => {\n const resources = await listDocs();\n\n docs = resources.map((doc) => {\n // Strip the leading \"app/\", the \"/page.ext\" suffix, and any Next.js\n // route-group segments like \"(site)\" which never appear in URLs. This\n // must match the URL slug produced by toDocPath() in mcp/tools.ts and\n // the nav slugs in the layout, otherwise content hits fail to map back\n // to a page on the client and get silently dropped.\n const slug = doc.path\n .replace(/^app\\//, \"\")\n .replace(/\\/page\\.\\w+$/, \"\")\n .split(\"/\")\n .filter((seg) => !(seg.startsWith(\"(\") && seg.endsWith(\")\")))\n .join(\"/\");\n const cleanContent = toPlainText(doc.content.replace(/\\r\\n/g, \"\\n\"))\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .slice(0, 200_000);\n return {\n id: slug || \"__index__\",\n slug,\n title: doc.name,\n content: cleanContent,\n };\n });\n\n index = new MiniSearch({\n fields: [\"title\", \"content\"],\n storeFields: [\"slug\", \"title\", \"content\"],\n searchOptions: {\n boost: { title: 3 },\n fuzzy: 0.2,\n prefix: true,\n },\n });\n\n index.addAll(docs);\n })();\n\n await buildPromise;\n return index!;\n}\n\nfunction extractSnippet(content: string, query: string): string {\n const lower = content.toLowerCase();\n const qLower = query.toLowerCase();\n const matchIdx = lower.indexOf(qLower);\n\n if (matchIdx === -1) {\n // Fuzzy match - no exact substring. Return start of content.\n const end = Math.min(content.length, CONTEXT_BEFORE + CONTEXT_AFTER);\n const raw = content.slice(0, end).trim();\n return raw.length < content.length ? raw + \"...\" : raw;\n }\n\n let start = Math.max(0, matchIdx - CONTEXT_BEFORE);\n let end = Math.min(content.length, matchIdx + query.length + CONTEXT_AFTER);\n\n // Align to word boundaries\n if (start > 0) {\n const space = content.indexOf(\" \", start);\n if (space !== -1 && space < matchIdx) start = space + 1;\n }\n if (end < content.length) {\n const space = content.lastIndexOf(\" \", end);\n if (space > matchIdx + query.length) end = space;\n }\n\n const prefix = start > 0 ? \"...\" : \"\";\n const suffix = end < content.length ? \"...\" : \"\";\n const snippet = content.slice(start, end).replace(/\\n+/g, \" \").trim();\n\n return prefix + snippet + suffix;\n}\n\nexport async function searchContent(\n query: string,\n limit = 10,\n): Promise {\n const q = query.trim();\n if (!q) return [];\n\n const idx = await ensureIndex();\n const results = idx.search(q);\n\n return results.slice(0, limit).map((result) => {\n const doc = docs.find((d) => d.id === result.id);\n const content = doc?.content || \"\";\n return {\n slug: doc?.slug || \"\",\n snippet: extractSnippet(content, q),\n };\n });\n}\n";