@orquentra/codebuddy 2.6.0 → 2.7.0

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.
Files changed (117) hide show
  1. package/CLAUDE_CODEX.md +6 -4
  2. package/WORKFLOW.md +5 -5
  3. package/dist/{chunk-U4HAJLIB.js → chunk-3QGW53GD.js} +16 -3
  4. package/dist/chunk-3QGW53GD.js.map +1 -0
  5. package/dist/{chunk-L5AYZC3F.js → chunk-5EDGAZQN.js} +438 -68
  6. package/dist/chunk-5EDGAZQN.js.map +1 -0
  7. package/dist/chunk-5UH3BEQR.js +32 -0
  8. package/dist/chunk-5UH3BEQR.js.map +1 -0
  9. package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
  10. package/dist/{chunk-UN57FKQX.js → chunk-AK2UAE32.js} +152 -862
  11. package/dist/chunk-AK2UAE32.js.map +1 -0
  12. package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
  13. package/dist/chunk-AL6VBTNA.js.map +1 -0
  14. package/dist/{chunk-WHZSVY2L.js → chunk-AQHPEJR6.js} +94 -23
  15. package/dist/chunk-AQHPEJR6.js.map +1 -0
  16. package/dist/{chunk-PCE4STAY.js → chunk-BQM7FJ7D.js} +6 -6
  17. package/dist/chunk-BQM7FJ7D.js.map +1 -0
  18. package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
  19. package/dist/chunk-CAEPHLHC.js.map +1 -0
  20. package/dist/chunk-CG5B3LYG.js +66 -0
  21. package/dist/chunk-CG5B3LYG.js.map +1 -0
  22. package/dist/chunk-CSRD5OQA.js +79 -0
  23. package/dist/chunk-CSRD5OQA.js.map +1 -0
  24. package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
  25. package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
  26. package/dist/chunk-EEUFU7KV.js +1634 -0
  27. package/dist/chunk-EEUFU7KV.js.map +1 -0
  28. package/dist/{chunk-UFZD3JAA.js → chunk-ESZXCZIN.js} +3 -3
  29. package/dist/{chunk-PYLH4MH5.js → chunk-FJOTP632.js} +1 -1
  30. package/dist/{chunk-SYWJG5NA.js → chunk-GKNYMNFM.js} +17 -7
  31. package/dist/{chunk-SYWJG5NA.js.map → chunk-GKNYMNFM.js.map} +1 -1
  32. package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
  33. package/dist/chunk-HPOR6YXA.js.map +1 -0
  34. package/dist/chunk-JUAFCLRB.js +1014 -0
  35. package/dist/chunk-JUAFCLRB.js.map +1 -0
  36. package/dist/{chunk-776EQXDV.js → chunk-KNR2P2UY.js} +110 -83
  37. package/dist/chunk-KNR2P2UY.js.map +1 -0
  38. package/dist/{chunk-N34XHWBA.js → chunk-LCSBL5TT.js} +24 -11
  39. package/dist/chunk-LCSBL5TT.js.map +1 -0
  40. package/dist/{chunk-QDBDYCM2.js → chunk-MSUAVR3R.js} +76 -11
  41. package/dist/chunk-MSUAVR3R.js.map +1 -0
  42. package/dist/{chunk-6MKEIDX5.js → chunk-SFMIMLXR.js} +2 -2
  43. package/dist/chunk-TGFTKWOR.js +16 -0
  44. package/dist/chunk-TGFTKWOR.js.map +1 -0
  45. package/dist/chunk-TUR6YKEE.js +55 -0
  46. package/dist/chunk-TUR6YKEE.js.map +1 -0
  47. package/dist/chunk-Y7D7PUBE.js +321 -0
  48. package/dist/chunk-Y7D7PUBE.js.map +1 -0
  49. package/dist/{chunk-VAKBJV26.js → chunk-YJIA2MFL.js} +918 -2238
  50. package/dist/chunk-YJIA2MFL.js.map +1 -0
  51. package/dist/cli/index.js +14 -3
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
  54. package/dist/{codebuddy-DM-i0Fu5.d.ts → codebuddy-DO7ZhoWn.d.ts} +37 -3
  55. package/dist/config-GEZFLXK6.js +12 -0
  56. package/dist/docker-autostart-KDME7TVC.js +12 -0
  57. package/dist/index.d.ts +48 -3
  58. package/dist/index.js +32 -25
  59. package/dist/langgraph/index.d.ts +1 -1
  60. package/dist/lean-command-CWJAVQ5G.js +18 -0
  61. package/dist/local-ST4WMRCG.js +17 -0
  62. package/dist/{main-HFHLGID4.js → main-AHNGHC4L.js} +327 -329
  63. package/dist/main-AHNGHC4L.js.map +1 -0
  64. package/dist/{memory-4RGVLPKG.js → memory-TGHVTBME.js} +9 -8
  65. package/dist/memory-TGHVTBME.js.map +1 -0
  66. package/dist/operations-7BTAAUHF.js +26 -0
  67. package/dist/postgres-docker-QQZQ2ABK.js +16 -0
  68. package/dist/pretool-command-3SXQSMAO.js +13 -0
  69. package/dist/{rerank-3P3C2UHN.js → rerank-IQAEWLQR.js} +6 -5
  70. package/dist/rerank-IQAEWLQR.js.map +1 -0
  71. package/dist/runtime-E7JQZ2BZ.js +46 -0
  72. package/dist/runtime-E7JQZ2BZ.js.map +1 -0
  73. package/dist/stdio-HOV4P3DQ.js +12 -0
  74. package/dist/stdio-HOV4P3DQ.js.map +1 -0
  75. package/dist/summaries-XV3JGGCC.js +20 -0
  76. package/dist/summaries-XV3JGGCC.js.map +1 -0
  77. package/dist/{zvec-MAPSJQSR.js → zvec-KP4KNB7Y.js} +10 -3
  78. package/dist/zvec-KP4KNB7Y.js.map +1 -0
  79. package/package.json +7 -10
  80. package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
  81. package/src/db/migrations/meta/0006_snapshot.json +1258 -0
  82. package/src/db/migrations/meta/_journal.json +7 -0
  83. package/dist/chunk-776EQXDV.js.map +0 -1
  84. package/dist/chunk-GDJH7XJC.js.map +0 -1
  85. package/dist/chunk-JPHJBCZB.js.map +0 -1
  86. package/dist/chunk-L5AYZC3F.js.map +0 -1
  87. package/dist/chunk-N34XHWBA.js.map +0 -1
  88. package/dist/chunk-PCE4STAY.js.map +0 -1
  89. package/dist/chunk-PQK3GKZH.js +0 -98
  90. package/dist/chunk-PQK3GKZH.js.map +0 -1
  91. package/dist/chunk-QDBDYCM2.js.map +0 -1
  92. package/dist/chunk-U4HAJLIB.js.map +0 -1
  93. package/dist/chunk-UN57FKQX.js.map +0 -1
  94. package/dist/chunk-VAKBJV26.js.map +0 -1
  95. package/dist/chunk-WHZSVY2L.js.map +0 -1
  96. package/dist/chunk-XBJQKCLU.js.map +0 -1
  97. package/dist/config-KFEHLV6W.js +0 -11
  98. package/dist/lean-command-C5YPFKHP.js +0 -15
  99. package/dist/local-OTOY56B4.js +0 -13
  100. package/dist/main-HFHLGID4.js.map +0 -1
  101. package/dist/memory-4RGVLPKG.js.map +0 -1
  102. package/dist/operations-LOBAQRLO.js +0 -24
  103. package/dist/pretool-command-MQQ2UNQM.js +0 -10
  104. package/dist/rerank-3P3C2UHN.js.map +0 -1
  105. package/dist/summaries-ZBV5IN7Y.js +0 -18
  106. /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
  107. /package/dist/{chunk-UFZD3JAA.js.map → chunk-ESZXCZIN.js.map} +0 -0
  108. /package/dist/{chunk-PYLH4MH5.js.map → chunk-FJOTP632.js.map} +0 -0
  109. /package/dist/{chunk-6MKEIDX5.js.map → chunk-SFMIMLXR.js.map} +0 -0
  110. /package/dist/{cloud-7WM3YLI4.js.map → cloud-LXZUR3U2.js.map} +0 -0
  111. /package/dist/{config-KFEHLV6W.js.map → config-GEZFLXK6.js.map} +0 -0
  112. /package/dist/{lean-command-C5YPFKHP.js.map → docker-autostart-KDME7TVC.js.map} +0 -0
  113. /package/dist/{local-OTOY56B4.js.map → lean-command-CWJAVQ5G.js.map} +0 -0
  114. /package/dist/{operations-LOBAQRLO.js.map → local-ST4WMRCG.js.map} +0 -0
  115. /package/dist/{pretool-command-MQQ2UNQM.js.map → operations-7BTAAUHF.js.map} +0 -0
  116. /package/dist/{summaries-ZBV5IN7Y.js.map → postgres-docker-QQZQ2ABK.js.map} +0 -0
  117. /package/dist/{zvec-MAPSJQSR.js.map → pretool-command-3SXQSMAO.js.map} +0 -0
@@ -43,6 +43,13 @@
43
43
  "when": 1792733194761,
44
44
  "tag": "0005_fact_propositions",
45
45
  "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "7",
50
+ "when": 1793842061726,
51
+ "tag": "0006_fact_idempotency_key",
52
+ "breakpoints": true
46
53
  }
47
54
  ]
48
55
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/markdown-store-fs.ts","../src/core/config-file.ts"],"sourcesContent":["import { lstat, open, rename } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\n\n/**\n * Filesystem helpers shared by the Markdown-backed stores\n * (`MemoryFileStore`, `PlanFileStore`). These enforce the security\n * invariants that keep `.codebuddy/` writes inside the repository root:\n * no path traversal, no symlinked path components, and crash-safe atomic\n * writes. Keep this module small and audited; both stores depend on it.\n */\n\n/**\n * Resolve `path` and assert it stays inside `root`. Returns the absolute\n * path. Throws if the resolved path escapes the root.\n */\nexport function resolveWithinRoot(root: string, path: string): string {\n const absoluteRoot = resolve(root);\n const absolute = resolve(path);\n const relativePath = relative(absoluteRoot, absolute);\n if (relativePath === \"..\" || relativePath.startsWith(`..${sep}`) || relativePath === \"\") {\n throw new Error(`Path escapes repository root: ${path}`);\n }\n return absolute;\n}\n\n/**\n * Walk each path component between `root` and `target` and refuse if any\n * component is a symbolic link. Prevents a planted symlink from\n * redirecting a write outside the repository. Missing components are\n * fine — the caller creates them.\n */\nexport async function assertNoSymlinkBetween(root: string, target: string): Promise<void> {\n const absoluteRoot = resolve(root);\n const absoluteTarget = resolve(target);\n const relativeTarget = relative(absoluteRoot, absoluteTarget);\n if (relativeTarget === \"..\" || relativeTarget.startsWith(`..${sep}`)) {\n throw new Error(`Path escapes repository root: ${target}`);\n }\n\n let current = absoluteRoot;\n const parts = relativeTarget.split(sep).filter(Boolean);\n for (const part of parts) {\n current = join(current, part);\n try {\n const info = await lstat(current);\n if (info.isSymbolicLink()) {\n throw new Error(`Refusing to use symlinked path: ${current}`);\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw error;\n }\n }\n}\n\n/**\n * Write `document` to `path` atomically: write to a unique temp file with\n * an exclusive (`wx`) create, fsync, then rename into place. A crash mid-\n * write leaves either the old file or nothing — never a partial file.\n *\n * `mode` sets permissions on NEWLY created files (an existing file keeps its\n * mode, matching rename semantics). Secrets-bearing configs should use 0o600.\n */\nexport async function writeAtomic(\n path: string,\n document: string,\n options?: { mode?: number },\n): Promise<void> {\n const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;\n const handle = await open(temporaryPath, \"wx\", options?.mode ?? 0o600);\n try {\n await handle.writeFile(document, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n await rename(temporaryPath, path);\n}\n\n/**\n * Split a Markdown document with leading YAML front matter into its\n * front-matter text and body. Throws on malformed or unterminated\n * front matter so hand-edited files fail loudly rather than silently\n * losing fields.\n */\nexport function parseFrontMatter(document: string): { frontMatter: string; content: string } {\n if (!document.startsWith(\"---\\n\")) {\n throw new Error(\"Markdown file must start with YAML front matter.\");\n }\n const closingIndex = document.indexOf(\"\\n---\\n\", 4);\n if (closingIndex === -1) {\n throw new Error(\"Markdown file has unterminated YAML front matter.\");\n }\n return {\n frontMatter: document.slice(4, closingIndex),\n content: document.slice(closingIndex + 5),\n };\n}\n\n/**\n * Compose a Markdown document from serialized front-matter YAML and a\n * body. Ensures exactly one trailing newline on the body so files are\n * stable across rewrites (minimal git diffs).\n */\nexport function composeMarkdown(frontMatterYaml: string, body: string): string {\n const trimmedFrontMatter = frontMatterYaml.trimEnd();\n const content = body.endsWith(\"\\n\") ? body : `${body}\\n`;\n return `---\\n${trimmedFrontMatter}\\n---\\n${content}`;\n}\n","import { constants } from \"node:fs\";\nimport { access, chmod, mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { z } from \"zod\";\nimport { writeAtomic } from \"./markdown-store-fs.js\";\nimport type { CodeBuddyConfig } from \"./types.js\";\n\nexport const CONFIG_DIR = \".codebuddy\";\nexport const CONFIG_FILE = \"config.json\";\nexport const POLICY_FILE = \"policies.yaml\";\n\nconst PROJECT_GITIGNORE = [\n \"# CodeBuddy local-only files\",\n \"/config.json\",\n \"/cache/\",\n \"/memory/review/\",\n \"/memory/review-archive/\",\n \"/plans/.locks/\",\n \"/memory/.locks/\",\n \"\",\n \"# Keep these reviewable project artifacts shareable by default:\",\n \"# /memory/facts/*.md\",\n \"# /memory/summaries/*.md\",\n \"# /plans/*.md\",\n \"# If your project memory is private, ignore /memory/ in the repo root .gitignore.\",\n \"\",\n].join(\"\\n\");\n\nconst DEFAULT_POLICY_FILE = [\n \"rules:\",\n \" - id: auth-sensitive\",\n ' pattern: \"src/auth/**\"',\n ' message: \"Auth changes need careful review.\"',\n \" weight: 70\",\n \"\",\n].join(\"\\n\");\n\n/**\n * Plan policy controls how strongly CodeBuddy nudges agents toward writing\n * a plan before substantive work:\n * - off: plans are never suggested or required.\n * - suggest: agents are reminded to plan, but small tasks stay lightweight (default).\n * - required: agents should refuse substantive changes without an active plan.\n */\nexport const PLAN_POLICIES = [\"off\", \"suggest\", \"required\"] as const;\nexport type PlanPolicy = (typeof PLAN_POLICIES)[number];\n\nconst fileSchema = z.object({\n postgresUrl: z.string().min(1).optional(),\n namespace: z.string().min(1).optional(),\n tokenBudget: z.number().int().positive().optional(),\n provider: z\n .object({\n type: z.enum([\"local\", \"huggingface\", \"openrouter\"]).default(\"local\"),\n apiKey: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n relevanceFloor: z.number().min(-1).max(1).optional(),\n })\n .optional(),\n /** Optional cross-encoder that orders recalled facts (off by default). */\n rerank: z\n .object({\n provider: z.enum([\"off\", \"local\", \"huggingface\", \"openrouter\"]).default(\"off\"),\n model: z.string().min(1).optional(),\n })\n .optional(),\n plan: z\n .object({\n policy: z.enum(PLAN_POLICIES).default(\"suggest\"),\n })\n .optional(),\n graphify: z\n .object({\n enabled: z.boolean(),\n graphPath: z.string().min(1).optional(),\n })\n .optional(),\n /**\n * Optional hosted small-model layer (bring your own key). Keys are read from\n * the environment only, never from this file (`src/llm/config.ts`).\n */\n llm: z\n .object({\n enabled: z.boolean(),\n provider: z.enum([\"groq\", \"openrouter\"]).optional(),\n model: z.string().min(1).optional(),\n fallback: z\n .object({ provider: z.enum([\"groq\", \"openrouter\"]), model: z.string().min(1) })\n .optional(),\n timeoutMs: z.number().int().min(100).max(5_000).optional(),\n dailyTokenBudget: z.number().int().positive().optional(),\n features: z\n .object({\n summaries: z.boolean().optional(),\n rerank: z.boolean().optional(),\n memory: z.boolean().optional(),\n })\n .optional(),\n })\n .optional(),\n /** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */\n zvec: z\n .object({\n enabled: z.boolean(),\n binary: z.string().min(1).optional(),\n timeoutMs: z.number().int().positive().max(10_000).optional(),\n })\n .optional(),\n});\n\nexport type CodeBuddyFileConfig = z.infer<typeof fileSchema>;\n\nexport type ConfigPermissionReport = {\n path: string;\n exists: boolean;\n mode: string | null;\n ok: boolean;\n warning?: string;\n};\n\nexport type ProjectScaffoldReport = {\n codebuddyDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memoryFactsDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memorySummariesDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n plansDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n gitignore: { path: string; exists: boolean; ok: boolean; warning?: string };\n policies: { path: string; exists: boolean; ok: boolean; warning?: string };\n};\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, CONFIG_DIR, CONFIG_FILE);\n}\n\nexport async function initProjectScaffold(cwd = process.cwd()): Promise<{\n codebuddyDir: string;\n gitignorePath: string;\n policyPath: string;\n}> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n await mkdir(join(codebuddyDir, \"memory\", \"facts\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"memory\", \"summaries\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"plans\"), { recursive: true, mode: 0o700 });\n\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n try {\n await access(gitignorePath, constants.F_OK);\n let gitignore = await readFile(gitignorePath, \"utf8\");\n const lines = new Set(gitignore.split(/\\r?\\n/));\n const missing = [\"/memory/review/\", \"/memory/review-archive/\"].filter(\n (line) => !lines.has(line),\n );\n if (missing.length > 0) {\n gitignore = `${gitignore.trimEnd()}\\n${missing.join(\"\\n\")}\\n`;\n await writeFile(gitignorePath, gitignore, { mode: 0o644 });\n }\n } catch {\n await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 0o644 });\n }\n\n const policyPath = join(codebuddyDir, POLICY_FILE);\n try {\n await access(policyPath, constants.F_OK);\n } catch {\n await writeFile(policyPath, DEFAULT_POLICY_FILE, { mode: 0o644 });\n }\n\n return { codebuddyDir, gitignorePath, policyPath };\n}\n\nexport async function initConfigFile(\n cwd = process.cwd(),\n): Promise<{ path: string; created: boolean }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n try {\n await access(path, constants.F_OK);\n await chmod(path, 0o600);\n return { path, created: false };\n } catch {\n const template: CodeBuddyFileConfig = {\n postgresUrl: process.env.DATABASE_URL ?? \"postgres://localhost:5432/codebuddy\",\n // Default to the folder name (sanitized) so a fresh scaffold agrees with\n // doctor's expected default instead of warning immediately after init.\n namespace: defaultNamespaceForCwd(cwd),\n provider: { type: \"local\" },\n tokenBudget: 4000,\n };\n await writeFile(path, `${JSON.stringify(template, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path, created: true };\n }\n}\n\n/** Persist a complete project config without changing its private permissions. */\nexport async function writeConfigFile(\n config: CodeBuddyFileConfig,\n cwd = process.cwd(),\n): Promise<{ path: string }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path };\n}\n\nexport async function readConfigFile(cwd = process.cwd()): Promise<CodeBuddyFileConfig> {\n const path = configPath(cwd);\n try {\n const raw = await readFile(path, \"utf8\");\n return fileSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n}\n\n/**\n * The ONE namespace-resolution rule, shared by the DB/MCP runtime, every\n * file-based CLI command, and doctor. Precedence:\n * 1. CODEBUDDY_NAMESPACE env\n * 2. `.codebuddy/config.json` `namespace`\n * 3. the current folder name (sanitized) — matching what doctor reports as\n * the expected default.\n */\nexport function sanitizeNamespace(value: string): string {\n return value.replace(/[^a-zA-Z0-9_.:-]/g, \"-\").slice(0, 128);\n}\n\n/** Folder-name fallback used when neither env nor config specify a namespace. */\nexport function defaultNamespaceForCwd(cwd = process.cwd()): string {\n return sanitizeNamespace(basename(cwd)) || \"default\";\n}\n\nexport async function resolveNamespaceFrom(cwd = process.cwd()): Promise<string> {\n if (process.env.CODEBUDDY_NAMESPACE) return process.env.CODEBUDDY_NAMESPACE;\n const file = await readConfigFile(cwd);\n return file.namespace ?? defaultNamespaceForCwd(cwd);\n}\n\nexport const MISSING_DATABASE_URL =\n \"DATABASE_URL or .codebuddy/config.json postgresUrl is required.\";\n\n/**\n * `requireDatabase: false` (the MCP server) returns an empty `postgresUrl`\n * instead of throwing, so file-based tools still run without PostgreSQL;\n * `createRuntime` rejects the empty URL with the same message.\n */\nexport async function loadRuntimeConfig(\n cwd = process.cwd(),\n options: { requireDatabase?: boolean } = {},\n): Promise<CodeBuddyConfig> {\n const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;\n const file = await readConfigFile(projectRoot);\n const postgresUrl = process.env.DATABASE_URL ?? file.postgresUrl ?? \"\";\n if (!postgresUrl && options.requireDatabase !== false) {\n throw new Error(MISSING_DATABASE_URL);\n }\n const hfToken = process.env.HF_TOKEN ?? file.provider?.apiKey;\n return {\n postgresUrl,\n namespace:\n process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),\n projectRoot,\n provider: {\n type: file.provider?.type ?? \"local\",\n ...(hfToken ? { apiKey: hfToken } : {}),\n ...(file.provider?.model ? { model: file.provider.model } : {}),\n ...(file.provider?.relevanceFloor !== undefined\n ? { relevanceFloor: file.provider.relevanceFloor }\n : {}),\n },\n ...(file.tokenBudget ? { tokenBudget: file.tokenBudget } : {}),\n ...(file.graphify ? { graphify: file.graphify } : {}),\n };\n}\n\nexport async function checkConfigPermissions(cwd = process.cwd()): Promise<ConfigPermissionReport> {\n const path = configPath(cwd);\n try {\n const info = await stat(path);\n const modeNumber = info.mode & 0o777;\n const mode = modeNumber.toString(8).padStart(4, \"0\");\n const ok = modeNumber === 0o600;\n return {\n path,\n exists: true,\n mode,\n ok,\n ...(ok ? {} : { warning: \".codebuddy/config.json should be chmod 0600.\" }),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { path, exists: false, mode: null, ok: true };\n }\n throw error;\n }\n}\n\nexport async function checkProjectScaffold(cwd = process.cwd()): Promise<ProjectScaffoldReport> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n const memoryFactsDir = join(codebuddyDir, \"memory\", \"facts\");\n const memorySummariesDir = join(codebuddyDir, \"memory\", \"summaries\");\n const plansDir = join(codebuddyDir, \"plans\");\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n const policyPath = join(codebuddyDir, POLICY_FILE);\n\n const [\n codebuddyDirExists,\n memoryFactsDirExists,\n memorySummariesDirExists,\n plansDirExists,\n gitignoreExists,\n policiesExist,\n ] = await Promise.all([\n pathExists(codebuddyDir),\n pathExists(memoryFactsDir),\n pathExists(memorySummariesDir),\n pathExists(plansDir),\n pathExists(gitignorePath),\n pathExists(policyPath),\n ]);\n\n return {\n codebuddyDir: {\n path: codebuddyDir,\n exists: codebuddyDirExists,\n ok: codebuddyDirExists,\n ...(codebuddyDirExists\n ? {}\n : { warning: \"Run `codebuddy use` or `codebuddy init` to create the project scaffold.\" }),\n },\n memoryFactsDir: {\n path: memoryFactsDir,\n exists: memoryFactsDirExists,\n ok: memoryFactsDirExists,\n ...(memoryFactsDirExists ? {} : { warning: \"Missing .codebuddy/memory/facts directory.\" }),\n },\n memorySummariesDir: {\n path: memorySummariesDir,\n exists: memorySummariesDirExists,\n ok: memorySummariesDirExists,\n ...(memorySummariesDirExists\n ? {}\n : { warning: \"Missing .codebuddy/memory/summaries directory.\" }),\n },\n plansDir: {\n path: plansDir,\n exists: plansDirExists,\n ok: plansDirExists,\n ...(plansDirExists ? {} : { warning: \"Missing .codebuddy/plans directory.\" }),\n },\n gitignore: {\n path: gitignorePath,\n exists: gitignoreExists,\n ok: gitignoreExists,\n ...(gitignoreExists ? {} : { warning: \"Missing .codebuddy/.gitignore.\" }),\n },\n policies: {\n path: policyPath,\n exists: policiesExist,\n ok: policiesExist,\n ...(policiesExist\n ? {}\n : {\n warning:\n \"Missing .codebuddy/policies.yaml. Run `codebuddy use` or `codebuddy init` to scaffold it.\",\n }),\n },\n };\n}\n\n/** Read the plan policy, honoring CODEBUDDY_PLAN_POLICY then the config file. */\nexport async function loadPlanPolicy(cwd = process.cwd()): Promise<PlanPolicy> {\n const fromEnv = process.env.CODEBUDDY_PLAN_POLICY;\n if (fromEnv && (PLAN_POLICIES as readonly string[]).includes(fromEnv)) {\n return fromEnv as PlanPolicy;\n }\n const file = await readConfigFile(cwd);\n return file.plan?.policy ?? \"suggest\";\n}\n\n/** Persist the plan policy into `.codebuddy/config.json`, creating it if needed. */\nexport async function setPlanPolicy(policy: PlanPolicy, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.plan = { ...(raw.plan as Record<string, unknown> | undefined), policy };\n // Atomic rewrite: this file holds the postgresUrl and provider apiKey.\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn optional zvec-grep retrieval on or off in `.codebuddy/config.json`. */\nexport async function setZvecEnabled(enabled: boolean, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.zvec = { ...(raw.zvec as Record<string, unknown> | undefined), enabled };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn the BYOK LLM layer on or off; never writes a key (keys live in the environment). */\nexport async function setLlmEnabled(\n enabled: boolean,\n options: { provider?: \"groq\" | \"openrouter\"; model?: string } = {},\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.llm = {\n ...(raw.llm as Record<string, unknown> | undefined),\n enabled,\n ...(options.provider ? { provider: options.provider } : {}),\n ...(options.model ? { model: options.model } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Replace one top-level block of `.codebuddy/config.json` (`provider`,\n * `rerank`), keeping every other key and the private file mode. The result\n * must still parse, so a bad value never reaches disk.\n */\nexport async function setConfigBlock(\n key: \"provider\" | \"rerank\",\n value: Record<string, unknown>,\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw[key] = value;\n fileSchema.parse(raw);\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function redactSecrets(value: unknown): unknown {\n if (typeof value === \"string\") {\n let redacted = value;\n // Provider keys (HF, BYOK LLM) literally anywhere in a string.\n for (const key of [\n process.env.HF_TOKEN,\n process.env.GROQ_API_KEY,\n process.env.OPENROUTER_API_KEY,\n process.env.CODEBUDDY_LLM_API_KEY,\n ]) {\n if (key) redacted = redacted.split(key).join(\"[REDACTED]\");\n }\n // Credentials embedded in connection URLs (postgres://user:pass@host…).\n // The password — not the username — is the secret.\n redacted = redacted.replace(\n /(\\b[a-z][a-z0-9+.-]*:\\/\\/[^:/\\s@]+:)[^/\\s@]+(@)/gi,\n \"$1[REDACTED]$2\",\n );\n return redacted;\n }\n if (Array.isArray(value)) return value.map(redactSecrets);\n if (!value || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, nested]) => [\n key,\n /token|apiKey|authorization|secret/i.test(key) ? \"[REDACTED]\" : redactSecrets(nested),\n ]),\n );\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["join","join"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/memory-extract/sensitive.ts"],"sourcesContent":["/**\n * Sensitive-content scan. Secrets and personal data must never be auto-saved\n * as durable memory — a matched candidate is forced into the review queue,\n * flagged, so a human decides. This is a conservative detector: false positives\n * (an extra review item) are acceptable; false negatives (a leaked key in\n * committed Markdown) are not.\n *\n * The scan only ever *routes to review* — it never blocks a turn, deletes\n * content, or rejects on its own. Adding a reason at most moves a candidate from\n * \"auto-save\" to \"human decides\".\n */\n\nexport type SensitivityResult = {\n sensitive: boolean;\n reasons: string[];\n};\n\nconst PATTERNS: Array<{ label: string; regex: RegExp }> = [\n { label: \"private-key\", regex: /-----BEGIN(?: [A-Z0-9]+)* PRIVATE KEY-----/ },\n { label: \"aws-access-key\", regex: /\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b/ },\n { label: \"hf-token\", regex: /\\bhf_[A-Za-z0-9]{16,}\\b/ },\n { label: \"github-token\", regex: /\\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\\b/ },\n // GitHub fine-grained personal access tokens.\n {\n label: \"github-fine-grained-pat\",\n regex: /\\bgithub_pat_[A-Za-z0-9_]{22,255}_[A-Za-z0-9]{22,255}\\b/,\n },\n { label: \"npm-token\", regex: /\\bnpm_[A-Za-z0-9]{36}\\b/ },\n { label: \"gitlab-token\", regex: /\\bglpat-[A-Za-z0-9_-]{20,}\\b/ },\n { label: \"slack-token\", regex: /\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b/ },\n { label: \"bearer-token\", regex: /\\bBearer\\s+[A-Za-z0-9._-]{12,}\\b/ },\n // Vendor API keys with distinctive, low-false-positive prefixes.\n { label: \"anthropic-key\", regex: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/ },\n { label: \"openai-key\", regex: /\\bsk-(?!ant-)[A-Za-z0-9_-]{20,}\\b/ },\n { label: \"google-api-key\", regex: /\\bAIza[0-9A-Za-z_-]{35}\\b/ },\n { label: \"stripe-key\", regex: /\\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}\\b/ },\n // A JWT: three base64url segments. Common in auth-related pastes.\n { label: \"jwt\", regex: /\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b/ },\n // Credentials embedded in a connection URL: scheme://user:password@host.\n {\n label: \"connection-string-credentials\",\n regex: /\\b[a-z][a-z0-9+.-]*:\\/\\/[^\\s:/@]+:[^\\s:/@]+@/i,\n },\n {\n // Catches `password: x`, `api_key = x`, and the prose form `password is x`.\n // Deliberately eager — an over-flagged review item is cheaper than a leak.\n label: \"credential-assignment\",\n regex:\n /\\b(?:api[_-]?key|secret|token|password|passwd|pwd|access[_-]?key|authorization|auth[_-]?token|credential[s]?)\\b\\s*(?:[:=]|\\bis\\b|\\bwas\\b|\\bequals\\b)\\s*\\S{6,}/i,\n },\n // HTTP Basic-auth style inline credentials outside a URL scheme\n // (`Authorization: Basic dXNlcjpwYXNz`).\n {\n label: \"basic-auth-header\",\n regex: /\\bAuthorization\\s*[:=]\\s*Basic\\s+[A-Za-z0-9+/=]{8,}/i,\n },\n { label: \"email\", regex: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/ },\n];\n\n/**\n * Deterministic, review-oriented high-entropy heuristic. Flags a token that\n * looks like a random secret — long, mixed character classes, high Shannon\n * entropy — that the prefix-based patterns above wouldn't catch. Tuned to stay\n * quiet on ordinary code and prose: identifiers are usually low-entropy words\n * (`camelCase`, `snake_case`), and hex hashes / SHAs lack the required mix of\n * upper, lower, and digit. Never a hard block; at most one extra review item.\n * OpenCode session/message/part ids (`ses_` + 12 hex + 14 random base62) are\n * host identifiers recorded as provenance, not secrets, so they are skipped;\n * otherwise every OpenCode capture would be queued for review.\n */\nconst OPENCODE_ID = /^(?:ses|msg|prt)_[0-9a-f]{12}[0-9A-Za-z]{14}$/;\n\nexport function hasHighEntropyToken(text: string): boolean {\n for (const token of text.split(/[^A-Za-z0-9+/_=-]+/)) {\n if (token.length < 24 || token.length > 200 || OPENCODE_ID.test(token)) continue;\n if (!/[a-z]/.test(token) || !/[A-Z]/.test(token) || !/[0-9]/.test(token)) continue;\n if (shannonEntropy(token) >= 3.5) return true;\n }\n return false;\n}\n\nfunction shannonEntropy(value: string): number {\n const counts = new Map<string, number>();\n for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);\n let entropy = 0;\n for (const count of counts.values()) {\n const p = count / value.length;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\nexport function scanSensitive(text: string): SensitivityResult {\n const reasons: string[] = [];\n for (const { label, regex } of PATTERNS) {\n if (regex.test(text) && !reasons.includes(label)) reasons.push(label);\n }\n if (hasHighEntropyToken(text)) reasons.push(\"high-entropy-string\");\n return { sensitive: reasons.length > 0, reasons };\n}\n\nexport const REDACTED = \"[REDACTED SENSITIVE CONTENT]\";\n\n/** Whole-field redaction: a field that scans sensitive is replaced, never partially kept. */\nexport function redactSensitive(text: string): string {\n return scanSensitive(text).sensitive ? REDACTED : text;\n}\n\n/**\n * The claim: fields that together state one memory. A credential can be split\n * across them (\"password\" / \"is\" / \"hunter2secret\"), so they are scanned\n * jointly and redacted as a unit.\n */\nexport const CLAIM_KEYS = [\"subject\", \"predicate\", \"object\", \"content\"] as const;\n\n/** Independent free-text provenance fields (string or string[]), redacted one by one. */\nexport const PROVENANCE_TEXT_KEYS = [\n \"paths\",\n \"verification\",\n \"reason\",\n \"createdByAgent\",\n \"sourcePlanId\",\n \"sourceSessionId\",\n \"introducedBy\",\n] as const;\n\n/** The claim fields joined in reading order, for a joint scan. */\nexport function claimText(record: Partial<Record<(typeof CLAIM_KEYS)[number], unknown>>): string {\n return CLAIM_KEYS.map((key) => record[key])\n .filter((value): value is string => typeof value === \"string\")\n .join(\" \");\n}\n\n/**\n * Persistence-boundary redaction: returns a copy of `record` with every\n * free-text field redacted. If any claim field, or the joined claim, scans\n * sensitive, ALL claim fields are replaced (a sibling may hold the value half\n * of the credential). Provenance fields are redacted individually. Idempotent,\n * so re-persisting (e.g. approving a queued item) never re-introduces a raw\n * value. Structural fields (ids, fingerprint, namespace, timestamps, enums)\n * are left alone.\n */\nexport function redactPersisted<T extends object>(record: T): T {\n const out = { ...record } as Record<string, unknown>;\n const present = CLAIM_KEYS.filter((key) => typeof out[key] === \"string\");\n const claimSensitive =\n present.length > 0 &&\n (scanSensitive(claimText(out)).sensitive ||\n present.some((key) => scanSensitive(out[key] as string).sensitive));\n if (claimSensitive) for (const key of present) out[key] = REDACTED;\n for (const key of PROVENANCE_TEXT_KEYS) {\n const value = out[key];\n if (typeof value === \"string\") out[key] = redactSensitive(value);\n else if (Array.isArray(value)) {\n out[key] = value.map((item) => (typeof item === \"string\" ? redactSensitive(item) : item));\n }\n }\n return out as T;\n}\n\n/** Scan several surfaces separately and merge the reason labels (never the matched text). */\nexport function scanFields(fields: Array<string | null | undefined>): SensitivityResult {\n const reasons: string[] = [];\n for (const field of fields) {\n if (!field) continue;\n for (const reason of scanSensitive(field).reasons) {\n if (!reasons.includes(reason)) reasons.push(reason);\n }\n }\n return { sensitive: reasons.length > 0, reasons };\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/providers/models.ts"],"sourcesContent":["export type ModelRole = \"primary\" | \"fallback\" | \"last_resort\";\nexport type ModelKind = \"embedding\" | \"llm\";\n\nexport type ModelDefinition = {\n id: string;\n role: ModelRole;\n kind: ModelKind;\n dimensions?: number;\n /** Actual deployment revision metadata; this does not pin Hugging Face requests. */\n revision?: string;\n schema?: string;\n contextWindow?: number;\n dailyCallBudget?: number;\n supportsBatch?: boolean;\n /**\n * Cosine at or above which a memory counts as semantically relevant to a\n * task on its own (without a shared word or path). Calibrated per model by\n * `npm run eval:semantic`; cosines are not comparable across models.\n */\n relevanceFloor?: number;\n};\n\nexport const EMBEDDING_DIMENSIONS = 384;\n\n/**\n * Default local embedder (ONNX via transformers.js, no network after the first\n * download): ModernBERT-based, 384-d, 8k context, trained on code retrieval.\n * The revision is a pinned commit so the embedding space never drifts.\n */\nexport const LOCAL_EMBEDDING_MODEL = {\n id: \"onnx-community/granite-embedding-small-english-r2-ONNX\",\n role: \"primary\",\n kind: \"embedding\",\n dimensions: EMBEDDING_DIMENSIONS,\n revision: \"1dc7835ba0cb9c76a3618d0bf0c427c97671b3c8\",\n schema: \"transformers.js@4-q8-cls-l2\",\n supportsBatch: true,\n relevanceFloor: 0.84,\n} satisfies ModelDefinition;\n\n/**\n * Cross-encoder rerankers per `rerank.provider` (off by default: on\n * `npm run eval:semantic` none beat granite's dense order). The local default\n * was the best small one measured (13/20 first results, ~20 ms per prompt).\n */\nexport const DEFAULT_RERANK_MODELS = {\n local: {\n id: \"mixedbread-ai/mxbai-rerank-xsmall-v1\",\n revision: \"b5c6e9da73abc3711f593f705371cdbe9e0fe422\",\n },\n huggingface: { id: \"BAAI/bge-reranker-v2-m3\" },\n openrouter: { id: \"cohere/rerank-v3.5\" },\n} as const;\n\n/** Default hosted embedders; each must return 384-d vectors. */\nexport const DEFAULT_CLOUD_EMBEDDING_MODELS = {\n huggingface: \"sentence-transformers/all-MiniLM-L6-v2\",\n openrouter: \"openai/text-embedding-3-small\",\n} as const;\n\nexport const DEFAULT_EMBEDDING_MODELS = [\n {\n id: \"sentence-transformers/all-MiniLM-L6-v2\",\n role: \"primary\",\n kind: \"embedding\",\n dimensions: EMBEDDING_DIMENSIONS,\n dailyCallBudget: 1_000,\n supportsBatch: true,\n relevanceFloor: 0.42,\n },\n {\n id: \"BAAI/bge-small-en-v1.5\",\n role: \"fallback\",\n kind: \"embedding\",\n dimensions: EMBEDDING_DIMENSIONS,\n dailyCallBudget: 1_000,\n supportsBatch: true,\n relevanceFloor: 0.69,\n },\n] satisfies ModelDefinition[];\n\nexport const DEFAULT_LLM_MODELS = [\n {\n id: \"meta-llama/Llama-3.2-3B-Instruct\",\n role: \"primary\",\n kind: \"llm\",\n contextWindow: 131_072,\n dailyCallBudget: 300,\n },\n {\n id: \"Qwen/Qwen2.5-7B-Instruct\",\n role: \"fallback\",\n kind: \"llm\",\n contextWindow: 32_768,\n dailyCallBudget: 300,\n },\n {\n id: \"mistralai/Mistral-7B-Instruct-v0.3\",\n role: \"last_resort\",\n kind: \"llm\",\n contextWindow: 32_768,\n dailyCallBudget: 300,\n },\n] satisfies ModelDefinition[];\n\nexport const MODEL_REGISTRY: ModelDefinition[] = [\n LOCAL_EMBEDDING_MODEL,\n ...DEFAULT_EMBEDDING_MODELS,\n ...DEFAULT_LLM_MODELS,\n];\n\nexport function getModelDefinition(modelId: string): ModelDefinition | undefined {\n return MODEL_REGISTRY.find((model) => model.id === modelId);\n}\n\nexport function getModelContextWindow(modelId: string): number | undefined {\n return getModelDefinition(modelId)?.contextWindow;\n}\n\nexport function assertCompatibleEmbeddingModels(models: ModelDefinition[]): void {\n const mismatchedModel = models.find((model) => model.dimensions !== EMBEDDING_DIMENSIONS);\n\n if (mismatchedModel) {\n throw new Error(\n `Embedding model ${mismatchedModel.id} has ${mismatchedModel.dimensions ?? \"unknown\"} dimensions; expected ${EMBEDDING_DIMENSIONS}.`,\n );\n }\n}\n"],"mappings":";AAsBO,IAAM,uBAAuB;AAO7B,IAAM,wBAAwB;AAAA,EACnC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,gBAAgB;AAClB;AAOO,IAAM,wBAAwB;AAAA,EACnC,OAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU;AAAA,EACZ;AAAA,EACA,aAAa,EAAE,IAAI,0BAA0B;AAAA,EAC7C,YAAY,EAAE,IAAI,qBAAqB;AACzC;AAGO,IAAM,iCAAiC;AAAA,EAC5C,aAAa;AAAA,EACb,YAAY;AACd;AAEO,IAAM,2BAA2B;AAAA,EACtC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AACF;AAEO,IAAM,qBAAqB;AAAA,EAChC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe;AAAA,IACf,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe;AAAA,IACf,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe;AAAA,IACf,iBAAiB;AAAA,EACnB;AACF;AAEO,IAAM,iBAAoC;AAAA,EAC/C;AAAA,EACA,GAAG;AAAA,EACH,GAAG;AACL;AAEO,SAAS,mBAAmB,SAA8C;AAC/E,SAAO,eAAe,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO;AAC5D;AAEO,SAAS,sBAAsB,SAAqC;AACzE,SAAO,mBAAmB,OAAO,GAAG;AACtC;AAEO,SAAS,gCAAgC,QAAiC;AAC/E,QAAM,kBAAkB,OAAO,KAAK,CAAC,UAAU,MAAM,eAAe,oBAAoB;AAExF,MAAI,iBAAiB;AACnB,UAAM,IAAI;AAAA,MACR,mBAAmB,gBAAgB,EAAE,QAAQ,gBAAgB,cAAc,SAAS,yBAAyB,oBAAoB;AAAA,IACnI;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/map/indexer.ts","../src/indexer/engine.ts","../src/map/cache.ts","../src/indexer/store.ts","../src/indexer/types.ts","../src/indexer/scan.ts","../src/indexer/summary.ts","../src/integrations/graphify.ts","../src/map/graph-freshness.ts","../src/context/discovery.ts","../src/map/graphify.ts","../src/context/anchors.ts"],"sourcesContent":["import { readdir, readFile, stat } from \"node:fs/promises\";\nimport { dirname, extname, join, relative, resolve } from \"node:path\";\nimport type { ArchitectureMap, MapQuery, ModuleEdge, ModuleNode } from \"./types.js\";\n\nconst SUPPORTED_EXTENSIONS = new Set([\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\"]);\nconst IGNORED_DIRECTORIES = new Set([\n \".git\",\n \"node_modules\",\n \"dist\",\n \"build\",\n \"coverage\",\n \".codebuddy\",\n]);\n\nexport async function buildArchitectureMap(\n repositoryRoot = process.cwd(),\n): Promise<ArchitectureMap> {\n const root = resolve(repositoryRoot);\n const files = await listSourceFiles(root);\n const fileSet = new Set(files.map((file) => normalize(relative(root, file))));\n const modules: ModuleNode[] = [];\n const edges: ModuleEdge[] = [];\n\n for (const absolutePath of files) {\n const source = await readFile(absolutePath, \"utf8\");\n const from = normalize(relative(root, absolutePath));\n const imports = extractImports(source);\n const resolvedImports: string[] = [];\n for (const specifier of imports) {\n const resolved = resolveImport(root, from, specifier.value, fileSet);\n if (!resolved) continue;\n resolvedImports.push(resolved);\n edges.push({ from, to: resolved, kind: specifier.kind });\n }\n modules.push({\n path: from,\n language: extname(absolutePath).includes(\"ts\") ? \"ts\" : \"js\",\n imports: [...new Set(resolvedImports)].sort(),\n });\n }\n\n return {\n modules: modules.sort((left, right) => left.path.localeCompare(right.path)),\n edges: edges.sort((left, right) =>\n `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`),\n ),\n };\n}\n\nexport type IncrementalMapStats = { readFiles: number; reusedFiles: number; rebuilt: boolean };\n\n/** Build or update a map from an already-known source file set. */\nexport async function updateArchitectureMap(\n repositoryRoot: string,\n sourcePaths: string[],\n previous: ArchitectureMap | null,\n changedPaths: string[],\n): Promise<{ map: ArchitectureMap; stats: IncrementalMapStats }> {\n const root = resolve(repositoryRoot);\n const normalizedPaths = sourcePaths.map(normalize).sort();\n const fileSet = new Set(normalizedPaths);\n const changed = new Set(changedPaths.map(normalize));\n const retainedModules = (previous?.modules ?? []).filter(\n (module) => fileSet.has(module.path) && !changed.has(module.path),\n );\n const retainedEdges = (previous?.edges ?? []).filter(\n (edge) => fileSet.has(edge.from) && fileSet.has(edge.to) && !changed.has(edge.from),\n );\n const modules = [...retainedModules];\n const edges = [...retainedEdges];\n\n for (const path of normalizedPaths) {\n if (!changed.has(path)) continue;\n const source = await readFile(join(root, path), \"utf8\");\n const imports = extractImports(source);\n const resolvedImports: string[] = [];\n for (const specifier of imports) {\n const resolvedImport = resolveImport(root, path, specifier.value, fileSet);\n if (!resolvedImport) continue;\n resolvedImports.push(resolvedImport);\n edges.push({ from: path, to: resolvedImport, kind: specifier.kind });\n }\n modules.push({\n path,\n language: extname(path).includes(\"ts\") ? \"ts\" : \"js\",\n imports: [...new Set(resolvedImports)].sort(),\n });\n }\n\n return {\n map: {\n modules: modules.sort((left, right) => left.path.localeCompare(right.path)),\n edges: edges.sort((left, right) =>\n `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`),\n ),\n },\n stats: {\n readFiles: changed.size,\n reusedFiles: retainedModules.length,\n rebuilt: previous === null || changed.size === normalizedPaths.length,\n },\n };\n}\n\nexport function queryMap(map: ArchitectureMap, query: MapQuery): ModuleEdge[] {\n const limit = query.limit ?? 50;\n return map.edges\n .filter((edge) => (query.from ? edge.from === normalize(query.from) : true))\n .filter((edge) => (query.to ? edge.to === normalize(query.to) : true))\n .slice(0, limit);\n}\n\nexport function neighbours(\n map: ArchitectureMap,\n modulePath: string,\n direction: \"in\" | \"out\" | \"both\" = \"both\",\n): { modules: ModuleNode[]; edges: ModuleEdge[] } {\n const target = normalize(modulePath);\n const edges = map.edges.filter((edge) => {\n if (direction === \"in\") return edge.to === target;\n if (direction === \"out\") return edge.from === target;\n return edge.from === target || edge.to === target;\n });\n const paths = new Set<string>([target]);\n for (const edge of edges) {\n paths.add(edge.from);\n paths.add(edge.to);\n }\n return {\n modules: map.modules.filter((module) => paths.has(module.path)),\n edges,\n };\n}\n\nasync function listSourceFiles(root: string): Promise<string[]> {\n const files: string[] = [];\n await walk(root, files);\n return files.sort();\n}\n\nasync function walk(directory: string, files: string[]): Promise<void> {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const absolute = join(directory, entry.name);\n if (entry.isDirectory()) {\n if (IGNORED_DIRECTORIES.has(entry.name)) continue;\n await walk(absolute, files);\n continue;\n }\n if (!entry.isFile()) continue;\n if (!SUPPORTED_EXTENSIONS.has(extname(entry.name))) continue;\n const info = await stat(absolute);\n if (info.size > 500_000) continue;\n files.push(absolute);\n }\n}\n\nfunction extractImports(source: string): Array<{ value: string; kind: ModuleEdge[\"kind\"] }> {\n const imports: Array<{ value: string; kind: ModuleEdge[\"kind\"] }> = [];\n // Strip line/block comments first so quoted specifiers inside comments can't\n // fabricate phantom edges in the architecture map.\n const stripped = source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \" \").replace(/(^|[^:])\\/\\/[^\\n]*/g, \"$1 \");\n const staticImport = /\\bimport\\s+(?:type\\s+)?(?:[^'\"()+]+?\\s+from\\s+)?[\"']([^\"']+)[\"']/g;\n const exportFrom = /\\bexport\\s+[^'\"()+]+?\\s+from\\s+[\"']([^\"']+)[\"']/g;\n const dynamicImport = /\\bimport\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/g;\n\n for (const match of stripped.matchAll(staticImport)) {\n if (match[1]) imports.push({ value: match[1], kind: \"import\" });\n }\n for (const match of stripped.matchAll(exportFrom)) {\n if (match[1]) imports.push({ value: match[1], kind: \"import\" });\n }\n for (const match of stripped.matchAll(dynamicImport)) {\n if (match[1]) imports.push({ value: match[1], kind: \"dynamic_import\" });\n }\n return imports;\n}\n\nfunction resolveImport(\n root: string,\n from: string,\n specifier: string,\n fileSet: Set<string>,\n): string | null {\n if (!specifier.startsWith(\".\")) return null;\n const base = normalize(relative(root, resolve(root, dirname(from), specifier)));\n // ESM specifiers point at emitted JS (`./x.js`), but the source on disk is\n // often `./x.ts`. Try the specifier EXACTLY as written first (so a real\n // `x.js` file wins when both `x.ts` and `x.js` exist), then fall back to the\n // extension-stripped stem for the TS-source case.\n const stem = base.replace(/\\.(js|jsx|mjs|cjs)$/, \"\");\n const bases = stem === base ? [base] : [base, stem];\n const candidates = bases.flatMap((b) => [\n b,\n `${b}.ts`,\n `${b}.tsx`,\n `${b}.js`,\n `${b}.jsx`,\n `${b}.mjs`,\n `${b}.cjs`,\n `${b}/index.ts`,\n `${b}/index.tsx`,\n `${b}/index.js`,\n `${b}/index.jsx`,\n ]);\n return candidates.find((candidate) => fileSet.has(candidate)) ?? null;\n}\n\nfunction normalize(path: string): string {\n return path.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n","import { createHash } from \"node:crypto\";\nimport type { Stats } from \"node:fs\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { refreshArchitectureCache } from \"../map/cache.js\";\nimport { listIndexableFiles } from \"./scan.js\";\nimport { emptyManifest, IndexStore } from \"./store.js\";\nimport { summarizeSource } from \"./summary.js\";\nimport { extractSymbolTable, supportsSymbols } from \"./symbols.js\";\nimport {\n INDEX_MANIFEST_VERSION,\n type IndexEntry,\n type IndexError,\n type IndexManifest,\n type IndexResult,\n} from \"./types.js\";\n\n/**\n * Incremental repository indexing. A file is re-summarized only when its\n * content hash differs from the manifest, so repeated runs over an unchanged\n * tree are cheap and deterministic. A file whose size, mtime and ctime still\n * match the entry is not even re-read. Per-file failures (unreadable, too\n * large) are isolated and reported; they never abort the pass. `full` ignores\n * existing hashes and rebuilds every entry.\n *\n * The same pass doubles as use-time validation (`loadFreshIndex`): context\n * tools run it before trusting indexed symbols, summaries or imports, so a CLI\n * or hook process with no watcher still sees current source.\n */\n\n/** Skip files larger than this — summarizing them is neither cheap nor useful. */\nconst MAX_FILE_BYTES = 512_000;\n/**\n * Files changed this recently are always re-hashed: a second edit inside a\n * coarse mtime tick could otherwise keep the same size and timestamps.\n */\nconst RACY_WINDOW_MS = 3_000;\n\nexport type IndexOptions = {\n repositoryRoot?: string;\n full?: boolean;\n /** Injectable clock for deterministic tests. */\n now?: () => Date;\n};\n\nexport async function indexRepository(options: IndexOptions = {}): Promise<IndexResult> {\n return (await refreshIndex(options)).result;\n}\n\nexport type FreshIndex = {\n manifest: IndexManifest;\n /** One `index:` explain line: how the manifest compared with current source. */\n explain: string;\n};\n\n/**\n * The manifest, validated against current source right now. Changed files are\n * re-summarized (symbols, imports), deleted ones dropped, new ones added, and\n * the result is persisted so the next call takes the metadata fast path. Never\n * throws: if validation fails the stored manifest is returned with a reason.\n */\nexport async function loadFreshIndex(repositoryRoot: string): Promise<FreshIndex> {\n try {\n const { result, manifest } = await refreshIndex({ repositoryRoot });\n const delta = result.added + result.changed + result.removed;\n const failures =\n result.errors.length > 0\n ? `; ${result.errors.length} file(s) could not be validated and were omitted`\n : \"\";\n return {\n manifest,\n explain:\n delta === 0\n ? `index: ${result.totalIndexed} indexed file(s) match current source${failures}`\n : `index: refreshed against current source (${result.changed} changed, ${result.added} added, ${result.removed} removed; ${result.totalIndexed} indexed)${failures}`,\n };\n } catch (error) {\n return {\n manifest: emptyManifest(),\n explain: `index: freshness check failed (${(error as Error).message}); indexed evidence omitted`,\n };\n }\n}\n\n// ponytail: in-process serialization only; separate processes (hook + MCP\n// server) can still race, which the metadata check heals on the next pass.\nconst passes = new Map<string, Promise<unknown>>();\n\n/** One pass at a time per repository, so overlapping callers never interleave writes. */\nfunction refreshIndex(\n options: IndexOptions,\n): Promise<{ result: IndexResult; manifest: IndexManifest }> {\n const key = resolve(options.repositoryRoot ?? process.cwd());\n const next = (passes.get(key) ?? Promise.resolve())\n .catch(() => undefined)\n .then(() => runIndexPass(options));\n passes.set(key, next);\n void next\n .finally(() => {\n if (passes.get(key) === next) passes.delete(key);\n })\n .catch(() => undefined);\n return next;\n}\n\nasync function runIndexPass(\n options: IndexOptions,\n): Promise<{ result: IndexResult; manifest: IndexManifest }> {\n const start = Date.now();\n const now = options.now ?? (() => new Date());\n const repositoryRoot = resolve(options.repositoryRoot ?? process.cwd());\n const store = new IndexStore(repositoryRoot);\n\n const [files, previous] = await Promise.all([listIndexableFiles(repositoryRoot), store.read()]);\n\n const entries: Record<string, IndexEntry> = {};\n const errors: IndexError[] = [];\n let added = 0;\n let changed = 0;\n let unchanged = 0;\n let skipped = 0;\n // A never-written manifest is persisted even when the tree is empty.\n let dirty = previous.generatedAt === emptyManifest().generatedAt;\n\n for (const path of files) {\n try {\n const info = await stat(join(repositoryRoot, path));\n if (!info.isFile()) continue;\n if (info.size > MAX_FILE_BYTES) {\n skipped++;\n continue;\n }\n\n const existing = previous.entries[path];\n if (!options.full && existing && statUnchanged(existing, info)) {\n entries[path] = existing;\n unchanged++;\n continue;\n }\n\n const content = await readFile(join(repositoryRoot, path), \"utf8\");\n const hash = hashContent(content);\n const metadata = trustedMetadata(info);\n dirty = true;\n\n if (!options.full && existing && existing.hash === hash) {\n const { mtimeMs: _mtime, ctimeMs: _ctime, ...rest } = existing;\n entries[path] = { ...rest, size: info.size, ...metadata };\n unchanged++;\n continue;\n }\n\n const { language, lines, symbols, summary } = summarizeSource(path, content);\n const symbolTable = supportsSymbols(path) ? extractSymbolTable(content) : [];\n entries[path] = {\n path,\n hash,\n size: info.size,\n ...metadata,\n language,\n lines,\n symbols,\n ...(symbolTable.length > 0 ? { symbolTable } : {}),\n summary,\n indexedAt: now().toISOString(),\n };\n if (existing) changed++;\n else added++;\n } catch (error) {\n errors.push({ path, error: (error as Error).message });\n skipped++;\n }\n }\n\n const removed = Object.keys(previous.entries).filter((path) => !(path in entries)).length;\n\n const manifest: IndexManifest = {\n version: INDEX_MANIFEST_VERSION,\n generatedAt: now().toISOString(),\n entries: sortEntries(entries),\n };\n if (dirty || removed > 0) await store.write(manifest);\n const architecture = await refreshArchitectureCache(repositoryRoot, manifest);\n\n const result: IndexResult = {\n scanned: files.length,\n added,\n changed,\n unchanged,\n removed,\n skipped,\n errors,\n durationMs: Date.now() - start,\n totalIndexed: Object.keys(entries).length,\n architecture: {\n source: architecture.source,\n readFiles: architecture.stats.readFiles,\n reusedFiles: architecture.stats.reusedFiles,\n },\n };\n return { result, manifest };\n}\n\nfunction statUnchanged(entry: IndexEntry, info: Stats): boolean {\n return (\n entry.mtimeMs !== undefined &&\n entry.size === info.size &&\n entry.mtimeMs === info.mtimeMs &&\n entry.ctimeMs === info.ctimeMs\n );\n}\n\n/** Stat metadata worth trusting later, or nothing while the file is still racy. */\nfunction trustedMetadata(info: Stats): { mtimeMs?: number; ctimeMs?: number } {\n if (Date.now() - Math.max(info.mtimeMs, info.ctimeMs) < RACY_WINDOW_MS) return {};\n return { mtimeMs: info.mtimeMs, ctimeMs: info.ctimeMs };\n}\n\nexport function hashContent(content: string): string {\n return createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\nfunction sortEntries(entries: Record<string, IndexEntry>): Record<string, IndexEntry> {\n return Object.fromEntries(\n Object.keys(entries)\n .sort()\n .map((key) => [key, entries[key] as IndexEntry]),\n );\n}\n","import { mkdir, readFile } from \"node:fs/promises\";\nimport { dirname, extname, join, resolve } from \"node:path\";\nimport { assertNoSymlinkBetween, writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { IndexStore } from \"../indexer/store.js\";\nimport type { IndexManifest } from \"../indexer/types.js\";\nimport {\n buildArchitectureMap,\n type IncrementalMapStats,\n updateArchitectureMap,\n} from \"./indexer.js\";\nimport type { ArchitectureMap } from \"./types.js\";\n\nconst CACHE_VERSION = 1;\nconst SOURCE_EXTENSIONS = new Set([\".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\", \".cjs\"]);\n\ntype ArchitectureCache = {\n version: 1;\n generatedAt: string;\n sourceHashes: Record<string, string>;\n map: ArchitectureMap;\n};\n\nexport type CachedArchitectureResult = {\n map: ArchitectureMap;\n stats: IncrementalMapStats;\n source: \"cache\" | \"incremental\" | \"rebuild\" | \"fallback\";\n};\n\nexport async function loadArchitectureMap(\n repositoryRoot = process.cwd(),\n suppliedManifest?: IndexManifest,\n): Promise<CachedArchitectureResult> {\n const root = resolve(repositoryRoot);\n const manifest = suppliedManifest ?? (await new IndexStore(root).read());\n if (Object.keys(manifest.entries).length === 0) {\n const map = await buildArchitectureMap(root);\n return {\n map,\n stats: { readFiles: map.modules.length, reusedFiles: 0, rebuilt: true },\n source: \"fallback\",\n };\n }\n return refreshArchitectureCache(root, manifest);\n}\n\nexport async function refreshArchitectureCache(\n repositoryRoot: string,\n manifest: IndexManifest,\n): Promise<CachedArchitectureResult> {\n const root = resolve(repositoryRoot);\n const path = join(root, \".codebuddy\", \"cache\", \"architecture.json\");\n const currentHashes = sourceHashes(manifest);\n const currentPaths = Object.keys(currentHashes).sort();\n const cached = await readCache(path);\n const cachedPaths = cached ? Object.keys(cached.sourceHashes).sort() : [];\n const fileSetChanged = cachedPaths.join(\"\\n\") !== currentPaths.join(\"\\n\");\n const changedPaths =\n !cached || fileSetChanged\n ? currentPaths\n : currentPaths.filter(\n (sourcePath) => cached.sourceHashes[sourcePath] !== currentHashes[sourcePath],\n );\n\n if (cached && changedPaths.length === 0) {\n return {\n map: cached.map,\n stats: { readFiles: 0, reusedFiles: cached.map.modules.length, rebuilt: false },\n source: \"cache\",\n };\n }\n\n const updated = await updateArchitectureMap(\n root,\n currentPaths,\n fileSetChanged ? null : (cached?.map ?? null),\n changedPaths,\n );\n const next: ArchitectureCache = {\n version: CACHE_VERSION,\n generatedAt: manifest.generatedAt,\n sourceHashes: currentHashes,\n map: updated.map,\n };\n await assertNoSymlinkBetween(root, dirname(path));\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeAtomic(path, `${JSON.stringify(next, null, 2)}\\n`);\n return { ...updated, source: fileSetChanged || !cached ? \"rebuild\" : \"incremental\" };\n}\n\nasync function readCache(path: string): Promise<ArchitectureCache | null> {\n try {\n const parsed = JSON.parse(await readFile(path, \"utf8\")) as ArchitectureCache;\n if (parsed.version !== CACHE_VERSION || !parsed.map || !parsed.sourceHashes) return null;\n if (!Array.isArray(parsed.map.modules) || !Array.isArray(parsed.map.edges)) return null;\n if (\n !parsed.map.modules.every(\n (module) =>\n module &&\n typeof module.path === \"string\" &&\n typeof module.language === \"string\" &&\n Array.isArray(module.imports),\n ) ||\n !parsed.map.edges.every(\n (edge) => edge && typeof edge.from === \"string\" && typeof edge.to === \"string\",\n )\n ) {\n return null;\n }\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction sourceHashes(manifest: IndexManifest): Record<string, string> {\n return Object.fromEntries(\n Object.values(manifest.entries)\n .filter((entry) => SOURCE_EXTENSIONS.has(extname(entry.path).toLowerCase()))\n .map((entry) => [entry.path, entry.hash]),\n );\n}\n","import { mkdir, readFile } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { z } from \"zod\";\nimport { assertNoSymlinkBetween, writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { INDEX_MANIFEST_VERSION, type IndexManifest } from \"./types.js\";\n\n/**\n * Persists the index manifest as a single JSON file under\n * `.codebuddy/cache/index.json`. `cache/` is gitignored by the project\n * scaffold, so the manifest is treated as a rebuildable derived artifact —\n * corrupt or version-mismatched manifests degrade to \"empty\" rather than\n * throwing, and a full `codebuddy index` regenerates them.\n */\n\nconst symbolSpanSchema = z.object({\n name: z.string(),\n kind: z.enum([\"function\", \"method\", \"class\", \"interface\", \"type\", \"enum\", \"const\"]),\n startLine: z.number().int().nonnegative(),\n endLine: z.number().int().nonnegative(),\n signature: z.string(),\n exported: z.boolean(),\n});\n\nconst entrySchema = z.object({\n path: z.string(),\n hash: z.string(),\n size: z.number().int().nonnegative(),\n mtimeMs: z.number().optional(),\n ctimeMs: z.number().optional(),\n language: z.string(),\n lines: z.number().int().nonnegative(),\n symbols: z.array(z.string()),\n symbolTable: z.array(symbolSpanSchema).optional(),\n summary: z.string(),\n indexedAt: z.string(),\n});\n\nconst manifestSchema = z.object({\n version: z.literal(INDEX_MANIFEST_VERSION),\n generatedAt: z.string(),\n entries: z.record(z.string(), entrySchema),\n});\n\nexport class IndexStore {\n readonly repositoryRoot: string;\n readonly cacheDirectory: string;\n readonly manifestPath: string;\n\n constructor(repositoryRoot = process.cwd()) {\n this.repositoryRoot = resolve(repositoryRoot);\n this.cacheDirectory = join(this.repositoryRoot, \".codebuddy\", \"cache\");\n this.manifestPath = join(this.cacheDirectory, \"index.json\");\n }\n\n /** Read the manifest, or an empty one if absent/corrupt/version-mismatched. */\n async read(): Promise<IndexManifest> {\n try {\n const raw = await readFile(this.manifestPath, \"utf8\");\n const parsed = manifestSchema.safeParse(JSON.parse(raw));\n if (!parsed.success) return emptyManifest();\n // Zod's optional() widens to `| undefined`; the parse guarantees the shape.\n return parsed.data as IndexManifest;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return emptyManifest();\n // Unreadable cache is non-fatal: rebuild from scratch.\n return emptyManifest();\n }\n }\n\n async write(manifest: IndexManifest): Promise<void> {\n await assertNoSymlinkBetween(this.repositoryRoot, dirname(this.manifestPath));\n await mkdir(this.cacheDirectory, { recursive: true, mode: 0o700 });\n await writeAtomic(this.manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`);\n }\n}\n\nexport function emptyManifest(): IndexManifest {\n return { version: INDEX_MANIFEST_VERSION, generatedAt: new Date(0).toISOString(), entries: {} };\n}\n","/**\n * Background index model (Proposal 04 Phase 04.3). The indexer builds a\n * deterministic, local-first picture of the repository — one content hash and\n * one lightweight summary per indexable file — so CodeBuddy knows something\n * useful before any memory is manually written. It is a derived *cache* under\n * `.codebuddy/cache/`, never a source of truth: deleting it and re-running\n * `codebuddy index` reproduces it exactly. No LLM is involved; summaries are\n * extracted with simple, stable heuristics.\n */\n\nimport type { SymbolSpan } from \"./symbols.js\";\n\n/** Bumped to 2 when symbol tables gained class methods, so old caches rebuild. */\nexport const INDEX_MANIFEST_VERSION = 2 as const;\n\n/** One indexed file. Everything here is reproducible from the file contents. */\nexport type IndexEntry = {\n /** Repo-relative POSIX path. */\n path: string;\n /** SHA-256 of the file contents; the authoritative change-detection signal. */\n hash: string;\n /** Byte size at index time. */\n size: number;\n /**\n * File mtime/ctime observed when `hash` was taken. Recorded only once the file\n * is older than the racy window, so a matching size+mtime+ctime is a reliable\n * \"unchanged\" shortcut; when absent the file is re-hashed.\n */\n mtimeMs?: number;\n ctimeMs?: number;\n /** Coarse language tag derived from the extension (ts, js, md, py, …). */\n language: string;\n /** Line count (excluding a single trailing newline). */\n lines: number;\n /** Best-effort top-level/exported symbol names. Empty for non-code files. */\n symbols: string[];\n /**\n * Optional richer symbol table (name/kind/range/signature) for code files —\n * the basis for symbol-level context (Roadmap N.3). Absent for non-code files\n * and older manifests, so consumers must treat it as optional.\n */\n symbolTable?: SymbolSpan[];\n /** Deterministic one-line summary. */\n summary: string;\n /** ISO timestamp of when this entry was (re)computed. */\n indexedAt: string;\n};\n\nexport type IndexManifest = {\n version: typeof INDEX_MANIFEST_VERSION;\n generatedAt: string;\n /** Keyed by repo-relative POSIX path. */\n entries: Record<string, IndexEntry>;\n};\n\nexport type IndexError = {\n path: string;\n error: string;\n};\n\n/** Outcome of one indexing pass. Counts are disjoint where noted. */\nexport type IndexResult = {\n scanned: number;\n added: number;\n changed: number;\n /** Unchanged files whose summary was reused (the incremental fast path). */\n unchanged: number;\n /** Entries dropped because the file no longer exists or is now ignored. */\n removed: number;\n /** Files skipped as too large or unreadable (isolated, non-fatal). */\n skipped: number;\n errors: IndexError[];\n durationMs: number;\n totalIndexed: number;\n architecture: {\n source: \"cache\" | \"incremental\" | \"rebuild\" | \"fallback\";\n readFiles: number;\n reusedFiles: number;\n };\n};\n","import { spawn } from \"node:child_process\";\nimport { readdir } from \"node:fs/promises\";\nimport { extname, join, relative } from \"node:path\";\nimport { SUPPORTED_EXTENSIONS } from \"./summary.js\";\n\n/**\n * Enumerate the repo-relative POSIX paths the indexer should consider.\n *\n * `.gitignore` respect is delegated to git itself: `git ls-files` lists tracked\n * plus untracked-but-not-ignored files, which is exactly the set a developer\n * expects to be \"in the project\". When the directory is not a git repository we\n * fall back to a plain walk with a conservative ignore list so indexing still\n * works on partially configured repos.\n *\n * CodeBuddy's own generated artifacts (`.codebuddy/`, `graphify-out/`) are\n * always excluded — the index does not index itself.\n */\n\nconst FALLBACK_IGNORED_DIRECTORIES = new Set([\n \".git\",\n \"node_modules\",\n \"dist\",\n \"build\",\n \"coverage\",\n \".codebuddy\",\n \"graphify-out\",\n \".next\",\n \".turbo\",\n \"out\",\n]);\n\nexport async function listIndexableFiles(repositoryRoot: string): Promise<string[]> {\n const tracked = await gitListFiles(repositoryRoot);\n const paths = tracked ?? (await walkFiles(repositoryRoot));\n return paths\n .map(normalize)\n .filter((path) => !path.startsWith(\".codebuddy/\") && !path.startsWith(\"graphify-out/\"))\n .filter((path) => SUPPORTED_EXTENSIONS.has(extname(path).toLowerCase()))\n .sort();\n}\n\n/** Returns null when the directory is not a git repository. */\nasync function gitListFiles(repositoryRoot: string): Promise<string[] | null> {\n const result = await runGit(repositoryRoot, [\n \"ls-files\",\n \"--cached\",\n \"--others\",\n \"--exclude-standard\",\n ]);\n if (result.code !== 0) return null;\n return result.stdout\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean);\n}\n\nasync function walkFiles(root: string): Promise<string[]> {\n const files: string[] = [];\n const walk = async (directory: string): Promise<void> => {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const absolute = join(directory, entry.name);\n if (entry.isDirectory()) {\n if (FALLBACK_IGNORED_DIRECTORIES.has(entry.name)) continue;\n await walk(absolute);\n continue;\n }\n if (entry.isFile()) files.push(relative(root, absolute));\n }\n };\n await walk(root);\n return files;\n}\n\nfunction runGit(\n cwd: string,\n args: string[],\n): Promise<{ code: number; stdout: string; stderr: string }> {\n return new Promise((resolveResult) => {\n const child = spawn(\"git\", args, { cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n // A missing git binary should degrade to the walk fallback, not crash.\n child.on(\"error\", () => resolveResult({ code: -1, stdout: \"\", stderr: \"git unavailable\" }));\n child.on(\"close\", (code) => resolveResult({ code: code ?? 0, stdout, stderr }));\n });\n}\n\nfunction normalize(path: string): string {\n return path.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n","import { extname } from \"node:path\";\n\n/**\n * Deterministic file summarization. No LLM, no network — the same file always\n * produces the same summary, which keeps the index stable across runs and easy\n * to diff. Symbol extraction is intentionally best-effort regex over source\n * text: good enough to orient an agent, cheap enough to run on every save.\n */\n\nconst LANGUAGE_BY_EXTENSION: Record<string, string> = {\n \".ts\": \"ts\",\n \".tsx\": \"tsx\",\n \".js\": \"js\",\n \".jsx\": \"jsx\",\n \".mjs\": \"js\",\n \".cjs\": \"js\",\n \".json\": \"json\",\n \".md\": \"md\",\n \".mdx\": \"md\",\n \".yaml\": \"yaml\",\n \".yml\": \"yaml\",\n \".toml\": \"toml\",\n \".css\": \"css\",\n \".scss\": \"scss\",\n \".html\": \"html\",\n \".py\": \"py\",\n \".go\": \"go\",\n \".rs\": \"rust\",\n \".java\": \"java\",\n \".rb\": \"ruby\",\n \".php\": \"php\",\n \".sh\": \"shell\",\n \".sql\": \"sql\",\n \".txt\": \"text\",\n};\n\n/** Extensions the indexer will read and summarize. */\nexport const SUPPORTED_EXTENSIONS = new Set(Object.keys(LANGUAGE_BY_EXTENSION));\n\nconst CODE_LANGUAGES = new Set([\"ts\", \"tsx\", \"js\", \"jsx\"]);\nconst SYMBOL_LIMIT = 8;\nconst SUMMARY_COMMENT_MAX = 80;\n\nexport function languageFor(path: string): string {\n return LANGUAGE_BY_EXTENSION[extname(path).toLowerCase()] ?? \"text\";\n}\n\nexport type FileSummary = {\n language: string;\n lines: number;\n symbols: string[];\n summary: string;\n};\n\nexport function summarizeSource(path: string, content: string): FileSummary {\n const language = languageFor(path);\n const lines = countLines(content);\n const symbols = CODE_LANGUAGES.has(language) ? extractSymbols(content) : [];\n const summary = composeSummary(language, lines, symbols, content);\n return { language, lines, symbols, summary };\n}\n\nfunction countLines(content: string): number {\n if (content.length === 0) return 0;\n return content.replace(/\\r/g, \"\").replace(/\\n$/, \"\").split(\"\\n\").length;\n}\n\n/**\n * Pull exported/top-level symbol names from JS/TS source. Covers the common\n * `export <kind> Name` forms, `export default function Name`, and named\n * re-export braces. Order-preserving and de-duplicated.\n */\nexport function extractSymbols(content: string): string[] {\n const names: string[] = [];\n const push = (name: string | undefined) => {\n const trimmed = name?.trim();\n if (trimmed && /^[A-Za-z_$][\\w$]*$/.test(trimmed) && !names.includes(trimmed)) {\n names.push(trimmed);\n }\n };\n\n const declaration =\n /^export\\s+(?:default\\s+)?(?:async\\s+)?(?:abstract\\s+)?(?:const|let|var|function|class|interface|type|enum)\\s+([A-Za-z_$][\\w$]*)/gm;\n for (const match of content.matchAll(declaration)) push(match[1]);\n\n const namedExports = /^export\\s*\\{([^}]*)\\}/gm;\n for (const match of content.matchAll(namedExports)) {\n for (const part of (match[1] ?? \"\").split(\",\")) {\n // Handle `foo as bar` — record the exported name (`bar`).\n const alias = part.includes(\" as \") ? part.split(\" as \").pop() : part;\n push(alias);\n }\n }\n\n return names.slice(0, SYMBOL_LIMIT);\n}\n\nfunction composeSummary(\n language: string,\n lines: number,\n symbols: string[],\n content: string,\n): string {\n const head = `${language} • ${lines} line${lines === 1 ? \"\" : \"s\"}`;\n if (symbols.length > 0) return `${head} • exports ${symbols.join(\", \")}`;\n const comment = leadingComment(content);\n if (comment) return `${head} • ${comment}`;\n return head;\n}\n\n/** First meaningful line of a leading comment block, if any. */\nfunction leadingComment(content: string): string | null {\n for (const raw of content.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (line === \"\") continue;\n const stripped = line\n .replace(/^\\/\\/+/, \"\")\n .replace(/^\\/\\*+/, \"\")\n .replace(/\\*+\\/$/, \"\")\n .replace(/^[#*]+/, \"\")\n .trim();\n if (\n line.startsWith(\"//\") ||\n line.startsWith(\"/*\") ||\n line.startsWith(\"#\") ||\n line.startsWith(\"*\")\n ) {\n if (stripped) return truncate(stripped, SUMMARY_COMMENT_MAX);\n continue;\n }\n // First non-blank line is not a comment: nothing to summarize.\n return null;\n }\n return null;\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n","import { execFile } from \"node:child_process\";\nimport { mkdir, mkdtemp, readFile, rm, stat, utimes } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { loadFreshIndex } from \"../indexer/engine.js\";\nimport { writeGraphSourceSnapshot } from \"../map/graph-freshness.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport type GraphifyCommandRunner = (\n command: string,\n args: string[],\n cwd: string,\n) => Promise<{ stdout: string; stderr: string }>;\n\nexport type GraphifySetupOptions = {\n repositoryRoot: string;\n run?: GraphifyCommandRunner;\n};\n\nexport type GraphifySetupResult = {\n available: boolean;\n installed: boolean;\n generated: boolean;\n graphPath: string;\n message: string;\n};\n\nexport type GenerateGraphifyOptions = {\n repositoryRoot: string;\n graphPath?: string;\n run?: GraphifyCommandRunner;\n};\n\nexport type GenerateGraphifyResult = {\n graphPath: string;\n generated: boolean;\n nodes: number;\n edges: number;\n};\n\nconst DEFAULT_GRAPH_PATH = \"graphify-out/graph.json\";\n\n/** Detect, install, and register Graphify without making it a CodeBuddy dependency. */\nexport async function setupGraphify(options: GraphifySetupOptions): Promise<GraphifySetupResult> {\n const run = options.run ?? runCommand;\n let available = await commandWorks(run, \"graphify\", [\"--version\"], options.repositoryRoot);\n let installed = false;\n\n if (!available) {\n const installers = [\n [\"uv\", [\"tool\", \"install\", \"graphifyy\"]],\n [\"pipx\", [\"install\", \"graphifyy\"]],\n [\"python\", [\"-m\", \"pip\", \"install\", \"--user\", \"graphifyy\"]],\n ] as const;\n for (const [command, args] of installers) {\n try {\n await run(command, [...args], options.repositoryRoot);\n available = await commandWorks(run, \"graphify\", [\"--version\"], options.repositoryRoot);\n if (available) {\n installed = true;\n break;\n }\n } catch {\n // Try the next supported local installer.\n }\n }\n }\n\n if (!available) {\n return {\n available: false,\n installed,\n generated: false,\n graphPath: DEFAULT_GRAPH_PATH,\n message:\n \"Graphify was selected but is not available. Install graphifyy with uv/pipx, then run `codebuddy graphify index`.\",\n };\n }\n\n try {\n await run(\"graphify\", [\"install\"], options.repositoryRoot).catch(() => undefined);\n const graph = await generateGraphifyGraph({ repositoryRoot: options.repositoryRoot, run });\n return {\n available: true,\n installed,\n generated: true,\n graphPath: graph.graphPath,\n message: `Graphify graph generated (${graph.nodes} nodes, ${graph.edges} edges).`,\n };\n } catch (error) {\n return {\n available: true,\n installed,\n generated: false,\n graphPath: DEFAULT_GRAPH_PATH,\n message: `Graphify is installed but its graph could not be generated: ${(error as Error).message}`,\n };\n }\n}\n\nasync function commandWorks(\n run: GraphifyCommandRunner,\n command: string,\n args: string[],\n cwd: string,\n): Promise<boolean> {\n try {\n await run(command, args, cwd);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function runCommand(\n command: string,\n args: string[],\n cwd: string,\n): Promise<{ stdout: string; stderr: string }> {\n return execFileAsync(command, args, { cwd });\n}\n\nexport async function graphifyGraphExists(\n repositoryRoot: string,\n graphPath = DEFAULT_GRAPH_PATH,\n): Promise<boolean> {\n try {\n const parsed = await readGraphFile(resolveGraphPath(repositoryRoot, graphPath));\n return parsed.nodes > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a graph with Graphify itself. Staging is used for first builds and\n * invalid graphs so a failed extractor can never replace a usable graph.\n */\nexport async function generateGraphifyGraph(\n options: GenerateGraphifyOptions,\n): Promise<GenerateGraphifyResult> {\n const run = options.run ?? runCommand;\n const repositoryRoot = resolve(options.repositoryRoot);\n const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;\n const destination = resolveGraphPath(repositoryRoot, graphPath);\n await mkdir(dirname(destination), { recursive: true });\n await ignoreGeneratedGraphDirectory(dirname(destination));\n\n const { manifest: sourceSnapshot } = await loadFreshIndex(repositoryRoot);\n const current = await readGraphFileIfPresent(destination);\n const isDefaultPath = graphPath === DEFAULT_GRAPH_PATH;\n if (current && isDefaultPath) {\n const backup = await readFile(destination, \"utf8\");\n const backupStat = await stat(destination);\n try {\n await run(\"graphify\", [\"update\", repositoryRoot, \"--force\", \"--no-cluster\"], repositoryRoot);\n const updated = await readGraphFile(destination);\n await writeGraphSourceSnapshot(destination, sourceSnapshot);\n return { graphPath, generated: true, ...updated };\n } catch (error) {\n await writeAtomic(destination, backup);\n await utimes(destination, backupStat.atime, backupStat.mtime);\n throw new Error(\n `Graphify update failed; the previous graph was preserved: ${(error as Error).message}`,\n );\n }\n }\n\n const stagingRoot = await mkdtemp(join(tmpdir(), \"codebuddy-graphify-\"));\n try {\n await run(\n \"graphify\",\n [\"extract\", repositoryRoot, \"--out\", stagingRoot, \"--code-only\", \"--no-cluster\"],\n repositoryRoot,\n );\n const stagedPath = join(stagingRoot, DEFAULT_GRAPH_PATH);\n const staged = await readGraphFile(stagedPath);\n await writeAtomic(destination, await readFile(stagedPath, \"utf8\"));\n await writeGraphSourceSnapshot(destination, sourceSnapshot);\n return { graphPath, generated: true, ...staged };\n } finally {\n await rm(stagingRoot, { recursive: true, force: true });\n }\n}\n\n/**\n * The graph directory is derived output CodeBuddy writes itself, so it is\n * self-ignoring like `.codebuddy/`. An existing .gitignore is never rewritten:\n * a project that deliberately commits its graph keeps doing so.\n */\nasync function ignoreGeneratedGraphDirectory(directory: string): Promise<void> {\n const path = join(directory, \".gitignore\");\n try {\n await stat(path);\n } catch {\n await writeAtomic(path, \"# CodeBuddy-generated Graphify output (derived, rebuildable)\\n*\\n\");\n }\n}\n\nfunction resolveGraphPath(repositoryRoot: string, graphPath: string): string {\n const absolute = resolve(repositoryRoot, graphPath);\n const relativePath = relative(repositoryRoot, absolute);\n if (relativePath === \"\" || relativePath === \"..\" || relativePath.startsWith(`..${sep}`)) {\n throw new Error(`Graph path must be inside the project root: ${graphPath}`);\n }\n return absolute;\n}\n\nasync function readGraphFileIfPresent(\n path: string,\n): Promise<{ nodes: number; edges: number } | null> {\n try {\n return await readGraphFile(path);\n } catch {\n return null;\n }\n}\n\nasync function readGraphFile(path: string): Promise<{ nodes: number; edges: number }> {\n const info = await stat(path);\n if (!info.isFile() || info.size === 0)\n throw new Error(`Graphify graph is missing or empty: ${path}`);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n const nodes = arrayLength(raw.nodes ?? raw.entities);\n const edges = arrayLength(raw.edges ?? raw.links ?? raw.relationships);\n if (nodes === 0) throw new Error(`Graphify graph contains no nodes: ${path}`);\n return { nodes, edges };\n}\n\nfunction arrayLength(value: unknown): number {\n return Array.isArray(value) ? value.length : 0;\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport type { IndexManifest } from \"../indexer/types.js\";\n\nconst digest = (value: string) => createHash(\"sha256\").update(value).digest(\"hex\");\n\n/** Record the source snapshot from BEFORE extraction, so edits during extraction fail closed. */\nexport async function writeGraphSourceSnapshot(\n path: string,\n manifest: IndexManifest,\n): Promise<void> {\n await writeAtomic(\n `${path}.sources.json`,\n JSON.stringify({\n version: 1,\n graphHash: digest(await readFile(path, \"utf8\")),\n sources: Object.fromEntries(\n Object.values(manifest.entries).map((entry) => [entry.path, entry.hash]),\n ),\n }),\n );\n}\n\n/** Missing = legacy graph. Invalid/mismatched = trust no endpoints until rebuilt. */\nexport async function readGraphSourceSnapshot(\n path: string,\n): Promise<Record<string, string> | null> {\n let raw: string;\n try {\n raw = await readFile(`${path}.sources.json`, \"utf8\");\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"ENOENT\" ? null : {};\n }\n try {\n const snapshot = JSON.parse(raw);\n if (\n snapshot.version !== 1 ||\n snapshot.graphHash !== digest(await readFile(path, \"utf8\")) ||\n !snapshot.sources ||\n typeof snapshot.sources !== \"object\" ||\n Array.isArray(snapshot.sources)\n )\n return {};\n return Object.fromEntries(\n Object.entries(snapshot.sources).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n );\n } catch {\n return {};\n }\n}\n","import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { isAbsolute, join, relative } from \"node:path\";\nimport { readConfigFile } from \"../core/config-file.js\";\nimport type { IndexManifest } from \"../indexer/types.js\";\nimport { graphifyGraphExists } from \"../integrations/graphify.js\";\nimport { loadArchitectureMap } from \"../map/cache.js\";\nimport { readGraphSourceSnapshot } from \"../map/graph-freshness.js\";\nimport { loadGraphifyMap } from \"../map/graphify.js\";\nimport type { ArchitectureMap } from \"../map/types.js\";\nimport type { RetrievalHit, ZvecCandidates } from \"../retrieval/zvec.js\";\nimport { extractTaskAnchors } from \"./anchors.js\";\n\n/**\n * Task-path discovery: rank indexed files against a natural-language task,\n * with calibrated abstention. Kept apart from the context engine so the lean\n * hook can use it without loading plan, risk, worklog or memory code.\n */\n\n/** Most files the engine describes (symbols, excerpts) for one request. */\nexport const TASK_PATH_LIMIT = 10;\n/** Task-discovered targets: a wrong file costs the agent a read, a missing one costs nothing. */\nconst TASK_FILES = 3;\n/** Multi-file expansion: neighbours of the top `seeds` targets, at most `files`. */\nconst EXPANSION = { seeds: 2, files: 1 } as const;\n\nconst MAX_EVIDENCE_BYTES = 512_000;\n\n/** Current file text, or null when it is missing, oversized, or binary. */\nexport async function readBoundedSource(\n repositoryRoot: string,\n path: string,\n): Promise<string | null> {\n try {\n const absolute = join(repositoryRoot, path);\n if ((await stat(absolute)).size > MAX_EVIDENCE_BYTES) return null;\n const source = await readFile(absolute, \"utf8\");\n return source.includes(\"\\0\") ? null : source;\n } catch {\n return null;\n }\n}\n\n/**\n * Rank cached file summaries for a task when the agent has not named files.\n * Explicit anchors in the task (paths, `path:line`, stack frames, quoted\n * error strings) come first; ranking fills the remaining slots.\n */\nexport async function discoverTaskPaths(\n repositoryRoot: string,\n task: string,\n manifest: IndexManifest,\n mapSource: Promise<ArchitectureMap>,\n options: DiscoveryOptions = {},\n): Promise<DiscoveredPaths> {\n const anchors = await extractTaskAnchors(\n repositoryRoot,\n task,\n new Set(Object.keys(manifest.entries)),\n );\n if (anchors.length === 0)\n return rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);\n const anchored = anchors.map((anchor) => anchor.path);\n const hits: RetrievalHit[] = anchors.flatMap((anchor) =>\n anchor.line === null\n ? []\n : [\n {\n path: anchor.path,\n startLine: anchor.line,\n endLine: anchor.line,\n rank: 0,\n source: \"anchor\" as const,\n freshness: \"unverified\" as const,\n },\n ],\n );\n const explain = `retrieval: anchors (${anchors\n .map(\n (anchor) => `${anchor.kind} ${anchor.path}${anchor.line === null ? \"\" : `:${anchor.line}`}`,\n )\n .join(\", \")})`;\n if (anchored.length >= TASK_FILES) return { paths: anchored, hits, explain: [explain] };\n const ranked = await rankTaskFiles(repositoryRoot, task, manifest, mapSource, options);\n return {\n ...(ranked.symbols ? { symbols: ranked.symbols } : {}),\n paths: [...anchored, ...ranked.paths.filter((path) => !anchored.includes(path))].slice(\n 0,\n TASK_FILES,\n ),\n hits: [...hits, ...ranked.hits.filter((hit) => !anchored.includes(hit.path))],\n explain: [explain, ...ranked.explain],\n };\n}\n\n/**\n * Index entries with their BYOK model summaries attached as `llmText` (when\n * the layer and its summaries feature are on). Loaded on demand so a repo\n * without the layer never imports it.\n */\nasync function withLlmSummaries(\n repositoryRoot: string,\n manifest: IndexManifest,\n): Promise<Array<IndexManifest[\"entries\"][string] & { llmText?: string }>> {\n const entries = Object.values(manifest.entries);\n try {\n const { resolveLlmConfig } = await import(\"../llm/config.js\");\n const config = await resolveLlmConfig(repositoryRoot);\n if (!config.enabled || !config.features.summaries) return entries;\n const { loadLlmSummaries } = await import(\"../llm/summaries.js\");\n const summaries = await loadLlmSummaries(repositoryRoot, manifest);\n if (summaries.size === 0) return entries;\n return entries.map((entry) => {\n const summary = summaries.get(entry.path);\n return summary\n ? { ...entry, llmText: `${summary.summary} ${Object.values(summary.symbols).join(\" \")}` }\n : entry;\n });\n } catch {\n return entries;\n }\n}\n\nexport type DiscoveryOptions = {\n semantic?: boolean;\n /**\n * Epoch ms by which the BYOK rerank must have answered (the caller's hook\n * budget). The call gets min(configured timeout, time left) measured right\n * before it is made; under 200 ms left it is skipped. Default: no deadline\n * beyond the configured timeout.\n */\n llmDeadlineAt?: number;\n};\n\nexport type DiscoveredPaths = {\n paths: string[];\n hits: RetrievalHit[];\n explain: string[];\n /** Symbols the BYOK rerank named per file, validated against the symbol table. */\n symbols?: Record<string, string[]>;\n};\n\nasync function rankTaskFiles(\n repositoryRoot: string,\n task: string,\n manifest: IndexManifest,\n mapSource: Promise<ArchitectureMap>,\n options: DiscoveryOptions,\n): Promise<DiscoveredPaths> {\n const entries = await withLlmSummaries(repositoryRoot, manifest);\n const wantsTests = /\\b(tests?|spec)\\b/i.test(task);\n // An exact code identifier names its file better than any embedding: route\n // it lexically to the declaring files and skip vector noise.\n const identifiers = identifierTokens(task).filter((token) => CODE_IDENTIFIER.test(token));\n if (identifiers.length > 0) {\n // Files declaring more of the named identifiers first.\n const exact = entries\n .filter((entry) => wantsTests || !TEST_PATH.test(entry.path))\n .map((entry) => ({\n path: entry.path,\n named: new Set(\n (entry.symbolTable ?? []).flatMap((symbol) =>\n symbol.name.split(\".\").filter((segment) => identifiers.includes(segment)),\n ),\n ).size,\n }))\n .filter((entry) => entry.named > 0)\n .sort((left, right) => right.named - left.named || left.path.localeCompare(right.path))\n .map((entry) => entry.path);\n if (exact.length > 0) {\n const evidence = new Map(\n scoreTaskPaths(entries, task)\n .filter((entry) => entry.score > 0)\n .map((entry) => [entry.path, entry.score]),\n );\n const expansion = expandAlongImports(exact, await mapSource, evidence, task, {\n allowGraphOnly: true,\n });\n return {\n paths: expansion.paths.slice(0, TASK_FILES),\n hits: [],\n explain: [\n `retrieval: exact-symbol routing (${identifiers.slice(0, 5).join(\", \")})`,\n ...(expansion.explain ? [expansion.explain] : []),\n ],\n };\n }\n }\n // A named CLI command (`<bin> <command>`) routes to its command module,\n // ahead of keyword ranking; the logic it calls still comes from keywords.\n const commands = await commandFiles(repositoryRoot, task, entries);\n const scored = scoreTaskPaths(entries, task);\n const keyword = scored\n .filter((entry) => entry.score >= EVIDENCE_FLOOR.keywordScore)\n .map((entry) => entry.path);\n const weakKeyword = scored.length - keyword.length;\n // Loaded on demand so callers that opt out (the lean hook) never import it.\n const semantic: ZvecCandidates =\n options.semantic === false\n ? {\n status: \"disabled\",\n paths: [],\n hits: [],\n reason: \"semantic retrieval off\",\n stale: 0,\n changed: 0,\n }\n : await import(\"../retrieval/zvec.js\")\n .then(({ zvecCandidates }) => zvecCandidates(repositoryRoot, task))\n .catch(\n (): ZvecCandidates => ({\n status: \"failed\",\n paths: [],\n hits: [],\n reason: \"zvec-grep failed\",\n stale: 0,\n changed: 0,\n }),\n );\n // Lexical evidence for expansion: any task term, even below the floor.\n const evidence = new Map(\n scored.filter((entry) => entry.score > 0).map((entry) => [entry.path, entry.score]),\n );\n let ranked = keyword;\n let hits: RetrievalHit[] = [];\n const explain: string[] = [];\n if (semantic.status !== \"disabled\" && semantic.paths.length === 0) {\n explain.push(`retrieval: keyword ranking only (${semantic.reason})`);\n } else if (semantic.status !== \"disabled\") {\n // Keep zvec hits consistent with keyword rules: no tests unless asked for.\n // zg 0.2.x reports a rank, not a calibrated score, so a vector hit below the\n // keyword floor must share enough task terms with its current source.\n const terms = taskTerms(task);\n const vector: string[] = [];\n const partial: string[] = [];\n let uncorroborated = 0;\n for (const path of semantic.paths) {\n if (!wantsTests && TEST_PATH.test(path)) continue;\n if (keyword.includes(path)) vector.push(path);\n else if (\n await hasMatchedPassageEvidence(\n repositoryRoot,\n path,\n semantic.hits,\n terms,\n manifest.entries[path]?.symbolTable ?? [],\n )\n ) {\n const source = (await readBoundedSource(repositoryRoot, path)) ?? \"\";\n if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) vector.push(path);\n else partial.push(path);\n if (!evidence.has(path)) evidence.set(path, 0);\n } else uncorroborated++;\n }\n // The floor admits files; it does not reorder them. A weak keyword match\n // still votes in fusion when its vector hit was corroborated.\n ranked = rankEvidenceCandidates(\n scored.map((entry) => entry.path),\n keyword,\n vector,\n partial,\n );\n hits = semantic.hits.filter((hit) => vector.includes(hit.path) || partial.includes(hit.path));\n explain.push(\n `retrieval: keyword ranking fused with ${semantic.reason}` +\n (uncorroborated > 0 ? `; ${uncorroborated} uncorroborated vector file(s) dropped` : \"\"),\n );\n }\n\n let symbols: Record<string, string[]> | undefined;\n const reranked = await llmRerank({\n repositoryRoot,\n task,\n manifest,\n entries,\n scored,\n semantic: semantic.paths,\n ranked,\n wantsTests,\n deadlineAt: options.llmDeadlineAt,\n });\n if (reranked) {\n explain.push(reranked.explain);\n ranked = reranked.ranked;\n symbols = reranked.symbols;\n }\n\n // Calibrated abstention (#45): return nothing rather than code when no\n // *source* file reaches the evidence floor; docs and config alone (an eval\n // dataset quoting the prompt, say) never justify sending code.\n if (!ranked.some((path) => !NON_SOURCE_PATH.test(path))) {\n explain.push(\n \"abstention: no source file reached the evidence floor (\" +\n (ranked.length > 0\n ? \"only docs/config did\"\n : `best keyword score ${scored[0]?.score ?? 0} < ${EVIDENCE_FLOOR.keywordScore}`) +\n (commands.length > 0\n ? \"); only the named CLI command returned\"\n : \"); no task targets returned\"),\n );\n return { paths: commands, hits: [], explain };\n }\n if (weakKeyword > 0) {\n explain.push(\n `abstention: ${weakKeyword} weak keyword candidate(s) below the evidence floor (score < ${EVIDENCE_FLOOR.keywordScore}) dropped`,\n );\n }\n const expansion = expandAlongImports(ranked, await mapSource, evidence, task);\n if (expansion.explain) explain.push(expansion.explain);\n if (commands.length > 0) {\n explain.push(`retrieval: CLI command routing (${commands.join(\", \")})`);\n }\n const paths = [...new Set([...commands, ...expansion.paths])];\n if (paths.length > TASK_FILES) {\n explain.push(\n `abstention: ${paths.length - TASK_FILES} lower-ranked candidate(s) beyond the top ${TASK_FILES} dropped`,\n );\n }\n return {\n paths: paths.slice(0, TASK_FILES),\n hits,\n explain,\n ...(symbols && Object.keys(symbols).length > 0 ? { symbols } : {}),\n };\n}\n\n/**\n * BYOK rerank of the keyword/fused path. Returns null when the layer is off.\n * The model may reorder admitted files, abstain, or promote a below-floor\n * candidate that has at least two task terms in its model summary or current\n * source (the vector corroboration rule); anything else it names is dropped.\n */\n/** A rerank call shorter than this cannot answer in time; skip it. */\nconst MIN_RERANK_MS = 200;\n\nasync function llmRerank(input: {\n repositoryRoot: string;\n task: string;\n manifest: IndexManifest;\n entries: Array<{ path: string; summary: string; llmText?: string }>;\n scored: Array<{ path: string; score: number; order: number }>;\n semantic: string[];\n ranked: string[];\n wantsTests: boolean;\n deadlineAt: number | undefined;\n}): Promise<{ ranked: string[]; explain: string; symbols: Record<string, string[]> } | null> {\n let config: Awaited<ReturnType<typeof import(\"../llm/config.js\").resolveLlmConfig>>;\n try {\n config = await (await import(\"../llm/config.js\")).resolveLlmConfig(input.repositoryRoot);\n } catch {\n return null;\n }\n if (!config.enabled || !config.features.rerank) return null;\n const skipped = (reason: string) => ({\n ranked: input.ranked,\n explain: `retrieval: llm rerank skipped (${reason})`,\n symbols: {},\n });\n const usable = (path: string) => input.wantsTests || !TEST_PATH.test(path);\n const candidatePaths = [\n ...new Set([\n ...input.ranked,\n ...input.scored\n .map((entry) => entry.path)\n .filter(usable)\n .slice(0, 12),\n ...input.semantic.filter(usable).slice(0, 8),\n ]),\n ].slice(0, 15);\n if (candidatePaths.length === 0) return null;\n const byPath = new Map(input.entries.map((entry) => [entry.path, entry]));\n const deadlineMs = Math.min(\n config.timeoutMs,\n input.deadlineAt === undefined ? config.timeoutMs : input.deadlineAt - Date.now(),\n );\n if (deadlineMs < MIN_RERANK_MS) return skipped(\"no time left in the hook budget\");\n const { rerankCandidates } = await import(\"../llm/rerank.js\");\n const result = await rerankCandidates(\n input.repositoryRoot,\n config,\n input.task,\n candidatePaths.map((path) => ({\n path,\n summary: byPath.get(path)?.llmText || byPath.get(path)?.summary || \"\",\n signatures: (input.manifest.entries[path]?.symbolTable ?? []).map((s) => s.signature),\n })),\n deadlineMs,\n ).catch(() => ({ ok: false as const, reason: \"invalid\" as const }));\n if (!result.ok) return skipped(result.reason);\n const label = `${config.provider} ${config.model}, ${result.cached ? \"cached\" : `${result.latencyMs} ms`}`;\n if (result.value.abstain) {\n return {\n ranked: [],\n explain: `retrieval: llm rerank (${label}, model abstained over ${candidatePaths.length} candidates)`,\n symbols: {},\n };\n }\n const terms = taskTerms(input.task);\n const picks: string[] = [];\n const symbols: Record<string, string[]> = {};\n let promoted = 0;\n for (const file of result.value.files) {\n if (!input.ranked.includes(file.path)) {\n const source = (await readBoundedSource(input.repositoryRoot, file.path)) ?? \"\";\n const llmText = byPath.get(file.path)?.llmText ?? \"\";\n if (\n Math.max(countTerms(llmText, terms), countTerms(source, terms)) < EVIDENCE_FLOOR.vectorTerms\n )\n continue;\n promoted++;\n }\n picks.push(file.path);\n const known = new Set(\n (input.manifest.entries[file.path]?.symbolTable ?? []).map((s) => s.name),\n );\n const named = file.symbols.filter((name) => known.has(name));\n if (named.length > 0) symbols[file.path] = named;\n }\n return {\n ranked: [...picks, ...input.ranked.filter((path) => !picks.includes(path))],\n explain:\n `retrieval: llm rerank (${label}, ${picks.length} of ${candidatePaths.length} candidates` +\n (promoted > 0 ? `, ${promoted} promoted` : \"\") +\n \")\",\n symbols,\n };\n}\n\n/**\n * Source files for CLI commands the task names as `<bin> <command> [sub]`,\n * where `<bin>` is a `bin` entry of the repository's package.json: a file in a\n * `cli`/`cmd`/`commands` directory whose stem is `command` or `command-sub`.\n */\nasync function commandFiles(\n repositoryRoot: string,\n task: string,\n entries: Array<{ path: string }>,\n): Promise<string[]> {\n const bins = await packageBins(repositoryRoot);\n if (bins.length === 0) return [];\n const stems = new Set<string>();\n const pattern = new RegExp(\n `\\\\b(?:${bins.map(escapeRegExp).join(\"|\")})\\\\s+([a-z][\\\\w-]*)(?:\\\\s+([a-z][\\\\w-]*))?`,\n \"gi\",\n );\n for (const [, command, sub] of task.matchAll(pattern)) {\n if (!command) continue;\n stems.add(command.toLowerCase());\n if (sub) stems.add(`${command}-${sub}`.toLowerCase());\n }\n if (stems.size === 0) return [];\n const matches = entries\n .map((entry) => entry.path)\n .filter((path) => {\n if (TEST_PATH.test(path) || NON_SOURCE_PATH.test(path) || !COMMAND_DIR.test(path)) {\n return false;\n }\n const name = path.slice(path.lastIndexOf(\"/\") + 1);\n return stems.has(name.slice(0, name.lastIndexOf(\".\")).toLowerCase());\n });\n // `command-sub` names the command more precisely than `command`.\n return matches\n .sort((left, right) => right.length - left.length || left.localeCompare(right))\n .slice(0, TASK_FILES);\n}\n\nconst COMMAND_DIR = /(^|\\/)(cli|cmd|commands?)\\//;\n\nasync function packageBins(repositoryRoot: string): Promise<string[]> {\n try {\n const manifest = JSON.parse(await readFile(join(repositoryRoot, \"package.json\"), \"utf8\")) as {\n name?: unknown;\n bin?: unknown;\n };\n if (typeof manifest.bin === \"string\" && typeof manifest.name === \"string\") {\n return [manifest.name.slice(manifest.name.lastIndexOf(\"/\") + 1)];\n }\n if (manifest.bin && typeof manifest.bin === \"object\") return Object.keys(manifest.bin);\n } catch {\n // No (readable) package.json: no CLI routing.\n }\n return [];\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/** Task wording that asks for a path through several files. */\nconst MULTI_FILE_TASK =\n /\\b(trace|follow|flows?|through|across|between|end[- ]to[- ]end|pipeline)\\b|\\bfrom\\b.+\\bto\\b/i;\n\n/**\n * Bounded multi-file expansion (#45). Only when the task asks for a path\n * through several files: import neighbours of the top targets that carry\n * lexical evidence (at least one task term, even below the floor) get the\n * import edge credited as one more term, up to `expansionFiles` of them, most\n * linked first. Each is placed before the first lower-scoring non-seed target,\n * so it never displaces a file with stronger evidence.\n */\nfunction expandAlongImports(\n ranked: string[],\n map: ArchitectureMap,\n evidence: Map<string, number>,\n task: string,\n options: { allowGraphOnly?: boolean } = {},\n): { paths: string[]; explain: string | null } {\n if (!MULTI_FILE_TASK.test(task)) return { paths: ranked, explain: null };\n const seeds = ranked.slice(0, EXPANSION.seeds);\n const links = new Map<string, Set<string>>();\n const graphOnly = new Map<string, number>();\n for (const edge of map.edges) {\n for (const [seed, other] of [\n [edge.from, edge.to],\n [edge.to, edge.from],\n ] as const) {\n if (!seeds.includes(seed) || seeds.includes(other)) continue;\n if (!evidence.has(other)) {\n if (\n !options.allowGraphOnly ||\n edge.from !== seed ||\n TEST_PATH.test(other) ||\n NON_SOURCE_PATH.test(other)\n )\n continue;\n graphOnly.set(\n other,\n Math.max(\n graphOnly.get(other) ?? 0,\n edge.symbols?.length ? 2 : edge.kind === \"import\" ? 1 : 0,\n ),\n );\n }\n links.set(other, (links.get(other) ?? new Set()).add(seed));\n }\n }\n const scoreOf = (path: string) => evidence.get(path) ?? 0;\n const added = [...links]\n .sort(\n ([left, a], [right, b]) =>\n b.size - a.size ||\n scoreOf(right) - scoreOf(left) ||\n (graphOnly.get(right) ?? 0) - (graphOnly.get(left) ?? 0) ||\n left.localeCompare(right),\n )\n .slice(0, EXPANSION.files)\n .map(([path]) => path);\n if (added.length === 0) return { paths: ranked, explain: null };\n const paths = ranked.filter((path) => !added.includes(path));\n for (const path of added) {\n const credited = scoreOf(path) + 1;\n const at = paths.findIndex(\n (other, index) => index >= seeds.length && scoreOf(other) < credited,\n );\n paths.splice(at < 0 ? paths.length : at, 0, path);\n }\n return {\n paths,\n explain:\n `expansion: ${added.length} import neighbour(s) of the top ${seeds.length} target(s) ranked with the import edge credited as a task term (${added.join(\", \")}; multi-file task wording)` +\n (added.some((path) => graphOnly.has(path)) ? \"; graph-only import bridge\" : \"\"),\n };\n}\n\n/** Partial semantic matches fill available slots after established evidence. */\nexport function rankEvidenceCandidates(\n lexical: string[],\n admittedKeywords: string[],\n vector: string[],\n partial: string[],\n): string[] {\n const admitted = new Set([...admittedKeywords, ...vector]);\n const established = fuseRankings([lexical, vector]).filter((path) => admitted.has(path));\n return [...new Set([...established, ...partial])];\n}\n\n/** Reciprocal rank fusion: robust to the two lists using unrelated score scales. */\nfunction fuseRankings(lists: string[][], k = 60): string[] {\n const scores = new Map<string, number>();\n for (const list of lists) {\n list.forEach((path, index) => {\n scores.set(path, (scores.get(path) ?? 0) + 1 / (k + index + 1));\n });\n }\n return [...scores]\n .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))\n .map(([path]) => path);\n}\n\nconst TASK_STOPWORDS = new Set(\n \"the and for are was were how does did which where when what why who with without from into onto that this these those there their them then than have has had not but can could should would will its it's our your you all any each some such only also just like make makes made use uses used get gets set sets new old way\".split(\n \" \",\n ),\n);\n/**\n * Minimum evidence for a task-discovered file (#45). `keywordScore` is the\n * `scoreTaskPaths` score: two distinct task terms, or one in the file name. A\n * vector hit below it needs `vectorTerms` distinct task terms in current source.\n * Calibrated on the `tune` split of evals/task-savings.json only: every expected\n * file retrieved there scored ≥ 2 (one 0.5 step of margin kept; no tune file\n * scoring 1 was expected) and expected vector-only files matched 2 and 3 terms.\n */\nexport const EVIDENCE_FLOOR = { keywordScore: 1.5, vectorTerms: 2 } as const;\n/** A zg chunk is evidence for its matched passage, not unrelated terms elsewhere in the file. */\nasync function hasMatchedPassageEvidence(\n repositoryRoot: string,\n path: string,\n hits: RetrievalHit[],\n terms: string[],\n symbols: Array<{ startLine: number; endLine: number }>,\n): Promise<boolean> {\n const source = await readBoundedSource(repositoryRoot, path);\n if (source === null) return false;\n // Keep the established file-evidence route: passage-only filtering lost\n // required files on the fused evaluation. Passages additionally admit narrow paraphrases.\n if (countTerms(source, terms) >= EVIDENCE_FLOOR.vectorTerms) return true;\n const lines = source.split(/\\r?\\n/);\n return hits.some((hit) => {\n if (hit.path !== path || hit.endLine > lines.length) return false;\n const passage = lines.slice(hit.startLine - 1, hit.endLine);\n const matched = countTerms(passage.join(\" \"), terms);\n return (\n matched >= EVIDENCE_FLOOR.vectorTerms ||\n (matched >= 1 &&\n terms.length === 2 &&\n hit.rank <= 3 &&\n symbols.some(\n (symbol) => hit.startLine <= symbol.endLine && hit.endLine >= symbol.startLine,\n ))\n );\n });\n}\nexport const TEST_PATH =\n /(^|\\/)(__tests__|__fixtures__|fixtures?|test|tests)\\/|\\.(test|spec)\\.[a-z]+$/;\nexport const NON_SOURCE_PATH =\n /(^|\\/)(docs?|\\.github)\\/|\\.(md|mdx|txt|ya?ml|json|lock)$|(^|\\/)CHANGELOG/i;\n\n/**\n * Rank indexed files against a natural-language task. Terms match whole words\n * (camelCase and snake_case split, simple plurals folded) in the path, summary\n * and symbols; a term in the file name counts extra. Tests and fixtures are\n * skipped unless the task is about tests, and docs/config files are\n * down-weighted so source files win ties.\n */\nexport function rankTaskPaths(entries: TaskPathEntry[], task: string): string[] {\n return scoreTaskPaths(entries, task).map((entry) => entry.path);\n}\n\ntype TaskPathEntry = {\n path: string;\n summary: string;\n symbols: string[];\n symbolTable?: Array<{ name: string }>;\n /** Model-written description (BYOK summaries): affects `order`, never `score`. */\n llmText?: string;\n};\n\n/** `rankTaskPaths` with each file's score, best first. */\nexport function scoreTaskPaths(\n entries: TaskPathEntry[],\n task: string,\n): Array<{ path: string; score: number; order: number }> {\n const terms = taskTerms(task);\n if (terms.length === 0) return [];\n const wantsTests = terms.some((term) => term === \"test\" || term === \"spec\");\n const candidates = entries.flatMap((entry) => {\n if (TEST_PATH.test(entry.path) && !wantsTests) return [];\n const words = new Set(\n splitWords(\n `${entry.path} ${entry.summary} ${entry.symbols.join(\" \")} ${entry.symbolTable?.map((symbol) => symbol.name).join(\" \") ?? \"\"}`,\n ),\n );\n const llmWords = new Set(entry.llmText ? splitWords(entry.llmText) : []);\n const fileWords = new Set(splitWords(entry.path.slice(entry.path.lastIndexOf(\"/\") + 1)));\n return [{ path: entry.path, words, llmWords, fileWords }];\n });\n // Rare task terms identify a file better than common ones, so ordering\n // weighs each matched term by inverse document frequency. The returned\n // `score` stays unweighted and ignores model-written text: the evidence\n // floor is calibrated on it.\n const idf = new Map(\n terms.map((term) => {\n const df = candidates.filter(\n (candidate) => candidate.words.has(term) || candidate.llmWords.has(term),\n ).length;\n return [term, Math.log((candidates.length + 1) / (df + 1)) + 1];\n }),\n );\n return candidates\n .flatMap(({ path, words, llmWords, fileWords }) => {\n let score = 0;\n let weighted = 0;\n for (const term of terms) {\n if (words.has(term)) {\n const points = fileWords.has(term) ? 1.5 : 1;\n score += points;\n weighted += points * (idf.get(term) ?? 1);\n } else if (llmWords.has(term)) {\n weighted += 0.5 * (idf.get(term) ?? 1);\n }\n }\n if (weighted === 0) return [];\n if (NON_SOURCE_PATH.test(path)) {\n score *= 0.5;\n weighted *= 0.5;\n }\n return [{ path, score, order: weighted }];\n })\n .sort(\n (left, right) =>\n right.order - left.order || right.score - left.score || left.path.localeCompare(right.path),\n );\n}\n\n/** Lowercase words with camelCase/snake_case split and a trailing plural \"s\" folded. */\nfunction splitWords(text: string): string[] {\n return text\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((word) => word.length > 2)\n .map((word) =>\n word.length > 3 && word.endsWith(\"s\") && !word.endsWith(\"ss\") ? word.slice(0, -1) : word,\n );\n}\n\n/** Task words that carry meaning (stopwords removed), as used for path ranking. */\nexport function taskTerms(task: string): string[] {\n return [...new Set(splitWords(task).filter((term) => !TASK_STOPWORDS.has(term)))];\n}\n\n/** How many distinct task terms appear in `text`. */\nexport function countTerms(text: string, terms: string[]): number {\n if (terms.length === 0) return 0;\n const words = new Set(splitWords(text));\n return terms.filter((term) => words.has(term)).length;\n}\n\n/** Looks like code, not prose: camelCase humps, snake_case, `$`, or dotted `Class.method`. */\nconst CODE_IDENTIFIER = /[a-z0-9][A-Z]|[_$.]/;\n\n/** Identifier-shaped task tokens; dotted ones also split (`Vault.lock` → `Vault`, `lock`). */\nexport function identifierTokens(task: string): string[] {\n const tokens = task.match(/[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)*/g) ?? [];\n return [...new Set(tokens.flatMap((token) => [token, ...token.split(\".\")]))];\n}\n\n/** How many segments of a (`Class.method`) symbol name the tokens name. */\nexport function namedSegments(name: string, tokens: string[]): number {\n return name.split(\".\").filter((segment) => tokens.includes(segment)).length;\n}\n\nexport async function loadProjectArchitectureMap(\n repositoryRoot: string,\n manifestSource: IndexManifest | Promise<IndexManifest>,\n): Promise<ArchitectureMap> {\n const [config, manifest] = await Promise.all([readConfigFile(repositoryRoot), manifestSource]);\n const builtIn = (await loadArchitectureMap(repositoryRoot, manifest)).map;\n if (\n config.graphify?.enabled &&\n (await graphifyGraphExists(repositoryRoot, config.graphify.graphPath))\n ) {\n try {\n const graphPath = join(\n repositoryRoot,\n config.graphify.graphPath ?? \"graphify-out/graph.json\",\n );\n const [graph, graphFile, sourceHashes] = await Promise.all([\n loadGraphifyMap(graphPath, repositoryRoot),\n stat(graphPath),\n readGraphSourceSnapshot(graphPath),\n ]);\n // A graph can be valid JSON yet lag the current source, or cover only a\n // subset of files. Keep fresh Graphify-only relations and symbol labels,\n // while retaining the current import scan for the rest of the project.\n const livePaths = new Set(\n (\n await Promise.all(\n graph.modules.map(async (module) => {\n try {\n const absolute = await realpath(join(repositoryRoot, module.path));\n const inside = relative(await realpath(repositoryRoot), absolute);\n if (inside.startsWith(\"..\") || isAbsolute(inside)) return null;\n const source = await stat(absolute);\n const fresh =\n sourceHashes === null\n ? source.mtimeMs <= graphFile.mtimeMs\n : sourceHashes[module.path] !== undefined &&\n sourceHashes[module.path] === manifest.entries[module.path]?.hash;\n return source.isFile() && fresh ? module.path : null;\n } catch {\n // Deleted files must not contribute graph relationships.\n return null;\n }\n }),\n )\n ).filter((path): path is string => path !== null),\n );\n const modules = new Map(builtIn.modules.map((module) => [module.path, module]));\n for (const module of graph.modules) {\n if (livePaths.has(module.path) && !modules.has(module.path))\n modules.set(module.path, module);\n }\n const edges = new Map(builtIn.edges.map((edge) => [`${edge.from}\\0${edge.to}`, edge]));\n let freshGraphEdges = 0;\n for (const edge of graph.edges) {\n if (!livePaths.has(edge.from) || !livePaths.has(edge.to)) continue;\n freshGraphEdges++;\n const key = `${edge.from}\\0${edge.to}`;\n const scanned = edges.get(key);\n edges.set(\n key,\n scanned ? { ...scanned, ...(edge.symbols ? { symbols: edge.symbols } : {}) } : edge,\n );\n }\n const mergedEdges = [...edges.values()].sort((left, right) =>\n `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`),\n );\n const importsByPath = new Map<string, string[]>();\n for (const edge of mergedEdges) {\n const imports = importsByPath.get(edge.from) ?? [];\n imports.push(edge.to);\n importsByPath.set(edge.from, imports);\n }\n return {\n modules: [...modules.values()]\n .map((module) => ({ ...module, imports: importsByPath.get(module.path) ?? [] }))\n .sort((left, right) => left.path.localeCompare(right.path)),\n edges: mergedEdges,\n source: freshGraphEdges > 0 ? \"graphify+imports\" : \"imports\",\n };\n } catch {\n // An unusable or malformed graph degrades to the built-in import map\n // instead of failing the whole context request.\n }\n }\n return { ...builtIn, source: \"imports\" };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport type { ArchitectureMap, ModuleEdge, ModuleNode } from \"./types.js\";\n\ntype GraphifyRecord = Record<string, unknown>;\n\n/**\n * Graphify emits containment and documentation relations alongside real\n * dependencies. Keeping them would make a README or an MCP config look like an\n * importer of the code it mentions, so they are dropped. Edges with no relation\n * field (older graph.json exports) are still treated as imports.\n */\nconst NON_DEPENDENCY_RELATIONS = new Set([\n \"contains\",\n \"part_of\",\n \"defines\",\n \"declares\",\n \"references\",\n \"requires_env\",\n \"rationale_for\",\n]);\n\n/**\n * Graphify's graph is undirected, so a relation phrased from the dependency's\n * point of view (\"b is imported by a\") arrives with its endpoints the wrong way\n * round for a dependency edge. These are swapped rather than dropped.\n */\nconst INVERSE_RELATIONS = new Set([\n \"imported_by\",\n \"called_by\",\n \"used_by\",\n \"referenced_by\",\n \"extended_by\",\n \"implemented_by\",\n \"dependency_of\",\n \"depended_on_by\",\n]);\n\n// Only known code dependencies may become file-level import neighbours. A new\n// Graphify relation must be reviewed before it can influence retrieval.\nconst DIRECT_DEPENDENCY_RELATIONS = new Set([\n \"import\",\n \"imports\",\n \"imports_from\",\n \"dynamic_import\",\n \"dynamic_imports\",\n \"calls\",\n \"uses\",\n \"extends\",\n \"implements\",\n \"depends_on\",\n]);\n\n/** How many symbol names an edge carries, so a large fan-in stays bounded. */\nconst MAX_EDGE_SYMBOLS = 8;\n\n/** Import the common graph.json node/edge shape without depending on Graphify. */\nexport async function loadGraphifyMap(\n graphPath: string,\n repositoryRoot = process.cwd(),\n): Promise<ArchitectureMap> {\n const raw = JSON.parse(await readFile(graphPath, \"utf8\")) as GraphifyRecord;\n const nodes = arrayRecord(raw.nodes ?? raw.entities);\n const edges = arrayRecord(raw.edges ?? raw.links ?? raw.relationships);\n const nodePaths = new Map<string, string>();\n const nodeLabels = new Map<string, string>();\n const modules: ModuleNode[] = [];\n\n for (const node of nodes) {\n const id = stringValue(node.id ?? node.key ?? node.name);\n const path = graphifyPath(node, repositoryRoot);\n if (!id || !path) continue;\n nodePaths.set(id, path);\n const label = stringValue(node.label ?? node.name);\n // The node standing for the file itself carries no useful symbol name.\n if (label && label !== path && !path.endsWith(`/${label}`)) nodeLabels.set(id, label);\n modules.push({ path, language: languageFor(path), imports: [] });\n }\n\n const modulePaths = new Set(modules.map((module) => module.path));\n const importedEdges: ModuleEdge[] = [];\n for (const edge of edges) {\n const relation = stringValue(edge.relation ?? edge.type ?? edge.kind)?.toLowerCase() ?? null;\n if (relation && NON_DEPENDENCY_RELATIONS.has(relation)) continue;\n if (relation && !DIRECT_DEPENDENCY_RELATIONS.has(relation) && !INVERSE_RELATIONS.has(relation))\n continue;\n const inverse = relation !== null && INVERSE_RELATIONS.has(relation);\n const sourceEndpoint = edge.from ?? edge.source;\n const targetEndpoint = edge.to ?? edge.target;\n const [tail, head] = inverse\n ? [targetEndpoint, sourceEndpoint]\n : [sourceEndpoint, targetEndpoint];\n const from = endpointPath(tail, nodePaths, repositoryRoot);\n const to = endpointPath(head, nodePaths, repositoryRoot);\n if (!from || !to || !modulePaths.has(from) || !modulePaths.has(to) || from === to) continue;\n // The endpoint inside the dependency names the symbol the dependant uses.\n const symbol = endpointLabel(head, nodeLabels);\n importedEdges.push({\n from,\n to,\n kind: relation?.includes(\"dynamic\") ? \"dynamic_import\" : \"import\",\n ...(symbol ? { symbols: [symbol] } : {}),\n });\n }\n\n const uniqueModules = [...new Map(modules.map((module) => [module.path, module])).values()].sort(\n (left, right) => left.path.localeCompare(right.path),\n );\n const byEndpoints = new Map<string, ModuleEdge>();\n for (const edge of importedEdges) {\n const key = `${edge.from}:${edge.to}`;\n const existing = byEndpoints.get(key);\n if (!existing) {\n byEndpoints.set(key, edge);\n continue;\n }\n // Several symbol-level links collapse into one file edge: keep every symbol\n // name, and let a static import win over a dynamic one.\n const symbols = mergeSymbols(existing.symbols, edge.symbols);\n byEndpoints.set(key, {\n ...(existing.kind === \"dynamic_import\" && edge.kind === \"import\" ? edge : existing),\n ...(symbols ? { symbols } : {}),\n });\n }\n const uniqueEdges = [...byEndpoints.values()].sort((left, right) =>\n `${left.from}:${left.to}`.localeCompare(`${right.from}:${right.to}`),\n );\n if (uniqueModules.length === 0 || uniqueEdges.length === 0) {\n throw new Error(`Graphify graph contains no usable file relationships: ${graphPath}`);\n }\n for (const module of uniqueModules) {\n module.imports = uniqueEdges\n .filter((edge) => edge.from === module.path)\n .map((edge) => edge.to)\n .sort();\n }\n return { modules: uniqueModules, edges: uniqueEdges };\n}\n\nfunction arrayRecord(value: unknown): GraphifyRecord[] {\n return Array.isArray(value)\n ? value.filter((item): item is GraphifyRecord => Boolean(item && typeof item === \"object\"))\n : [];\n}\n\nfunction endpointPath(\n endpoint: unknown,\n nodePaths: Map<string, string>,\n repositoryRoot: string,\n): string | null {\n if (typeof endpoint === \"string\")\n return nodePaths.get(endpoint) ?? normalizeGraphPath(endpoint, repositoryRoot);\n if (!endpoint || typeof endpoint !== \"object\") return null;\n const record = endpoint as GraphifyRecord;\n const id = stringValue(record.id ?? record.key ?? record.name);\n return nodePaths.get(id ?? \"\") ?? graphifyPath(record, repositoryRoot);\n}\n\nfunction endpointLabel(endpoint: unknown, nodeLabels: Map<string, string>): string | null {\n if (typeof endpoint === \"string\") return nodeLabels.get(endpoint) ?? null;\n if (!endpoint || typeof endpoint !== \"object\") return null;\n const id = stringValue((endpoint as GraphifyRecord).id);\n return id ? (nodeLabels.get(id) ?? null) : null;\n}\n\nfunction mergeSymbols(left?: string[], right?: string[]): string[] | undefined {\n if (!left && !right) return undefined;\n return [...new Set([...(left ?? []), ...(right ?? [])])].sort().slice(0, MAX_EDGE_SYMBOLS);\n}\n\nfunction graphifyPath(node: GraphifyRecord, repositoryRoot: string): string | null {\n const source = node.source;\n const location = node.location;\n const candidate =\n stringValue(node.path ?? node.file ?? node.file_path ?? node.source_file) ??\n (source && typeof source === \"object\" ? stringValue((source as GraphifyRecord).path) : null) ??\n (location && typeof location === \"object\"\n ? stringValue((location as GraphifyRecord).file ?? (location as GraphifyRecord).path)\n : null);\n return candidate ? normalizeGraphPath(candidate, repositoryRoot) : null;\n}\n\nfunction normalizeGraphPath(path: string, repositoryRoot: string): string | null {\n const absolute = isAbsolute(path) ? resolve(path) : resolve(repositoryRoot, path);\n const relativePath = relative(resolve(repositoryRoot), absolute).replace(/\\\\/g, \"/\");\n if (relativePath === \"\" || relativePath === \"..\" || relativePath.startsWith(\"../\")) return null;\n return relativePath.replace(/^\\.\\//, \"\");\n}\n\nfunction stringValue(value: unknown): string | null {\n return typeof value === \"string\" && value.length > 0 ? value : null;\n}\n\nfunction languageFor(path: string): \"ts\" | \"js\" {\n return /\\.tsx?$/.test(path) ? \"ts\" : \"js\";\n}\n","import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { NON_SOURCE_PATH, readBoundedSource, TEST_PATH } from \"./discovery.js\";\n\n/**\n * Explicit anchors in a task: repo paths, `path:line`, stack frames and quoted\n * error strings found verbatim in current source. They name the code better\n * than any ranking, so discovery puts them first. Only files in the index count;\n * a typo or a deleted file yields no anchor.\n */\nexport type TaskAnchor = {\n path: string;\n line: number | null;\n kind: \"path\" | \"line\" | \"stack\" | \"error\";\n};\n\nconst MAX_ANCHORS = 3;\n/** Source files searched for a quoted error string (bounded for the prompt hook). */\nconst MAX_ERROR_SEARCH_FILES = 2000;\n\nconst JS_FRAME = /\\bat\\s+(?:[^\\s()]+\\s+\\()?([^\\s()]+?):(\\d+):\\d+\\)?/g;\nconst PY_FRAME = /\\bFile\\s+\"([^\"]+)\",\\s+line\\s+(\\d+)/g;\nconst PATH_TOKEN =\n /(?:^|[\\s(`'\"[])((?:\\.{0,2}\\/)?[\\w@.-]+(?:\\/[\\w@.-]+)*\\.[A-Za-z0-9]+)(?::(\\d+))?/g;\nconst QUOTED = /\"([^\"\\n]{8,200})\"|'([^'\\n]{8,200})'|`([^`\\n]{8,200})`/g;\n\nexport async function extractTaskAnchors(\n repositoryRoot: string,\n task: string,\n paths: ReadonlySet<string>,\n): Promise<TaskAnchor[]> {\n const root = resolve(repositoryRoot);\n const found: Array<TaskAnchor & { at: number }> = [];\n const add = (raw: string, line: string | undefined, kind: TaskAnchor[\"kind\"], at: number) => {\n const path = toIndexedPath(root, raw, paths);\n if (path) found.push({ path, line: line ? Number(line) : null, kind, at });\n };\n for (const match of task.matchAll(JS_FRAME)) add(match[1] ?? \"\", match[2], \"stack\", match.index);\n for (const match of task.matchAll(PY_FRAME)) add(match[1] ?? \"\", match[2], \"stack\", match.index);\n for (const match of task.matchAll(PATH_TOKEN)) {\n add(match[1] ?? \"\", match[2], match[2] ? \"line\" : \"path\", match.index);\n }\n\n const anchors: TaskAnchor[] = [];\n const seen = new Set<string>();\n // A stack frame and a plain path token can match the same text; the first\n // (stack frames are collected first) wins for that path.\n for (const { at: _at, ...anchor } of found.sort((left, right) => left.at - right.at)) {\n if (seen.has(anchor.path)) continue;\n seen.add(anchor.path);\n anchors.push(anchor);\n }\n if (anchors.length < MAX_ANCHORS) {\n for (const error of await errorAnchors(root, task, paths)) {\n if (seen.has(error.path)) continue;\n seen.add(error.path);\n anchors.push(error);\n }\n }\n return anchors.slice(0, MAX_ANCHORS);\n}\n\nfunction toIndexedPath(root: string, raw: string, paths: ReadonlySet<string>): string | null {\n const absolute = isAbsolute(raw) ? raw : resolve(root, raw);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel.startsWith(`..${sep}`) || rel === \"..\" || isAbsolute(rel)) return null;\n const normalized = rel.replace(/\\\\/g, \"/\");\n return paths.has(normalized) ? normalized : null;\n}\n\n/**\n * Quoted strings (not paths) found verbatim in exactly one source file. A\n * string in several files (\"use client\", a shared label) names none of them.\n */\nasync function errorAnchors(\n root: string,\n task: string,\n paths: ReadonlySet<string>,\n): Promise<TaskAnchor[]> {\n const quotes = [...task.matchAll(QUOTED)]\n .map((match) => match[1] ?? match[2] ?? match[3] ?? \"\")\n .filter((text) => text.trim().length >= 8 && !toIndexedPath(root, text, paths));\n if (quotes.length === 0) return [];\n const sources = [...paths]\n .filter((path) => !TEST_PATH.test(path) && !NON_SOURCE_PATH.test(path))\n .sort()\n .slice(0, MAX_ERROR_SEARCH_FILES);\n // ponytail: one pass per quote over bounded sources; an index of string literals if repos outgrow it.\n const matches = quotes.map(() => [] as TaskAnchor[]);\n for (const path of sources) {\n if (matches.every((found) => found.length > 1)) break;\n const source = await readBoundedSource(root, path);\n if (!source) continue;\n quotes.forEach((quote, i) => {\n const found = matches[i] as TaskAnchor[];\n if (found.length > 1) return;\n const index = source.indexOf(quote);\n if (index >= 0) {\n found.push({ path, line: source.slice(0, index).split(\"\\n\").length, kind: \"error\" });\n }\n });\n }\n return matches.flatMap((found) => (found.length === 1 ? found : []));\n}\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,SAAS,UAAU,YAAY;AACxC,SAAS,SAAS,SAAS,MAAM,UAAU,eAAe;AAG1D,IAAM,uBAAuB,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,MAAM,CAAC;AACnF,IAAM,sBAAsB,oBAAI,IAAI;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,eAAsB,qBACpB,iBAAiB,QAAQ,IAAI,GACH;AAC1B,QAAM,OAAO,QAAQ,cAAc;AACnC,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,UAAU,SAAS,MAAM,IAAI,CAAC,CAAC,CAAC;AAC5E,QAAM,UAAwB,CAAC;AAC/B,QAAM,QAAsB,CAAC;AAE7B,aAAW,gBAAgB,OAAO;AAChC,UAAM,SAAS,MAAM,SAAS,cAAc,MAAM;AAClD,UAAM,OAAO,UAAU,SAAS,MAAM,YAAY,CAAC;AACnD,UAAM,UAAU,eAAe,MAAM;AACrC,UAAM,kBAA4B,CAAC;AACnC,eAAW,aAAa,SAAS;AAC/B,YAAM,WAAW,cAAc,MAAM,MAAM,UAAU,OAAO,OAAO;AACnE,UAAI,CAAC,SAAU;AACf,sBAAgB,KAAK,QAAQ;AAC7B,YAAM,KAAK,EAAE,MAAM,IAAI,UAAU,MAAM,UAAU,KAAK,CAAC;AAAA,IACzD;AACA,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,UAAU,QAAQ,YAAY,EAAE,SAAS,IAAI,IAAI,OAAO;AAAA,MACxD,SAAS,CAAC,GAAG,IAAI,IAAI,eAAe,CAAC,EAAE,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,SAAS,QAAQ,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAAA,IAC1E,OAAO,MAAM;AAAA,MAAK,CAAC,MAAM,UACvB,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,GAAG,cAAc,GAAG,MAAM,IAAI,IAAI,MAAM,EAAE,EAAE;AAAA,IACrE;AAAA,EACF;AACF;AAKA,eAAsB,sBACpB,gBACA,aACA,UACA,cAC+D;AAC/D,QAAM,OAAO,QAAQ,cAAc;AACnC,QAAM,kBAAkB,YAAY,IAAI,SAAS,EAAE,KAAK;AACxD,QAAM,UAAU,IAAI,IAAI,eAAe;AACvC,QAAM,UAAU,IAAI,IAAI,aAAa,IAAI,SAAS,CAAC;AACnD,QAAM,mBAAmB,UAAU,WAAW,CAAC,GAAG;AAAA,IAChD,CAAC,WAAW,QAAQ,IAAI,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,OAAO,IAAI;AAAA,EAClE;AACA,QAAM,iBAAiB,UAAU,SAAS,CAAC,GAAG;AAAA,IAC5C,CAAC,SAAS,QAAQ,IAAI,KAAK,IAAI,KAAK,QAAQ,IAAI,KAAK,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,IAAI;AAAA,EACpF;AACA,QAAM,UAAU,CAAC,GAAG,eAAe;AACnC,QAAM,QAAQ,CAAC,GAAG,aAAa;AAE/B,aAAW,QAAQ,iBAAiB;AAClC,QAAI,CAAC,QAAQ,IAAI,IAAI,EAAG;AACxB,UAAM,SAAS,MAAM,SAAS,KAAK,MAAM,IAAI,GAAG,MAAM;AACtD,UAAM,UAAU,eAAe,MAAM;AACrC,UAAM,kBAA4B,CAAC;AACnC,eAAW,aAAa,SAAS;AAC/B,YAAM,iBAAiB,cAAc,MAAM,MAAM,UAAU,OAAO,OAAO;AACzE,UAAI,CAAC,eAAgB;AACrB,sBAAgB,KAAK,cAAc;AACnC,YAAM,KAAK,EAAE,MAAM,MAAM,IAAI,gBAAgB,MAAM,UAAU,KAAK,CAAC;AAAA,IACrE;AACA,YAAQ,KAAK;AAAA,MACX;AAAA,MACA,UAAU,QAAQ,IAAI,EAAE,SAAS,IAAI,IAAI,OAAO;AAAA,MAChD,SAAS,CAAC,GAAG,IAAI,IAAI,eAAe,CAAC,EAAE,KAAK;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,KAAK;AAAA,MACH,SAAS,QAAQ,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAAA,MAC1E,OAAO,MAAM;AAAA,QAAK,CAAC,MAAM,UACvB,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,GAAG,cAAc,GAAG,MAAM,IAAI,IAAI,MAAM,EAAE,EAAE;AAAA,MACrE;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,aAAa,gBAAgB;AAAA,MAC7B,SAAS,aAAa,QAAQ,QAAQ,SAAS,gBAAgB;AAAA,IACjE;AAAA,EACF;AACF;AAEO,SAAS,SAAS,KAAsB,OAA+B;AAC5E,QAAM,QAAQ,MAAM,SAAS;AAC7B,SAAO,IAAI,MACR,OAAO,CAAC,SAAU,MAAM,OAAO,KAAK,SAAS,UAAU,MAAM,IAAI,IAAI,IAAK,EAC1E,OAAO,CAAC,SAAU,MAAM,KAAK,KAAK,OAAO,UAAU,MAAM,EAAE,IAAI,IAAK,EACpE,MAAM,GAAG,KAAK;AACnB;AAEO,SAAS,WACd,KACA,YACA,YAAmC,QACa;AAChD,QAAM,SAAS,UAAU,UAAU;AACnC,QAAM,QAAQ,IAAI,MAAM,OAAO,CAAC,SAAS;AACvC,QAAI,cAAc,KAAM,QAAO,KAAK,OAAO;AAC3C,QAAI,cAAc,MAAO,QAAO,KAAK,SAAS;AAC9C,WAAO,KAAK,SAAS,UAAU,KAAK,OAAO;AAAA,EAC7C,CAAC;AACD,QAAM,QAAQ,oBAAI,IAAY,CAAC,MAAM,CAAC;AACtC,aAAW,QAAQ,OAAO;AACxB,UAAM,IAAI,KAAK,IAAI;AACnB,UAAM,IAAI,KAAK,EAAE;AAAA,EACnB;AACA,SAAO;AAAA,IACL,SAAS,IAAI,QAAQ,OAAO,CAAC,WAAW,MAAM,IAAI,OAAO,IAAI,CAAC;AAAA,IAC9D;AAAA,EACF;AACF;AAEA,eAAe,gBAAgB,MAAiC;AAC9D,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,MAAM,KAAK;AACtB,SAAO,MAAM,KAAK;AACpB;AAEA,eAAe,KAAK,WAAmB,OAAgC;AACrE,QAAM,UAAU,MAAM,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAChE,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,KAAK,WAAW,MAAM,IAAI;AAC3C,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,oBAAoB,IAAI,MAAM,IAAI,EAAG;AACzC,YAAM,KAAK,UAAU,KAAK;AAC1B;AAAA,IACF;AACA,QAAI,CAAC,MAAM,OAAO,EAAG;AACrB,QAAI,CAAC,qBAAqB,IAAI,QAAQ,MAAM,IAAI,CAAC,EAAG;AACpD,UAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,QAAI,KAAK,OAAO,IAAS;AACzB,UAAM,KAAK,QAAQ;AAAA,EACrB;AACF;AAEA,SAAS,eAAe,QAAoE;AAC1F,QAAM,UAA8D,CAAC;AAGrE,QAAM,WAAW,OAAO,QAAQ,qBAAqB,GAAG,EAAE,QAAQ,uBAAuB,KAAK;AAC9F,QAAM,eAAe;AACrB,QAAM,aAAa;AACnB,QAAM,gBAAgB;AAEtB,aAAW,SAAS,SAAS,SAAS,YAAY,GAAG;AACnD,QAAI,MAAM,CAAC,EAAG,SAAQ,KAAK,EAAE,OAAO,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC;AAAA,EAChE;AACA,aAAW,SAAS,SAAS,SAAS,UAAU,GAAG;AACjD,QAAI,MAAM,CAAC,EAAG,SAAQ,KAAK,EAAE,OAAO,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC;AAAA,EAChE;AACA,aAAW,SAAS,SAAS,SAAS,aAAa,GAAG;AACpD,QAAI,MAAM,CAAC,EAAG,SAAQ,KAAK,EAAE,OAAO,MAAM,CAAC,GAAG,MAAM,iBAAiB,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAEA,SAAS,cACP,MACA,MACA,WACA,SACe;AACf,MAAI,CAAC,UAAU,WAAW,GAAG,EAAG,QAAO;AACvC,QAAM,OAAO,UAAU,SAAS,MAAM,QAAQ,MAAM,QAAQ,IAAI,GAAG,SAAS,CAAC,CAAC;AAK9E,QAAM,OAAO,KAAK,QAAQ,uBAAuB,EAAE;AACnD,QAAM,QAAQ,SAAS,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI;AAClD,QAAM,aAAa,MAAM,QAAQ,CAAC,MAAM;AAAA,IACtC;AAAA,IACA,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,IACJ,GAAG,CAAC;AAAA,EACN,CAAC;AACD,SAAO,WAAW,KAAK,CAAC,cAAc,QAAQ,IAAI,SAAS,CAAC,KAAK;AACnE;AAEA,SAAS,UAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,EAAE;AACrD;;;AClNA,SAAS,kBAAkB;AAE3B,SAAS,YAAAA,WAAU,QAAAC,aAAY;AAC/B,SAAS,QAAAC,OAAM,WAAAC,gBAAe;;;ACH9B,SAAS,SAAAC,QAAO,YAAAC,iBAAgB;AAChC,SAAS,WAAAC,UAAS,WAAAC,UAAS,QAAAC,OAAM,WAAAC,gBAAe;;;ACDhD,SAAS,OAAO,YAAAC,iBAAgB;AAChC,SAAS,WAAAC,UAAS,QAAAC,OAAM,WAAAC,gBAAe;AACvC,SAAS,SAAS;;;ACWX,IAAM,yBAAyB;;;ADCtC,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,MAAM,EAAE,OAAO;AAAA,EACf,MAAM,EAAE,KAAK,CAAC,YAAY,UAAU,SAAS,aAAa,QAAQ,QAAQ,OAAO,CAAC;AAAA,EAClF,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACtC,WAAW,EAAE,OAAO;AAAA,EACpB,UAAU,EAAE,QAAQ;AACtB,CAAC;AAED,IAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,MAAM,EAAE,OAAO;AAAA,EACf,MAAM,EAAE,OAAO;AAAA,EACf,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACnC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,UAAU,EAAE,OAAO;AAAA,EACnB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACpC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;AAAA,EAC3B,aAAa,EAAE,MAAM,gBAAgB,EAAE,SAAS;AAAA,EAChD,SAAS,EAAE,OAAO;AAAA,EAClB,WAAW,EAAE,OAAO;AACtB,CAAC;AAED,IAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,SAAS,EAAE,QAAQ,sBAAsB;AAAA,EACzC,aAAa,EAAE,OAAO;AAAA,EACtB,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,WAAW;AAC3C,CAAC;AAEM,IAAM,aAAN,MAAiB;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,iBAAiB,QAAQ,IAAI,GAAG;AAC1C,SAAK,iBAAiBC,SAAQ,cAAc;AAC5C,SAAK,iBAAiBC,MAAK,KAAK,gBAAgB,cAAc,OAAO;AACrE,SAAK,eAAeA,MAAK,KAAK,gBAAgB,YAAY;AAAA,EAC5D;AAAA;AAAA,EAGA,MAAM,OAA+B;AACnC,QAAI;AACF,YAAM,MAAM,MAAMC,UAAS,KAAK,cAAc,MAAM;AACpD,YAAM,SAAS,eAAe,UAAU,KAAK,MAAM,GAAG,CAAC;AACvD,UAAI,CAAC,OAAO,QAAS,QAAO,cAAc;AAE1C,aAAO,OAAO;AAAA,IAChB,SAAS,OAAO;AACd,UAAK,MAAgC,SAAS,SAAU,QAAO,cAAc;AAE7E,aAAO,cAAc;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,UAAwC;AAClD,UAAM,uBAAuB,KAAK,gBAAgBC,SAAQ,KAAK,YAAY,CAAC;AAC5E,UAAM,MAAM,KAAK,gBAAgB,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACjE,UAAM,YAAY,KAAK,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC/E;AACF;AAEO,SAAS,gBAA+B;AAC7C,SAAO,EAAE,SAAS,wBAAwB,cAAa,oBAAI,KAAK,CAAC,GAAE,YAAY,GAAG,SAAS,CAAC,EAAE;AAChG;;;ADlEA,IAAM,gBAAgB;AACtB,IAAM,oBAAoB,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,QAAQ,QAAQ,MAAM,CAAC;AAehF,eAAsB,oBACpB,iBAAiB,QAAQ,IAAI,GAC7B,kBACmC;AACnC,QAAM,OAAOC,SAAQ,cAAc;AACnC,QAAM,WAAW,oBAAqB,MAAM,IAAI,WAAW,IAAI,EAAE,KAAK;AACtE,MAAI,OAAO,KAAK,SAAS,OAAO,EAAE,WAAW,GAAG;AAC9C,UAAM,MAAM,MAAM,qBAAqB,IAAI;AAC3C,WAAO;AAAA,MACL;AAAA,MACA,OAAO,EAAE,WAAW,IAAI,QAAQ,QAAQ,aAAa,GAAG,SAAS,KAAK;AAAA,MACtE,QAAQ;AAAA,IACV;AAAA,EACF;AACA,SAAO,yBAAyB,MAAM,QAAQ;AAChD;AAEA,eAAsB,yBACpB,gBACA,UACmC;AACnC,QAAM,OAAOA,SAAQ,cAAc;AACnC,QAAM,OAAOC,MAAK,MAAM,cAAc,SAAS,mBAAmB;AAClE,QAAM,gBAAgB,aAAa,QAAQ;AAC3C,QAAM,eAAe,OAAO,KAAK,aAAa,EAAE,KAAK;AACrD,QAAM,SAAS,MAAM,UAAU,IAAI;AACnC,QAAM,cAAc,SAAS,OAAO,KAAK,OAAO,YAAY,EAAE,KAAK,IAAI,CAAC;AACxE,QAAM,iBAAiB,YAAY,KAAK,IAAI,MAAM,aAAa,KAAK,IAAI;AACxE,QAAM,eACJ,CAAC,UAAU,iBACP,eACA,aAAa;AAAA,IACX,CAAC,eAAe,OAAO,aAAa,UAAU,MAAM,cAAc,UAAU;AAAA,EAC9E;AAEN,MAAI,UAAU,aAAa,WAAW,GAAG;AACvC,WAAO;AAAA,MACL,KAAK,OAAO;AAAA,MACZ,OAAO,EAAE,WAAW,GAAG,aAAa,OAAO,IAAI,QAAQ,QAAQ,SAAS,MAAM;AAAA,MAC9E,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,iBAAiB,OAAQ,QAAQ,OAAO;AAAA,IACxC;AAAA,EACF;AACA,QAAM,OAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,aAAa,SAAS;AAAA,IACtB,cAAc;AAAA,IACd,KAAK,QAAQ;AAAA,EACf;AACA,QAAM,uBAAuB,MAAMC,SAAQ,IAAI,CAAC;AAChD,QAAMC,OAAMD,SAAQ,IAAI,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC3D,QAAM,YAAY,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,CAAI;AAC5D,SAAO,EAAE,GAAG,SAAS,QAAQ,kBAAkB,CAAC,SAAS,YAAY,cAAc;AACrF;AAEA,eAAe,UAAU,MAAiD;AACxE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,MAAME,UAAS,MAAM,MAAM,CAAC;AACtD,QAAI,OAAO,YAAY,iBAAiB,CAAC,OAAO,OAAO,CAAC,OAAO,aAAc,QAAO;AACpF,QAAI,CAAC,MAAM,QAAQ,OAAO,IAAI,OAAO,KAAK,CAAC,MAAM,QAAQ,OAAO,IAAI,KAAK,EAAG,QAAO;AACnF,QACE,CAAC,OAAO,IAAI,QAAQ;AAAA,MAClB,CAAC,WACC,UACA,OAAO,OAAO,SAAS,YACvB,OAAO,OAAO,aAAa,YAC3B,MAAM,QAAQ,OAAO,OAAO;AAAA,IAChC,KACA,CAAC,OAAO,IAAI,MAAM;AAAA,MAChB,CAAC,SAAS,QAAQ,OAAO,KAAK,SAAS,YAAY,OAAO,KAAK,OAAO;AAAA,IACxE,GACA;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,UAAiD;AACrE,SAAO,OAAO;AAAA,IACZ,OAAO,OAAO,SAAS,OAAO,EAC3B,OAAO,CAAC,UAAU,kBAAkB,IAAIC,SAAQ,MAAM,IAAI,EAAE,YAAY,CAAC,CAAC,EAC1E,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC;AAAA,EAC5C;AACF;;;AGxHA,SAAS,aAAa;AACtB,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,UAAS,QAAAC,OAAM,YAAAC,iBAAgB;;;ACFxC,SAAS,WAAAC,gBAAe;AASxB,IAAM,wBAAgD;AAAA,EACpD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AAGO,IAAMC,wBAAuB,IAAI,IAAI,OAAO,KAAK,qBAAqB,CAAC;AAE9E,IAAM,iBAAiB,oBAAI,IAAI,CAAC,MAAM,OAAO,MAAM,KAAK,CAAC;AACzD,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAErB,SAAS,YAAY,MAAsB;AAChD,SAAO,sBAAsBD,SAAQ,IAAI,EAAE,YAAY,CAAC,KAAK;AAC/D;AASO,SAAS,gBAAgB,MAAc,SAA8B;AAC1E,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,QAAQ,WAAW,OAAO;AAChC,QAAM,UAAU,eAAe,IAAI,QAAQ,IAAI,eAAe,OAAO,IAAI,CAAC;AAC1E,QAAM,UAAU,eAAe,UAAU,OAAO,SAAS,OAAO;AAChE,SAAO,EAAE,UAAU,OAAO,SAAS,QAAQ;AAC7C;AAEA,SAAS,WAAW,SAAyB;AAC3C,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,QAAQ,QAAQ,OAAO,EAAE,EAAE,QAAQ,OAAO,EAAE,EAAE,MAAM,IAAI,EAAE;AACnE;AAOO,SAAS,eAAe,SAA2B;AACxD,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,CAAC,SAA6B;AACzC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,WAAW,qBAAqB,KAAK,OAAO,KAAK,CAAC,MAAM,SAAS,OAAO,GAAG;AAC7E,YAAM,KAAK,OAAO;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,cACJ;AACF,aAAW,SAAS,QAAQ,SAAS,WAAW,EAAG,MAAK,MAAM,CAAC,CAAC;AAEhE,QAAM,eAAe;AACrB,aAAW,SAAS,QAAQ,SAAS,YAAY,GAAG;AAClD,eAAW,SAAS,MAAM,CAAC,KAAK,IAAI,MAAM,GAAG,GAAG;AAE9C,YAAM,QAAQ,KAAK,SAAS,MAAM,IAAI,KAAK,MAAM,MAAM,EAAE,IAAI,IAAI;AACjE,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAEA,SAAO,MAAM,MAAM,GAAG,YAAY;AACpC;AAEA,SAAS,eACP,UACA,OACA,SACA,SACQ;AACR,QAAM,OAAO,GAAG,QAAQ,WAAM,KAAK,QAAQ,UAAU,IAAI,KAAK,GAAG;AACjE,MAAI,QAAQ,SAAS,EAAG,QAAO,GAAG,IAAI,mBAAc,QAAQ,KAAK,IAAI,CAAC;AACtE,QAAM,UAAU,eAAe,OAAO;AACtC,MAAI,QAAS,QAAO,GAAG,IAAI,WAAM,OAAO;AACxC,SAAO;AACT;AAGA,SAAS,eAAe,SAAgC;AACtD,aAAW,OAAO,QAAQ,MAAM,OAAO,GAAG;AACxC,UAAM,OAAO,IAAI,KAAK;AACtB,QAAI,SAAS,GAAI;AACjB,UAAM,WAAW,KACd,QAAQ,UAAU,EAAE,EACpB,QAAQ,UAAU,EAAE,EACpB,QAAQ,UAAU,EAAE,EACpB,QAAQ,UAAU,EAAE,EACpB,KAAK;AACR,QACE,KAAK,WAAW,IAAI,KACpB,KAAK,WAAW,IAAI,KACpB,KAAK,WAAW,GAAG,KACnB,KAAK,WAAW,GAAG,GACnB;AACA,UAAI,SAAU,QAAO,SAAS,UAAU,mBAAmB;AAC3D;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,SAAS,OAAe,KAAqB;AACpD,SAAO,MAAM,UAAU,MAAM,QAAQ,GAAG,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC;AACjE;;;ADxHA,IAAM,+BAA+B,oBAAI,IAAI;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,eAAsB,mBAAmB,gBAA2C;AAClF,QAAM,UAAU,MAAM,aAAa,cAAc;AACjD,QAAM,QAAQ,WAAY,MAAM,UAAU,cAAc;AACxD,SAAO,MACJ,IAAIE,UAAS,EACb,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW,aAAa,KAAK,CAAC,KAAK,WAAW,eAAe,CAAC,EACrF,OAAO,CAAC,SAASC,sBAAqB,IAAIC,SAAQ,IAAI,EAAE,YAAY,CAAC,CAAC,EACtE,KAAK;AACV;AAGA,eAAe,aAAa,gBAAkD;AAC5E,QAAM,SAAS,MAAM,OAAO,gBAAgB;AAAA,IAC1C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,SAAO,OAAO,OACX,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,OAAO;AACnB;AAEA,eAAe,UAAU,MAAiC;AACxD,QAAM,QAAkB,CAAC;AACzB,QAAMC,QAAO,OAAO,cAAqC;AACvD,UAAM,UAAU,MAAMC,SAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AAChE,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAWC,MAAK,WAAW,MAAM,IAAI;AAC3C,UAAI,MAAM,YAAY,GAAG;AACvB,YAAI,6BAA6B,IAAI,MAAM,IAAI,EAAG;AAClD,cAAMF,MAAK,QAAQ;AACnB;AAAA,MACF;AACA,UAAI,MAAM,OAAO,EAAG,OAAM,KAAKG,UAAS,MAAM,QAAQ,CAAC;AAAA,IACzD;AAAA,EACF;AACA,QAAMH,MAAK,IAAI;AACf,SAAO;AACT;AAEA,SAAS,OACP,KACA,MAC2D;AAC3D,SAAO,IAAI,QAAQ,CAAC,kBAAkB;AACpC,UAAM,QAAQ,MAAM,OAAO,MAAM,EAAE,KAAK,OAAO,CAAC,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC3E,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AACjC,gBAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AACD,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AACjC,gBAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AAED,UAAM,GAAG,SAAS,MAAM,cAAc,EAAE,MAAM,IAAI,QAAQ,IAAI,QAAQ,kBAAkB,CAAC,CAAC;AAC1F,UAAM,GAAG,SAAS,CAAC,SAAS,cAAc,EAAE,MAAM,QAAQ,GAAG,QAAQ,OAAO,CAAC,CAAC;AAAA,EAChF,CAAC;AACH;AAEA,SAASH,WAAU,MAAsB;AACvC,SAAO,KAAK,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,EAAE;AACrD;;;AJjEA,IAAM,iBAAiB;AAKvB,IAAM,iBAAiB;AASvB,eAAsB,gBAAgB,UAAwB,CAAC,GAAyB;AACtF,UAAQ,MAAM,aAAa,OAAO,GAAG;AACvC;AAcA,eAAsB,eAAe,gBAA6C;AAChF,MAAI;AACF,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,aAAa,EAAE,eAAe,CAAC;AAClE,UAAM,QAAQ,OAAO,QAAQ,OAAO,UAAU,OAAO;AACrD,UAAM,WACJ,OAAO,OAAO,SAAS,IACnB,KAAK,OAAO,OAAO,MAAM,qDACzB;AACN,WAAO;AAAA,MACL;AAAA,MACA,SACE,UAAU,IACN,UAAU,OAAO,YAAY,wCAAwC,QAAQ,KAC7E,4CAA4C,OAAO,OAAO,aAAa,OAAO,KAAK,WAAW,OAAO,OAAO,aAAa,OAAO,YAAY,YAAY,QAAQ;AAAA,IACxK;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,UAAU,cAAc;AAAA,MACxB,SAAS,kCAAmC,MAAgB,OAAO;AAAA,IACrE;AAAA,EACF;AACF;AAIA,IAAM,SAAS,oBAAI,IAA8B;AAGjD,SAAS,aACP,SAC2D;AAC3D,QAAM,MAAMO,SAAQ,QAAQ,kBAAkB,QAAQ,IAAI,CAAC;AAC3D,QAAM,QAAQ,OAAO,IAAI,GAAG,KAAK,QAAQ,QAAQ,GAC9C,MAAM,MAAM,MAAS,EACrB,KAAK,MAAM,aAAa,OAAO,CAAC;AACnC,SAAO,IAAI,KAAK,IAAI;AACpB,OAAK,KACF,QAAQ,MAAM;AACb,QAAI,OAAO,IAAI,GAAG,MAAM,KAAM,QAAO,OAAO,GAAG;AAAA,EACjD,CAAC,EACA,MAAM,MAAM,MAAS;AACxB,SAAO;AACT;AAEA,eAAe,aACb,SAC2D;AAC3D,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,MAAM,QAAQ,QAAQ,MAAM,oBAAI,KAAK;AAC3C,QAAM,iBAAiBA,SAAQ,QAAQ,kBAAkB,QAAQ,IAAI,CAAC;AACtE,QAAM,QAAQ,IAAI,WAAW,cAAc;AAE3C,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,QAAQ,IAAI,CAAC,mBAAmB,cAAc,GAAG,MAAM,KAAK,CAAC,CAAC;AAE9F,QAAM,UAAsC,CAAC;AAC7C,QAAM,SAAuB,CAAC;AAC9B,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,MAAI,YAAY;AAChB,MAAI,UAAU;AAEd,MAAI,QAAQ,SAAS,gBAAgB,cAAc,EAAE;AAErD,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,OAAO,MAAMC,MAAKC,MAAK,gBAAgB,IAAI,CAAC;AAClD,UAAI,CAAC,KAAK,OAAO,EAAG;AACpB,UAAI,KAAK,OAAO,gBAAgB;AAC9B;AACA;AAAA,MACF;AAEA,YAAM,WAAW,SAAS,QAAQ,IAAI;AACtC,UAAI,CAAC,QAAQ,QAAQ,YAAY,cAAc,UAAU,IAAI,GAAG;AAC9D,gBAAQ,IAAI,IAAI;AAChB;AACA;AAAA,MACF;AAEA,YAAM,UAAU,MAAMC,UAASD,MAAK,gBAAgB,IAAI,GAAG,MAAM;AACjE,YAAM,OAAO,YAAY,OAAO;AAChC,YAAM,WAAW,gBAAgB,IAAI;AACrC,cAAQ;AAER,UAAI,CAAC,QAAQ,QAAQ,YAAY,SAAS,SAAS,MAAM;AACvD,cAAM,EAAE,SAAS,QAAQ,SAAS,QAAQ,GAAG,KAAK,IAAI;AACtD,gBAAQ,IAAI,IAAI,EAAE,GAAG,MAAM,MAAM,KAAK,MAAM,GAAG,SAAS;AACxD;AACA;AAAA,MACF;AAEA,YAAM,EAAE,UAAU,OAAO,SAAS,QAAQ,IAAI,gBAAgB,MAAM,OAAO;AAC3E,YAAM,cAAc,gBAAgB,IAAI,IAAI,mBAAmB,OAAO,IAAI,CAAC;AAC3E,cAAQ,IAAI,IAAI;AAAA,QACd;AAAA,QACA;AAAA,QACA,MAAM,KAAK;AAAA,QACX,GAAG;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,YAAY,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;AAAA,QAChD;AAAA,QACA,WAAW,IAAI,EAAE,YAAY;AAAA,MAC/B;AACA,UAAI,SAAU;AAAA,UACT;AAAA,IACP,SAAS,OAAO;AACd,aAAO,KAAK,EAAE,MAAM,OAAQ,MAAgB,QAAQ,CAAC;AACrD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,KAAK,SAAS,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,QAAQ,EAAE;AAEnF,QAAM,WAA0B;AAAA,IAC9B,SAAS;AAAA,IACT,aAAa,IAAI,EAAE,YAAY;AAAA,IAC/B,SAAS,YAAY,OAAO;AAAA,EAC9B;AACA,MAAI,SAAS,UAAU,EAAG,OAAM,MAAM,MAAM,QAAQ;AACpD,QAAM,eAAe,MAAM,yBAAyB,gBAAgB,QAAQ;AAE5E,QAAM,SAAsB;AAAA,IAC1B,SAAS,MAAM;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,KAAK,IAAI,IAAI;AAAA,IACzB,cAAc,OAAO,KAAK,OAAO,EAAE;AAAA,IACnC,cAAc;AAAA,MACZ,QAAQ,aAAa;AAAA,MACrB,WAAW,aAAa,MAAM;AAAA,MAC9B,aAAa,aAAa,MAAM;AAAA,IAClC;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,SAAS;AAC5B;AAEA,SAAS,cAAc,OAAmB,MAAsB;AAC9D,SACE,MAAM,YAAY,UAClB,MAAM,SAAS,KAAK,QACpB,MAAM,YAAY,KAAK,WACvB,MAAM,YAAY,KAAK;AAE3B;AAGA,SAAS,gBAAgB,MAAqD;AAC5E,MAAI,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,SAAS,KAAK,OAAO,IAAI,eAAgB,QAAO,CAAC;AAChF,SAAO,EAAE,SAAS,KAAK,SAAS,SAAS,KAAK,QAAQ;AACxD;AAEO,SAAS,YAAY,SAAyB;AACnD,SAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,MAAM,EAAE,OAAO,KAAK;AAClE;AAEA,SAAS,YAAY,SAAiE;AACpF,SAAO,OAAO;AAAA,IACZ,OAAO,KAAK,OAAO,EAChB,KAAK,EACL,IAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ,GAAG,CAAe,CAAC;AAAA,EACnD;AACF;;;AMpOA,SAAS,gBAAgB;AACzB,SAAS,SAAAE,QAAO,SAAS,YAAAC,WAAU,IAAI,QAAAC,OAAM,cAAc;AAC3D,SAAS,cAAc;AACvB,SAAS,WAAAC,UAAS,QAAAC,OAAM,YAAAC,WAAU,WAAAC,UAAS,WAAW;AACtD,SAAS,iBAAiB;;;ACJ1B,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAAC,iBAAgB;AAIzB,IAAM,SAAS,CAAC,UAAkBC,YAAW,QAAQ,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAGjF,eAAsB,yBACpB,MACA,UACe;AACf,QAAM;AAAA,IACJ,GAAG,IAAI;AAAA,IACP,KAAK,UAAU;AAAA,MACb,SAAS;AAAA,MACT,WAAW,OAAO,MAAMC,UAAS,MAAM,MAAM,CAAC;AAAA,MAC9C,SAAS,OAAO;AAAA,QACd,OAAO,OAAO,SAAS,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC;AAAA,MACzE;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,eAAsB,wBACpB,MACwC;AACxC,MAAI;AACJ,MAAI;AACF,UAAM,MAAMA,UAAS,GAAG,IAAI,iBAAiB,MAAM;AAAA,EACrD,SAAS,OAAO;AACd,WAAQ,MAAgC,SAAS,WAAW,OAAO,CAAC;AAAA,EACtE;AACA,MAAI;AACF,UAAM,WAAW,KAAK,MAAM,GAAG;AAC/B,QACE,SAAS,YAAY,KACrB,SAAS,cAAc,OAAO,MAAMA,UAAS,MAAM,MAAM,CAAC,KAC1D,CAAC,SAAS,WACV,OAAO,SAAS,YAAY,YAC5B,MAAM,QAAQ,SAAS,OAAO;AAE9B,aAAO,CAAC;AACV,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,SAAS,OAAO,EAAE;AAAA,QAC/B,CAAC,UAAqC,OAAO,MAAM,CAAC,MAAM;AAAA,MAC5D;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;;;AD3CA,IAAM,gBAAgB,UAAU,QAAQ;AAkCxC,IAAM,qBAAqB;AAG3B,eAAsB,cAAc,SAA6D;AAC/F,QAAM,MAAM,QAAQ,OAAO;AAC3B,MAAI,YAAY,MAAM,aAAa,KAAK,YAAY,CAAC,WAAW,GAAG,QAAQ,cAAc;AACzF,MAAI,YAAY;AAEhB,MAAI,CAAC,WAAW;AACd,UAAM,aAAa;AAAA,MACjB,CAAC,MAAM,CAAC,QAAQ,WAAW,WAAW,CAAC;AAAA,MACvC,CAAC,QAAQ,CAAC,WAAW,WAAW,CAAC;AAAA,MACjC,CAAC,UAAU,CAAC,MAAM,OAAO,WAAW,UAAU,WAAW,CAAC;AAAA,IAC5D;AACA,eAAW,CAAC,SAAS,IAAI,KAAK,YAAY;AACxC,UAAI;AACF,cAAM,IAAI,SAAS,CAAC,GAAG,IAAI,GAAG,QAAQ,cAAc;AACpD,oBAAY,MAAM,aAAa,KAAK,YAAY,CAAC,WAAW,GAAG,QAAQ,cAAc;AACrF,YAAI,WAAW;AACb,sBAAY;AACZ;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,MACL,WAAW;AAAA,MACX;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,MACX,SACE;AAAA,IACJ;AAAA,EACF;AAEA,MAAI;AACF,UAAM,IAAI,YAAY,CAAC,SAAS,GAAG,QAAQ,cAAc,EAAE,MAAM,MAAM,MAAS;AAChF,UAAM,QAAQ,MAAM,sBAAsB,EAAE,gBAAgB,QAAQ,gBAAgB,IAAI,CAAC;AACzF,WAAO;AAAA,MACL,WAAW;AAAA,MACX;AAAA,MACA,WAAW;AAAA,MACX,WAAW,MAAM;AAAA,MACjB,SAAS,6BAA6B,MAAM,KAAK,WAAW,MAAM,KAAK;AAAA,IACzE;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,WAAW;AAAA,MACX;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,MACX,SAAS,+DAAgE,MAAgB,OAAO;AAAA,IAClG;AAAA,EACF;AACF;AAEA,eAAe,aACb,KACA,SACA,MACA,KACkB;AAClB,MAAI;AACF,UAAM,IAAI,SAAS,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,WACb,SACA,MACA,KAC6C;AAC7C,SAAO,cAAc,SAAS,MAAM,EAAE,IAAI,CAAC;AAC7C;AAEA,eAAsB,oBACpB,gBACA,YAAY,oBACM;AAClB,MAAI;AACF,UAAM,SAAS,MAAM,cAAc,iBAAiB,gBAAgB,SAAS,CAAC;AAC9E,WAAO,OAAO,QAAQ;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,sBACpB,SACiC;AACjC,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,iBAAiBC,SAAQ,QAAQ,cAAc;AACrD,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,cAAc,iBAAiB,gBAAgB,SAAS;AAC9D,QAAMC,OAAMC,SAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,QAAM,8BAA8BA,SAAQ,WAAW,CAAC;AAExD,QAAM,EAAE,UAAU,eAAe,IAAI,MAAM,eAAe,cAAc;AACxE,QAAM,UAAU,MAAM,uBAAuB,WAAW;AACxD,QAAM,gBAAgB,cAAc;AACpC,MAAI,WAAW,eAAe;AAC5B,UAAM,SAAS,MAAMC,UAAS,aAAa,MAAM;AACjD,UAAM,aAAa,MAAMC,MAAK,WAAW;AACzC,QAAI;AACF,YAAM,IAAI,YAAY,CAAC,UAAU,gBAAgB,WAAW,cAAc,GAAG,cAAc;AAC3F,YAAM,UAAU,MAAM,cAAc,WAAW;AAC/C,YAAM,yBAAyB,aAAa,cAAc;AAC1D,aAAO,EAAE,WAAW,WAAW,MAAM,GAAG,QAAQ;AAAA,IAClD,SAAS,OAAO;AACd,YAAM,YAAY,aAAa,MAAM;AACrC,YAAM,OAAO,aAAa,WAAW,OAAO,WAAW,KAAK;AAC5D,YAAM,IAAI;AAAA,QACR,6DAA8D,MAAgB,OAAO;AAAA,MACvF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,MAAM,QAAQC,MAAK,OAAO,GAAG,qBAAqB,CAAC;AACvE,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,MACA,CAAC,WAAW,gBAAgB,SAAS,aAAa,eAAe,cAAc;AAAA,MAC/E;AAAA,IACF;AACA,UAAM,aAAaA,MAAK,aAAa,kBAAkB;AACvD,UAAM,SAAS,MAAM,cAAc,UAAU;AAC7C,UAAM,YAAY,aAAa,MAAMF,UAAS,YAAY,MAAM,CAAC;AACjE,UAAM,yBAAyB,aAAa,cAAc;AAC1D,WAAO,EAAE,WAAW,WAAW,MAAM,GAAG,OAAO;AAAA,EACjD,UAAE;AACA,UAAM,GAAG,aAAa,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACxD;AACF;AAOA,eAAe,8BAA8B,WAAkC;AAC7E,QAAM,OAAOE,MAAK,WAAW,YAAY;AACzC,MAAI;AACF,UAAMD,MAAK,IAAI;AAAA,EACjB,QAAQ;AACN,UAAM,YAAY,MAAM,mEAAmE;AAAA,EAC7F;AACF;AAEA,SAAS,iBAAiB,gBAAwB,WAA2B;AAC3E,QAAM,WAAWJ,SAAQ,gBAAgB,SAAS;AAClD,QAAM,eAAeM,UAAS,gBAAgB,QAAQ;AACtD,MAAI,iBAAiB,MAAM,iBAAiB,QAAQ,aAAa,WAAW,KAAK,GAAG,EAAE,GAAG;AACvF,UAAM,IAAI,MAAM,+CAA+C,SAAS,EAAE;AAAA,EAC5E;AACA,SAAO;AACT;AAEA,eAAe,uBACb,MACkD;AAClD,MAAI;AACF,WAAO,MAAM,cAAc,IAAI;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,cAAc,MAAyD;AACpF,QAAM,OAAO,MAAMF,MAAK,IAAI;AAC5B,MAAI,CAAC,KAAK,OAAO,KAAK,KAAK,SAAS;AAClC,UAAM,IAAI,MAAM,uCAAuC,IAAI,EAAE;AAC/D,QAAM,MAAM,KAAK,MAAM,MAAMD,UAAS,MAAM,MAAM,CAAC;AACnD,QAAM,QAAQ,YAAY,IAAI,SAAS,IAAI,QAAQ;AACnD,QAAM,QAAQ,YAAY,IAAI,SAAS,IAAI,SAAS,IAAI,aAAa;AACrE,MAAI,UAAU,EAAG,OAAM,IAAI,MAAM,qCAAqC,IAAI,EAAE;AAC5E,SAAO,EAAE,OAAO,MAAM;AACxB;AAEA,SAAS,YAAY,OAAwB;AAC3C,SAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;AAC/C;;;AE1OA,SAAS,YAAAI,WAAU,UAAU,QAAAC,aAAY;AACzC,SAAS,cAAAC,aAAY,QAAAC,OAAM,YAAAC,iBAAgB;;;ACD3C,SAAS,YAAAC,iBAAgB;AACzB,SAAS,YAAY,YAAAC,WAAU,WAAAC,gBAAe;AAW9C,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOD,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,IAAM,8BAA8B,oBAAI,IAAI;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,mBAAmB;AAGzB,eAAsB,gBACpB,WACA,iBAAiB,QAAQ,IAAI,GACH;AAC1B,QAAM,MAAM,KAAK,MAAM,MAAMF,UAAS,WAAW,MAAM,CAAC;AACxD,QAAM,QAAQ,YAAY,IAAI,SAAS,IAAI,QAAQ;AACnD,QAAM,QAAQ,YAAY,IAAI,SAAS,IAAI,SAAS,IAAI,aAAa;AACrE,QAAM,YAAY,oBAAI,IAAoB;AAC1C,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,YAAY,KAAK,MAAM,KAAK,OAAO,KAAK,IAAI;AACvD,UAAM,OAAO,aAAa,MAAM,cAAc;AAC9C,QAAI,CAAC,MAAM,CAAC,KAAM;AAClB,cAAU,IAAI,IAAI,IAAI;AACtB,UAAM,QAAQ,YAAY,KAAK,SAAS,KAAK,IAAI;AAEjD,QAAI,SAAS,UAAU,QAAQ,CAAC,KAAK,SAAS,IAAI,KAAK,EAAE,EAAG,YAAW,IAAI,IAAI,KAAK;AACpF,YAAQ,KAAK,EAAE,MAAM,UAAUG,aAAY,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;AAAA,EACjE;AAEA,QAAM,cAAc,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI,CAAC;AAChE,QAAM,gBAA8B,CAAC;AACrC,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,YAAY,KAAK,YAAY,KAAK,QAAQ,KAAK,IAAI,GAAG,YAAY,KAAK;AACxF,QAAI,YAAY,yBAAyB,IAAI,QAAQ,EAAG;AACxD,QAAI,YAAY,CAAC,4BAA4B,IAAI,QAAQ,KAAK,CAAC,kBAAkB,IAAI,QAAQ;AAC3F;AACF,UAAM,UAAU,aAAa,QAAQ,kBAAkB,IAAI,QAAQ;AACnE,UAAM,iBAAiB,KAAK,QAAQ,KAAK;AACzC,UAAM,iBAAiB,KAAK,MAAM,KAAK;AACvC,UAAM,CAAC,MAAM,IAAI,IAAI,UACjB,CAAC,gBAAgB,cAAc,IAC/B,CAAC,gBAAgB,cAAc;AACnC,UAAM,OAAO,aAAa,MAAM,WAAW,cAAc;AACzD,UAAM,KAAK,aAAa,MAAM,WAAW,cAAc;AACvD,QAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,IAAI,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,KAAK,SAAS,GAAI;AAEnF,UAAM,SAAS,cAAc,MAAM,UAAU;AAC7C,kBAAc,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,MACA,MAAM,UAAU,SAAS,SAAS,IAAI,mBAAmB;AAAA,MACzD,GAAI,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IACxC,CAAC;AAAA,EACH;AAEA,QAAM,gBAAgB,CAAC,GAAG,IAAI,IAAI,QAAQ,IAAI,CAAC,WAAW,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE;AAAA,IAC1F,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI;AAAA,EACrD;AACA,QAAM,cAAc,oBAAI,IAAwB;AAChD,aAAW,QAAQ,eAAe;AAChC,UAAM,MAAM,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE;AACnC,UAAM,WAAW,YAAY,IAAI,GAAG;AACpC,QAAI,CAAC,UAAU;AACb,kBAAY,IAAI,KAAK,IAAI;AACzB;AAAA,IACF;AAGA,UAAM,UAAU,aAAa,SAAS,SAAS,KAAK,OAAO;AAC3D,gBAAY,IAAI,KAAK;AAAA,MACnB,GAAI,SAAS,SAAS,oBAAoB,KAAK,SAAS,WAAW,OAAO;AAAA,MAC1E,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/B,CAAC;AAAA,EACH;AACA,QAAM,cAAc,CAAC,GAAG,YAAY,OAAO,CAAC,EAAE;AAAA,IAAK,CAAC,MAAM,UACxD,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,GAAG,cAAc,GAAG,MAAM,IAAI,IAAI,MAAM,EAAE,EAAE;AAAA,EACrE;AACA,MAAI,cAAc,WAAW,KAAK,YAAY,WAAW,GAAG;AAC1D,UAAM,IAAI,MAAM,yDAAyD,SAAS,EAAE;AAAA,EACtF;AACA,aAAW,UAAU,eAAe;AAClC,WAAO,UAAU,YACd,OAAO,CAAC,SAAS,KAAK,SAAS,OAAO,IAAI,EAC1C,IAAI,CAAC,SAAS,KAAK,EAAE,EACrB,KAAK;AAAA,EACV;AACA,SAAO,EAAE,SAAS,eAAe,OAAO,YAAY;AACtD;AAEA,SAAS,YAAY,OAAkC;AACrD,SAAO,MAAM,QAAQ,KAAK,IACtB,MAAM,OAAO,CAAC,SAAiC,QAAQ,QAAQ,OAAO,SAAS,QAAQ,CAAC,IACxF,CAAC;AACP;AAEA,SAAS,aACP,UACA,WACA,gBACe;AACf,MAAI,OAAO,aAAa;AACtB,WAAO,UAAU,IAAI,QAAQ,KAAK,mBAAmB,UAAU,cAAc;AAC/E,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,SAAS;AACf,QAAM,KAAK,YAAY,OAAO,MAAM,OAAO,OAAO,OAAO,IAAI;AAC7D,SAAO,UAAU,IAAI,MAAM,EAAE,KAAK,aAAa,QAAQ,cAAc;AACvE;AAEA,SAAS,cAAc,UAAmB,YAAgD;AACxF,MAAI,OAAO,aAAa,SAAU,QAAO,WAAW,IAAI,QAAQ,KAAK;AACrE,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,KAAK,YAAa,SAA4B,EAAE;AACtD,SAAO,KAAM,WAAW,IAAI,EAAE,KAAK,OAAQ;AAC7C;AAEA,SAAS,aAAa,MAAiB,OAAwC;AAC7E,MAAI,CAAC,QAAQ,CAAC,MAAO,QAAO;AAC5B,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAI,QAAQ,CAAC,GAAI,GAAI,SAAS,CAAC,CAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB;AAC3F;AAEA,SAAS,aAAa,MAAsB,gBAAuC;AACjF,QAAM,SAAS,KAAK;AACpB,QAAM,WAAW,KAAK;AACtB,QAAM,YACJ,YAAY,KAAK,QAAQ,KAAK,QAAQ,KAAK,aAAa,KAAK,WAAW,MACvE,UAAU,OAAO,WAAW,WAAW,YAAa,OAA0B,IAAI,IAAI,UACtF,YAAY,OAAO,aAAa,WAC7B,YAAa,SAA4B,QAAS,SAA4B,IAAI,IAClF;AACN,SAAO,YAAY,mBAAmB,WAAW,cAAc,IAAI;AACrE;AAEA,SAAS,mBAAmB,MAAc,gBAAuC;AAC/E,QAAM,WAAW,WAAW,IAAI,IAAID,SAAQ,IAAI,IAAIA,SAAQ,gBAAgB,IAAI;AAChF,QAAM,eAAeD,UAASC,SAAQ,cAAc,GAAG,QAAQ,EAAE,QAAQ,OAAO,GAAG;AACnF,MAAI,iBAAiB,MAAM,iBAAiB,QAAQ,aAAa,WAAW,KAAK,EAAG,QAAO;AAC3F,SAAO,aAAa,QAAQ,SAAS,EAAE;AACzC;AAEA,SAAS,YAAY,OAA+B;AAClD,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEA,SAASC,aAAY,MAA2B;AAC9C,SAAO,UAAU,KAAK,IAAI,IAAI,OAAO;AACvC;;;ACnMA,SAAS,cAAAC,aAAY,YAAAC,WAAU,WAAAC,UAAS,OAAAC,YAAW;AAenD,IAAM,cAAc;AAEpB,IAAM,yBAAyB;AAE/B,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,aACJ;AACF,IAAM,SAAS;AAEf,eAAsB,mBACpB,gBACA,MACA,OACuB;AACvB,QAAM,OAAOC,SAAQ,cAAc;AACnC,QAAM,QAA4C,CAAC;AACnD,QAAM,MAAM,CAAC,KAAa,MAA0B,MAA0B,OAAe;AAC3F,UAAM,OAAO,cAAc,MAAM,KAAK,KAAK;AAC3C,QAAI,KAAM,OAAM,KAAK,EAAE,MAAM,MAAM,OAAO,OAAO,IAAI,IAAI,MAAM,MAAM,GAAG,CAAC;AAAA,EAC3E;AACA,aAAW,SAAS,KAAK,SAAS,QAAQ,EAAG,KAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,SAAS,MAAM,KAAK;AAC/F,aAAW,SAAS,KAAK,SAAS,QAAQ,EAAG,KAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,SAAS,MAAM,KAAK;AAC/F,aAAW,SAAS,KAAK,SAAS,UAAU,GAAG;AAC7C,QAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,SAAS,QAAQ,MAAM,KAAK;AAAA,EACvE;AAEA,QAAM,UAAwB,CAAC;AAC/B,QAAM,OAAO,oBAAI,IAAY;AAG7B,aAAW,EAAE,IAAI,KAAK,GAAG,OAAO,KAAK,MAAM,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,MAAM,EAAE,GAAG;AACpF,QAAI,KAAK,IAAI,OAAO,IAAI,EAAG;AAC3B,SAAK,IAAI,OAAO,IAAI;AACpB,YAAQ,KAAK,MAAM;AAAA,EACrB;AACA,MAAI,QAAQ,SAAS,aAAa;AAChC,eAAW,SAAS,MAAM,aAAa,MAAM,MAAM,KAAK,GAAG;AACzD,UAAI,KAAK,IAAI,MAAM,IAAI,EAAG;AAC1B,WAAK,IAAI,MAAM,IAAI;AACnB,cAAQ,KAAK,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO,QAAQ,MAAM,GAAG,WAAW;AACrC;AAEA,SAAS,cAAc,MAAc,KAAa,OAA2C;AAC3F,QAAM,WAAWC,YAAW,GAAG,IAAI,MAAMD,SAAQ,MAAM,GAAG;AAC1D,QAAM,MAAME,UAAS,MAAM,QAAQ;AACnC,MAAI,QAAQ,MAAM,IAAI,WAAW,KAAKC,IAAG,EAAE,KAAK,QAAQ,QAAQF,YAAW,GAAG,EAAG,QAAO;AACxF,QAAM,aAAa,IAAI,QAAQ,OAAO,GAAG;AACzC,SAAO,MAAM,IAAI,UAAU,IAAI,aAAa;AAC9C;AAMA,eAAe,aACb,MACA,MACA,OACuB;AACvB,QAAM,SAAS,CAAC,GAAG,KAAK,SAAS,MAAM,CAAC,EACrC,IAAI,CAAC,UAAU,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,EACrD,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,UAAU,KAAK,CAAC,cAAc,MAAM,MAAM,KAAK,CAAC;AAChF,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,QAAM,UAAU,CAAC,GAAG,KAAK,EACtB,OAAO,CAAC,SAAS,CAAC,UAAU,KAAK,IAAI,KAAK,CAAC,gBAAgB,KAAK,IAAI,CAAC,EACrE,KAAK,EACL,MAAM,GAAG,sBAAsB;AAElC,QAAM,UAAU,OAAO,IAAI,MAAM,CAAC,CAAiB;AACnD,aAAW,QAAQ,SAAS;AAC1B,QAAI,QAAQ,MAAM,CAAC,UAAU,MAAM,SAAS,CAAC,EAAG;AAChD,UAAM,SAAS,MAAM,kBAAkB,MAAM,IAAI;AACjD,QAAI,CAAC,OAAQ;AACb,WAAO,QAAQ,CAAC,OAAO,MAAM;AAC3B,YAAM,QAAQ,QAAQ,CAAC;AACvB,UAAI,MAAM,SAAS,EAAG;AACtB,YAAM,QAAQ,OAAO,QAAQ,KAAK;AAClC,UAAI,SAAS,GAAG;AACd,cAAM,KAAK,EAAE,MAAM,MAAM,OAAO,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,EAAE,QAAQ,MAAM,QAAQ,CAAC;AAAA,MACrF;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,QAAQ,QAAQ,CAAC,UAAW,MAAM,WAAW,IAAI,QAAQ,CAAC,CAAE;AACrE;;;AFnFO,IAAM,kBAAkB;AAE/B,IAAM,aAAa;AAEnB,IAAM,YAAY,EAAE,OAAO,GAAG,OAAO,EAAE;AAEvC,IAAM,qBAAqB;AAG3B,eAAsB,kBACpB,gBACA,MACwB;AACxB,MAAI;AACF,UAAM,WAAWG,MAAK,gBAAgB,IAAI;AAC1C,SAAK,MAAMC,MAAK,QAAQ,GAAG,OAAO,mBAAoB,QAAO;AAC7D,UAAM,SAAS,MAAMC,UAAS,UAAU,MAAM;AAC9C,WAAO,OAAO,SAAS,IAAI,IAAI,OAAO;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,kBACpB,gBACA,MACA,UACA,WACA,UAA4B,CAAC,GACH;AAC1B,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,IAAI,IAAI,OAAO,KAAK,SAAS,OAAO,CAAC;AAAA,EACvC;AACA,MAAI,QAAQ,WAAW;AACrB,WAAO,cAAc,gBAAgB,MAAM,UAAU,WAAW,OAAO;AACzE,QAAM,WAAW,QAAQ,IAAI,CAAC,WAAW,OAAO,IAAI;AACpD,QAAM,OAAuB,QAAQ;AAAA,IAAQ,CAAC,WAC5C,OAAO,SAAS,OACZ,CAAC,IACD;AAAA,MACE;AAAA,QACE,MAAM,OAAO;AAAA,QACb,WAAW,OAAO;AAAA,QAClB,SAAS,OAAO;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACN;AACA,QAAM,UAAU,uBAAuB,QACpC;AAAA,IACC,CAAC,WAAW,GAAG,OAAO,IAAI,IAAI,OAAO,IAAI,GAAG,OAAO,SAAS,OAAO,KAAK,IAAI,OAAO,IAAI,EAAE;AAAA,EAC3F,EACC,KAAK,IAAI,CAAC;AACb,MAAI,SAAS,UAAU,WAAY,QAAO,EAAE,OAAO,UAAU,MAAM,SAAS,CAAC,OAAO,EAAE;AACtF,QAAM,SAAS,MAAM,cAAc,gBAAgB,MAAM,UAAU,WAAW,OAAO;AACrF,SAAO;AAAA,IACL,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;AAAA,IACpD,OAAO,CAAC,GAAG,UAAU,GAAG,OAAO,MAAM,OAAO,CAAC,SAAS,CAAC,SAAS,SAAS,IAAI,CAAC,CAAC,EAAE;AAAA,MAC/E;AAAA,MACA;AAAA,IACF;AAAA,IACA,MAAM,CAAC,GAAG,MAAM,GAAG,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,SAAS,SAAS,IAAI,IAAI,CAAC,CAAC;AAAA,IAC5E,SAAS,CAAC,SAAS,GAAG,OAAO,OAAO;AAAA,EACtC;AACF;AAOA,eAAe,iBACb,gBACA,UACyE;AACzE,QAAM,UAAU,OAAO,OAAO,SAAS,OAAO;AAC9C,MAAI;AACF,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,sBAAkB;AAC5D,UAAM,SAAS,MAAM,iBAAiB,cAAc;AACpD,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,SAAS,UAAW,QAAO;AAC1D,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,yBAAqB;AAC/D,UAAM,YAAY,MAAM,iBAAiB,gBAAgB,QAAQ;AACjE,QAAI,UAAU,SAAS,EAAG,QAAO;AACjC,WAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,YAAM,UAAU,UAAU,IAAI,MAAM,IAAI;AACxC,aAAO,UACH,EAAE,GAAG,OAAO,SAAS,GAAG,QAAQ,OAAO,IAAI,OAAO,OAAO,QAAQ,OAAO,EAAE,KAAK,GAAG,CAAC,GAAG,IACtF;AAAA,IACN,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAqBA,eAAe,cACb,gBACA,MACA,UACA,WACA,SAC0B;AAC1B,QAAM,UAAU,MAAM,iBAAiB,gBAAgB,QAAQ;AAC/D,QAAM,aAAa,qBAAqB,KAAK,IAAI;AAGjD,QAAM,cAAc,iBAAiB,IAAI,EAAE,OAAO,CAAC,UAAU,gBAAgB,KAAK,KAAK,CAAC;AACxF,MAAI,YAAY,SAAS,GAAG;AAE1B,UAAM,QAAQ,QACX,OAAO,CAAC,UAAU,cAAc,CAAC,UAAU,KAAK,MAAM,IAAI,CAAC,EAC3D,IAAI,CAAC,WAAW;AAAA,MACf,MAAM,MAAM;AAAA,MACZ,OAAO,IAAI;AAAA,SACR,MAAM,eAAe,CAAC,GAAG;AAAA,UAAQ,CAAC,WACjC,OAAO,KAAK,MAAM,GAAG,EAAE,OAAO,CAAC,YAAY,YAAY,SAAS,OAAO,CAAC;AAAA,QAC1E;AAAA,MACF,EAAE;AAAA,IACJ,EAAE,EACD,OAAO,CAAC,UAAU,MAAM,QAAQ,CAAC,EACjC,KAAK,CAAC,MAAM,UAAU,MAAM,QAAQ,KAAK,SAAS,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC,EACrF,IAAI,CAAC,UAAU,MAAM,IAAI;AAC5B,QAAI,MAAM,SAAS,GAAG;AACpB,YAAMC,YAAW,IAAI;AAAA,QACnB,eAAe,SAAS,IAAI,EACzB,OAAO,CAAC,UAAU,MAAM,QAAQ,CAAC,EACjC,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,MAC7C;AACA,YAAMC,aAAY,mBAAmB,OAAO,MAAM,WAAWD,WAAU,MAAM;AAAA,QAC3E,gBAAgB;AAAA,MAClB,CAAC;AACD,aAAO;AAAA,QACL,OAAOC,WAAU,MAAM,MAAM,GAAG,UAAU;AAAA,QAC1C,MAAM,CAAC;AAAA,QACP,SAAS;AAAA,UACP,oCAAoC,YAAY,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,UACtE,GAAIA,WAAU,UAAU,CAACA,WAAU,OAAO,IAAI,CAAC;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,MAAM,aAAa,gBAAgB,MAAM,OAAO;AACjE,QAAM,SAAS,eAAe,SAAS,IAAI;AAC3C,QAAM,UAAU,OACb,OAAO,CAAC,UAAU,MAAM,SAAS,eAAe,YAAY,EAC5D,IAAI,CAAC,UAAU,MAAM,IAAI;AAC5B,QAAM,cAAc,OAAO,SAAS,QAAQ;AAE5C,QAAM,WACJ,QAAQ,aAAa,QACjB;AAAA,IACE,QAAQ;AAAA,IACR,OAAO,CAAC;AAAA,IACR,MAAM,CAAC;AAAA,IACP,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,SAAS;AAAA,EACX,IACA,MAAM,OAAO,oBAAsB,EAChC,KAAK,CAAC,EAAE,eAAe,MAAM,eAAe,gBAAgB,IAAI,CAAC,EACjE;AAAA,IACC,OAAuB;AAAA,MACrB,QAAQ;AAAA,MACR,OAAO,CAAC;AAAA,MACR,MAAM,CAAC;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,SAAS;AAAA,IACX;AAAA,EACF;AAER,QAAM,WAAW,IAAI;AAAA,IACnB,OAAO,OAAO,CAAC,UAAU,MAAM,QAAQ,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,EACpF;AACA,MAAI,SAAS;AACb,MAAI,OAAuB,CAAC;AAC5B,QAAM,UAAoB,CAAC;AAC3B,MAAI,SAAS,WAAW,cAAc,SAAS,MAAM,WAAW,GAAG;AACjE,YAAQ,KAAK,oCAAoC,SAAS,MAAM,GAAG;AAAA,EACrE,WAAW,SAAS,WAAW,YAAY;AAIzC,UAAM,QAAQ,UAAU,IAAI;AAC5B,UAAM,SAAmB,CAAC;AAC1B,UAAM,UAAoB,CAAC;AAC3B,QAAI,iBAAiB;AACrB,eAAW,QAAQ,SAAS,OAAO;AACjC,UAAI,CAAC,cAAc,UAAU,KAAK,IAAI,EAAG;AACzC,UAAI,QAAQ,SAAS,IAAI,EAAG,QAAO,KAAK,IAAI;AAAA,eAE1C,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT;AAAA,QACA,SAAS,QAAQ,IAAI,GAAG,eAAe,CAAC;AAAA,MAC1C,GACA;AACA,cAAM,SAAU,MAAM,kBAAkB,gBAAgB,IAAI,KAAM;AAClE,YAAI,WAAW,QAAQ,KAAK,KAAK,eAAe,YAAa,QAAO,KAAK,IAAI;AAAA,YACxE,SAAQ,KAAK,IAAI;AACtB,YAAI,CAAC,SAAS,IAAI,IAAI,EAAG,UAAS,IAAI,MAAM,CAAC;AAAA,MAC/C,MAAO;AAAA,IACT;AAGA,aAAS;AAAA,MACP,OAAO,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO,SAAS,KAAK,OAAO,CAAC,QAAQ,OAAO,SAAS,IAAI,IAAI,KAAK,QAAQ,SAAS,IAAI,IAAI,CAAC;AAC5F,YAAQ;AAAA,MACN,yCAAyC,SAAS,MAAM,MACrD,iBAAiB,IAAI,KAAK,cAAc,2CAA2C;AAAA,IACxF;AAAA,EACF;AAEA,MAAI;AACJ,QAAM,WAAW,MAAM,UAAU;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,SAAS;AAAA,IACnB;AAAA,IACA;AAAA,IACA,YAAY,QAAQ;AAAA,EACtB,CAAC;AACD,MAAI,UAAU;AACZ,YAAQ,KAAK,SAAS,OAAO;AAC7B,aAAS,SAAS;AAClB,cAAU,SAAS;AAAA,EACrB;AAKA,MAAI,CAAC,OAAO,KAAK,CAAC,SAAS,CAAC,gBAAgB,KAAK,IAAI,CAAC,GAAG;AACvD,YAAQ;AAAA,MACN,6DACG,OAAO,SAAS,IACb,yBACA,sBAAsB,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,eAAe,YAAY,OAC/E,SAAS,SAAS,IACf,2CACA;AAAA,IACR;AACA,WAAO,EAAE,OAAO,UAAU,MAAM,CAAC,GAAG,QAAQ;AAAA,EAC9C;AACA,MAAI,cAAc,GAAG;AACnB,YAAQ;AAAA,MACN,eAAe,WAAW,gEAAgE,eAAe,YAAY;AAAA,IACvH;AAAA,EACF;AACA,QAAM,YAAY,mBAAmB,QAAQ,MAAM,WAAW,UAAU,IAAI;AAC5E,MAAI,UAAU,QAAS,SAAQ,KAAK,UAAU,OAAO;AACrD,MAAI,SAAS,SAAS,GAAG;AACvB,YAAQ,KAAK,mCAAmC,SAAS,KAAK,IAAI,CAAC,GAAG;AAAA,EACxE;AACA,QAAM,QAAQ,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,UAAU,GAAG,UAAU,KAAK,CAAC,CAAC;AAC5D,MAAI,MAAM,SAAS,YAAY;AAC7B,YAAQ;AAAA,MACN,eAAe,MAAM,SAAS,UAAU,6CAA6C,UAAU;AAAA,IACjG;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO,MAAM,MAAM,GAAG,UAAU;AAAA,IAChC;AAAA,IACA;AAAA,IACA,GAAI,WAAW,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClE;AACF;AASA,IAAM,gBAAgB;AAEtB,eAAe,UAAU,OAUoE;AAC3F,MAAI;AACJ,MAAI;AACF,aAAS,OAAO,MAAM,OAAO,sBAAkB,GAAG,iBAAiB,MAAM,cAAc;AAAA,EACzF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,OAAO,WAAW,CAAC,OAAO,SAAS,OAAQ,QAAO;AACvD,QAAM,UAAU,CAAC,YAAoB;AAAA,IACnC,QAAQ,MAAM;AAAA,IACd,SAAS,kCAAkC,MAAM;AAAA,IACjD,SAAS,CAAC;AAAA,EACZ;AACA,QAAM,SAAS,CAAC,SAAiB,MAAM,cAAc,CAAC,UAAU,KAAK,IAAI;AACzE,QAAM,iBAAiB;AAAA,IACrB,GAAG,oBAAI,IAAI;AAAA,MACT,GAAG,MAAM;AAAA,MACT,GAAG,MAAM,OACN,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,OAAO,MAAM,EACb,MAAM,GAAG,EAAE;AAAA,MACd,GAAG,MAAM,SAAS,OAAO,MAAM,EAAE,MAAM,GAAG,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH,EAAE,MAAM,GAAG,EAAE;AACb,MAAI,eAAe,WAAW,EAAG,QAAO;AACxC,QAAM,SAAS,IAAI,IAAI,MAAM,QAAQ,IAAI,CAAC,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;AACxE,QAAM,aAAa,KAAK;AAAA,IACtB,OAAO;AAAA,IACP,MAAM,eAAe,SAAY,OAAO,YAAY,MAAM,aAAa,KAAK,IAAI;AAAA,EAClF;AACA,MAAI,aAAa,cAAe,QAAO,QAAQ,iCAAiC;AAChF,QAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,sBAAkB;AAC5D,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM;AAAA,IACN;AAAA,IACA,MAAM;AAAA,IACN,eAAe,IAAI,CAAC,UAAU;AAAA,MAC5B;AAAA,MACA,SAAS,OAAO,IAAI,IAAI,GAAG,WAAW,OAAO,IAAI,IAAI,GAAG,WAAW;AAAA,MACnE,aAAa,MAAM,SAAS,QAAQ,IAAI,GAAG,eAAe,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,IACtF,EAAE;AAAA,IACF;AAAA,EACF,EAAE,MAAM,OAAO,EAAE,IAAI,OAAgB,QAAQ,UAAmB,EAAE;AAClE,MAAI,CAAC,OAAO,GAAI,QAAO,QAAQ,OAAO,MAAM;AAC5C,QAAM,QAAQ,GAAG,OAAO,QAAQ,IAAI,OAAO,KAAK,KAAK,OAAO,SAAS,WAAW,GAAG,OAAO,SAAS,KAAK;AACxG,MAAI,OAAO,MAAM,SAAS;AACxB,WAAO;AAAA,MACL,QAAQ,CAAC;AAAA,MACT,SAAS,0BAA0B,KAAK,0BAA0B,eAAe,MAAM;AAAA,MACvF,SAAS,CAAC;AAAA,IACZ;AAAA,EACF;AACA,QAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAoC,CAAC;AAC3C,MAAI,WAAW;AACf,aAAW,QAAQ,OAAO,MAAM,OAAO;AACrC,QAAI,CAAC,MAAM,OAAO,SAAS,KAAK,IAAI,GAAG;AACrC,YAAM,SAAU,MAAM,kBAAkB,MAAM,gBAAgB,KAAK,IAAI,KAAM;AAC7E,YAAM,UAAU,OAAO,IAAI,KAAK,IAAI,GAAG,WAAW;AAClD,UACE,KAAK,IAAI,WAAW,SAAS,KAAK,GAAG,WAAW,QAAQ,KAAK,CAAC,IAAI,eAAe;AAEjF;AACF;AAAA,IACF;AACA,UAAM,KAAK,KAAK,IAAI;AACpB,UAAM,QAAQ,IAAI;AAAA,OACf,MAAM,SAAS,QAAQ,KAAK,IAAI,GAAG,eAAe,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IAC1E;AACA,UAAM,QAAQ,KAAK,QAAQ,OAAO,CAAC,SAAS,MAAM,IAAI,IAAI,CAAC;AAC3D,QAAI,MAAM,SAAS,EAAG,SAAQ,KAAK,IAAI,IAAI;AAAA,EAC7C;AACA,SAAO;AAAA,IACL,QAAQ,CAAC,GAAG,OAAO,GAAG,MAAM,OAAO,OAAO,CAAC,SAAS,CAAC,MAAM,SAAS,IAAI,CAAC,CAAC;AAAA,IAC1E,SACE,0BAA0B,KAAK,KAAK,MAAM,MAAM,OAAO,eAAe,MAAM,iBAC3E,WAAW,IAAI,KAAK,QAAQ,cAAc,MAC3C;AAAA,IACF;AAAA,EACF;AACF;AAOA,eAAe,aACb,gBACA,MACA,SACmB;AACnB,QAAM,OAAO,MAAM,YAAY,cAAc;AAC7C,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAC/B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,UAAU,IAAI;AAAA,IAClB,SAAS,KAAK,IAAI,YAAY,EAAE,KAAK,GAAG,CAAC;AAAA,IACzC;AAAA,EACF;AACA,aAAW,CAAC,EAAE,SAAS,GAAG,KAAK,KAAK,SAAS,OAAO,GAAG;AACrD,QAAI,CAAC,QAAS;AACd,UAAM,IAAI,QAAQ,YAAY,CAAC;AAC/B,QAAI,IAAK,OAAM,IAAI,GAAG,OAAO,IAAI,GAAG,GAAG,YAAY,CAAC;AAAA,EACtD;AACA,MAAI,MAAM,SAAS,EAAG,QAAO,CAAC;AAC9B,QAAM,UAAU,QACb,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,OAAO,CAAC,SAAS;AAChB,QAAI,UAAU,KAAK,IAAI,KAAK,gBAAgB,KAAK,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,GAAG;AACjF,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC;AACjD,WAAO,MAAM,IAAI,KAAK,MAAM,GAAG,KAAK,YAAY,GAAG,CAAC,EAAE,YAAY,CAAC;AAAA,EACrE,CAAC;AAEH,SAAO,QACJ,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,UAAU,KAAK,cAAc,KAAK,CAAC,EAC7E,MAAM,GAAG,UAAU;AACxB;AAEA,IAAM,cAAc;AAEpB,eAAe,YAAY,gBAA2C;AACpE,MAAI;AACF,UAAM,WAAW,KAAK,MAAM,MAAMF,UAASF,MAAK,gBAAgB,cAAc,GAAG,MAAM,CAAC;AAIxF,QAAI,OAAO,SAAS,QAAQ,YAAY,OAAO,SAAS,SAAS,UAAU;AACzE,aAAO,CAAC,SAAS,KAAK,MAAM,SAAS,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC;AAAA,IACjE;AACA,QAAI,SAAS,OAAO,OAAO,SAAS,QAAQ,SAAU,QAAO,OAAO,KAAK,SAAS,GAAG;AAAA,EACvF,QAAQ;AAAA,EAER;AACA,SAAO,CAAC;AACV;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAGA,IAAM,kBACJ;AAUF,SAAS,mBACP,QACA,KACA,UACA,MACA,UAAwC,CAAC,GACI;AAC7C,MAAI,CAAC,gBAAgB,KAAK,IAAI,EAAG,QAAO,EAAE,OAAO,QAAQ,SAAS,KAAK;AACvE,QAAM,QAAQ,OAAO,MAAM,GAAG,UAAU,KAAK;AAC7C,QAAM,QAAQ,oBAAI,IAAyB;AAC3C,QAAM,YAAY,oBAAI,IAAoB;AAC1C,aAAW,QAAQ,IAAI,OAAO;AAC5B,eAAW,CAAC,MAAM,KAAK,KAAK;AAAA,MAC1B,CAAC,KAAK,MAAM,KAAK,EAAE;AAAA,MACnB,CAAC,KAAK,IAAI,KAAK,IAAI;AAAA,IACrB,GAAY;AACV,UAAI,CAAC,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,KAAK,EAAG;AACpD,UAAI,CAAC,SAAS,IAAI,KAAK,GAAG;AACxB,YACE,CAAC,QAAQ,kBACT,KAAK,SAAS,QACd,UAAU,KAAK,KAAK,KACpB,gBAAgB,KAAK,KAAK;AAE1B;AACF,kBAAU;AAAA,UACR;AAAA,UACA,KAAK;AAAA,YACH,UAAU,IAAI,KAAK,KAAK;AAAA,YACxB,KAAK,SAAS,SAAS,IAAI,KAAK,SAAS,WAAW,IAAI;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AACA,YAAM,IAAI,QAAQ,MAAM,IAAI,KAAK,KAAK,oBAAI,IAAI,GAAG,IAAI,IAAI,CAAC;AAAA,IAC5D;AAAA,EACF;AACA,QAAM,UAAU,CAAC,SAAiB,SAAS,IAAI,IAAI,KAAK;AACxD,QAAM,QAAQ,CAAC,GAAG,KAAK,EACpB;AAAA,IACC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MACnB,EAAE,OAAO,EAAE,QACX,QAAQ,KAAK,IAAI,QAAQ,IAAI,MAC5B,UAAU,IAAI,KAAK,KAAK,MAAM,UAAU,IAAI,IAAI,KAAK,MACtD,KAAK,cAAc,KAAK;AAAA,EAC5B,EACC,MAAM,GAAG,UAAU,KAAK,EACxB,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACvB,MAAI,MAAM,WAAW,EAAG,QAAO,EAAE,OAAO,QAAQ,SAAS,KAAK;AAC9D,QAAM,QAAQ,OAAO,OAAO,CAAC,SAAS,CAAC,MAAM,SAAS,IAAI,CAAC;AAC3D,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,UAAM,KAAK,MAAM;AAAA,MACf,CAAC,OAAO,UAAU,SAAS,MAAM,UAAU,QAAQ,KAAK,IAAI;AAAA,IAC9D;AACA,UAAM,OAAO,KAAK,IAAI,MAAM,SAAS,IAAI,GAAG,IAAI;AAAA,EAClD;AACA,SAAO;AAAA,IACL;AAAA,IACA,SACE,cAAc,MAAM,MAAM,mCAAmC,MAAM,MAAM,mEAAmE,MAAM,KAAK,IAAI,CAAC,gCAC3J,MAAM,KAAK,CAAC,SAAS,UAAU,IAAI,IAAI,CAAC,IAAI,+BAA+B;AAAA,EAChF;AACF;AAGO,SAAS,uBACd,SACA,kBACA,QACA,SACU;AACV,QAAM,WAAW,oBAAI,IAAI,CAAC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AACzD,QAAM,cAAc,aAAa,CAAC,SAAS,MAAM,CAAC,EAAE,OAAO,CAAC,SAAS,SAAS,IAAI,IAAI,CAAC;AACvF,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,aAAa,GAAG,OAAO,CAAC,CAAC;AAClD;AAGA,SAAS,aAAa,OAAmB,IAAI,IAAc;AACzD,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,QAAQ,OAAO;AACxB,SAAK,QAAQ,CAAC,MAAM,UAAU;AAC5B,aAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,QAAQ,EAAE;AAAA,IAChE,CAAC;AAAA,EACH;AACA,SAAO,CAAC,GAAG,MAAM,EACd,KAAK,CAAC,MAAM,UAAU,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,cAAc,MAAM,CAAC,CAAC,CAAC,EAC3E,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI;AACzB;AAEA,IAAM,iBAAiB,IAAI;AAAA,EACzB,iUAAiU;AAAA,IAC/T;AAAA,EACF;AACF;AASO,IAAM,iBAAiB,EAAE,cAAc,KAAK,aAAa,EAAE;AAElE,eAAe,0BACb,gBACA,MACA,MACA,OACA,SACkB;AAClB,QAAM,SAAS,MAAM,kBAAkB,gBAAgB,IAAI;AAC3D,MAAI,WAAW,KAAM,QAAO;AAG5B,MAAI,WAAW,QAAQ,KAAK,KAAK,eAAe,YAAa,QAAO;AACpE,QAAM,QAAQ,OAAO,MAAM,OAAO;AAClC,SAAO,KAAK,KAAK,CAAC,QAAQ;AACxB,QAAI,IAAI,SAAS,QAAQ,IAAI,UAAU,MAAM,OAAQ,QAAO;AAC5D,UAAM,UAAU,MAAM,MAAM,IAAI,YAAY,GAAG,IAAI,OAAO;AAC1D,UAAM,UAAU,WAAW,QAAQ,KAAK,GAAG,GAAG,KAAK;AACnD,WACE,WAAW,eAAe,eACzB,WAAW,KACV,MAAM,WAAW,KACjB,IAAI,QAAQ,KACZ,QAAQ;AAAA,MACN,CAAC,WAAW,IAAI,aAAa,OAAO,WAAW,IAAI,WAAW,OAAO;AAAA,IACvE;AAAA,EAEN,CAAC;AACH;AACO,IAAM,YACX;AACK,IAAM,kBACX;AAuBK,SAAS,eACd,SACA,MACuD;AACvD,QAAM,QAAQ,UAAU,IAAI;AAC5B,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,aAAa,MAAM,KAAK,CAAC,SAAS,SAAS,UAAU,SAAS,MAAM;AAC1E,QAAM,aAAa,QAAQ,QAAQ,CAAC,UAAU;AAC5C,QAAI,UAAU,KAAK,MAAM,IAAI,KAAK,CAAC,WAAY,QAAO,CAAC;AACvD,UAAM,QAAQ,IAAI;AAAA,MAChB;AAAA,QACE,GAAG,MAAM,IAAI,IAAI,MAAM,OAAO,IAAI,MAAM,QAAQ,KAAK,GAAG,CAAC,IAAI,MAAM,aAAa,IAAI,CAAC,WAAW,OAAO,IAAI,EAAE,KAAK,GAAG,KAAK,EAAE;AAAA,MAC9H;AAAA,IACF;AACA,UAAM,WAAW,IAAI,IAAI,MAAM,UAAU,WAAW,MAAM,OAAO,IAAI,CAAC,CAAC;AACvE,UAAM,YAAY,IAAI,IAAI,WAAW,MAAM,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC;AACvF,WAAO,CAAC,EAAE,MAAM,MAAM,MAAM,OAAO,UAAU,UAAU,CAAC;AAAA,EAC1D,CAAC;AAKD,QAAM,MAAM,IAAI;AAAA,IACd,MAAM,IAAI,CAAC,SAAS;AAClB,YAAM,KAAK,WAAW;AAAA,QACpB,CAAC,cAAc,UAAU,MAAM,IAAI,IAAI,KAAK,UAAU,SAAS,IAAI,IAAI;AAAA,MACzE,EAAE;AACF,aAAO,CAAC,MAAM,KAAK,KAAK,WAAW,SAAS,MAAM,KAAK,EAAE,IAAI,CAAC;AAAA,IAChE,CAAC;AAAA,EACH;AACA,SAAO,WACJ,QAAQ,CAAC,EAAE,MAAM,OAAO,UAAU,UAAU,MAAM;AACjD,QAAI,QAAQ;AACZ,QAAI,WAAW;AACf,eAAW,QAAQ,OAAO;AACxB,UAAI,MAAM,IAAI,IAAI,GAAG;AACnB,cAAM,SAAS,UAAU,IAAI,IAAI,IAAI,MAAM;AAC3C,iBAAS;AACT,oBAAY,UAAU,IAAI,IAAI,IAAI,KAAK;AAAA,MACzC,WAAW,SAAS,IAAI,IAAI,GAAG;AAC7B,oBAAY,OAAO,IAAI,IAAI,IAAI,KAAK;AAAA,MACtC;AAAA,IACF;AACA,QAAI,aAAa,EAAG,QAAO,CAAC;AAC5B,QAAI,gBAAgB,KAAK,IAAI,GAAG;AAC9B,eAAS;AACT,kBAAY;AAAA,IACd;AACA,WAAO,CAAC,EAAE,MAAM,OAAO,OAAO,SAAS,CAAC;AAAA,EAC1C,CAAC,EACA;AAAA,IACC,CAAC,MAAM,UACL,MAAM,QAAQ,KAAK,SAAS,MAAM,QAAQ,KAAK,SAAS,KAAK,KAAK,cAAc,MAAM,IAAI;AAAA,EAC9F;AACJ;AAGA,SAAS,WAAW,MAAwB;AAC1C,SAAO,KACJ,QAAQ,sBAAsB,OAAO,EACrC,YAAY,EACZ,MAAM,YAAY,EAClB,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC;AAAA,IAAI,CAAC,SACJ,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,KAAK,CAAC,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AAAA,EACtF;AACJ;AAGO,SAAS,UAAU,MAAwB;AAChD,SAAO,CAAC,GAAG,IAAI,IAAI,WAAW,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC,CAAC;AAClF;AAGO,SAAS,WAAW,MAAc,OAAyB;AAChE,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,QAAQ,IAAI,IAAI,WAAW,IAAI,CAAC;AACtC,SAAO,MAAM,OAAO,CAAC,SAAS,MAAM,IAAI,IAAI,CAAC,EAAE;AACjD;AAGA,IAAM,kBAAkB;AAGjB,SAAS,iBAAiB,MAAwB;AACvD,QAAM,SAAS,KAAK,MAAM,0CAA0C,KAAK,CAAC;AAC1E,SAAO,CAAC,GAAG,IAAI,IAAI,OAAO,QAAQ,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAC7E;AAGO,SAAS,cAAc,MAAc,QAA0B;AACpE,SAAO,KAAK,MAAM,GAAG,EAAE,OAAO,CAAC,YAAY,OAAO,SAAS,OAAO,CAAC,EAAE;AACvE;AAEA,eAAsB,2BACpB,gBACA,gBAC0B;AAC1B,QAAM,CAAC,QAAQ,QAAQ,IAAI,MAAM,QAAQ,IAAI,CAAC,eAAe,cAAc,GAAG,cAAc,CAAC;AAC7F,QAAM,WAAW,MAAM,oBAAoB,gBAAgB,QAAQ,GAAG;AACtE,MACE,OAAO,UAAU,WAChB,MAAM,oBAAoB,gBAAgB,OAAO,SAAS,SAAS,GACpE;AACA,QAAI;AACF,YAAM,YAAYK;AAAA,QAChB;AAAA,QACA,OAAO,SAAS,aAAa;AAAA,MAC/B;AACA,YAAM,CAAC,OAAO,WAAWC,aAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,QACzD,gBAAgB,WAAW,cAAc;AAAA,QACzCC,MAAK,SAAS;AAAA,QACd,wBAAwB,SAAS;AAAA,MACnC,CAAC;AAID,YAAM,YAAY,IAAI;AAAA,SAElB,MAAM,QAAQ;AAAA,UACZ,MAAM,QAAQ,IAAI,OAAO,WAAW;AAClC,gBAAI;AACF,oBAAM,WAAW,MAAM,SAASF,MAAK,gBAAgB,OAAO,IAAI,CAAC;AACjE,oBAAM,SAASG,UAAS,MAAM,SAAS,cAAc,GAAG,QAAQ;AAChE,kBAAI,OAAO,WAAW,IAAI,KAAKC,YAAW,MAAM,EAAG,QAAO;AAC1D,oBAAM,SAAS,MAAMF,MAAK,QAAQ;AAClC,oBAAM,QACJD,kBAAiB,OACb,OAAO,WAAW,UAAU,UAC5BA,cAAa,OAAO,IAAI,MAAM,UAC9BA,cAAa,OAAO,IAAI,MAAM,SAAS,QAAQ,OAAO,IAAI,GAAG;AACnE,qBAAO,OAAO,OAAO,KAAK,QAAQ,OAAO,OAAO;AAAA,YAClD,QAAQ;AAEN,qBAAO;AAAA,YACT;AAAA,UACF,CAAC;AAAA,QACH,GACA,OAAO,CAAC,SAAyB,SAAS,IAAI;AAAA,MAClD;AACA,YAAM,UAAU,IAAI,IAAI,QAAQ,QAAQ,IAAI,CAAC,WAAW,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;AAC9E,iBAAW,UAAU,MAAM,SAAS;AAClC,YAAI,UAAU,IAAI,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,OAAO,IAAI;AACxD,kBAAQ,IAAI,OAAO,MAAM,MAAM;AAAA,MACnC;AACA,YAAM,QAAQ,IAAI,IAAI,QAAQ,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,IAAI,KAAK,KAAK,EAAE,IAAI,IAAI,CAAC,CAAC;AACrF,UAAI,kBAAkB;AACtB,iBAAW,QAAQ,MAAM,OAAO;AAC9B,YAAI,CAAC,UAAU,IAAI,KAAK,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,EAAE,EAAG;AAC1D;AACA,cAAM,MAAM,GAAG,KAAK,IAAI,KAAK,KAAK,EAAE;AACpC,cAAM,UAAU,MAAM,IAAI,GAAG;AAC7B,cAAM;AAAA,UACJ;AAAA,UACA,UAAU,EAAE,GAAG,SAAS,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC,EAAG,IAAI;AAAA,QACjF;AAAA,MACF;AACA,YAAM,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE;AAAA,QAAK,CAAC,MAAM,UAClD,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,GAAG,cAAc,GAAG,MAAM,IAAI,IAAI,MAAM,EAAE,EAAE;AAAA,MACrE;AACA,YAAM,gBAAgB,oBAAI,IAAsB;AAChD,iBAAW,QAAQ,aAAa;AAC9B,cAAM,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,CAAC;AACjD,gBAAQ,KAAK,KAAK,EAAE;AACpB,sBAAc,IAAI,KAAK,MAAM,OAAO;AAAA,MACtC;AACA,aAAO;AAAA,QACL,SAAS,CAAC,GAAG,QAAQ,OAAO,CAAC,EAC1B,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,SAAS,cAAc,IAAI,OAAO,IAAI,KAAK,CAAC,EAAE,EAAE,EAC9E,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AAAA,QAC5D,OAAO;AAAA,QACP,QAAQ,kBAAkB,IAAI,qBAAqB;AAAA,MACrD;AAAA,IACF,QAAQ;AAAA,IAGR;AAAA,EACF;AACA,SAAO,EAAE,GAAG,SAAS,QAAQ,UAAU;AACzC;","names":["readFile","stat","join","resolve","mkdir","readFile","dirname","extname","join","resolve","readFile","dirname","join","resolve","resolve","join","readFile","dirname","resolve","join","dirname","mkdir","readFile","extname","readdir","extname","join","relative","extname","SUPPORTED_EXTENSIONS","normalize","SUPPORTED_EXTENSIONS","extname","walk","readdir","join","relative","resolve","stat","join","readFile","mkdir","readFile","stat","dirname","join","relative","resolve","createHash","readFile","createHash","readFile","resolve","mkdir","dirname","readFile","stat","join","relative","readFile","stat","isAbsolute","join","relative","readFile","relative","resolve","languageFor","isAbsolute","relative","resolve","sep","resolve","isAbsolute","relative","sep","join","stat","readFile","evidence","expansion","join","sourceHashes","stat","relative","isAbsolute"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/lean.ts","../src/hooks/lean-command.ts"],"sourcesContent":["/**\n * Lean mode: the prompt hook's only job is to cut the agent's token spend. It\n * injects ranked file pointers (and code spans for read-only questions) and\n * nothing else — no MCP tools, rules block, or memory capture.\n */\nimport { createHash } from \"node:crypto\";\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { extname, join, resolve } from \"node:path\";\nimport {\n discoverTaskPaths,\n loadProjectArchitectureMap,\n NON_SOURCE_PATH,\n readBoundedSource,\n taskTerms,\n} from \"../context/discovery.js\";\nimport { type ContextCapsule, SessionCapsuleLedger } from \"../context/session-ledger.js\";\nimport { detectTestCommand, findCoveringTest } from \"../context/tests.js\";\nimport { loadFreshIndex } from \"../indexer/engine.js\";\nimport { estimateTokens } from \"../savings/tokens.js\";\nimport type { HookInput } from \"./runtime.js\";\n\nexport const LEAN_POINTER_PREFIX = \"CodeBuddy likely files: \";\nexport const LEAN_LIMITS = {\n paths: 6,\n symbolsPerFile: 3,\n wholeFileLines: 80,\n spanTokens: 1200,\n} as const;\n\nexport type LeanPointer = {\n path: string;\n symbols: Array<{ name: string; startLine: number; endLine: number }>;\n /** The test file that covers this path, when one follows a known convention. */\n test?: string;\n};\n\ntype LoadedPointer = LeanPointer & { lines: string[] };\n\n/** A lean block finished later than this is dropped: the host may already have moved on. */\nexport const LEAN_HOOK_BUDGET_MS = 1500;\n/** Rendering after the BYOK rerank needs this much of the budget. */\nconst RENDER_RESERVE_MS = 300;\n\n/**\n * UserPromptSubmit in lean mode → ranked file pointers, or nothing. Never\n * throws: an error is appended to `.codebuddy/cache/hooks/errors.log` and the\n * turn proceeds as if CodeBuddy were not installed.\n */\nexport async function runLeanHook(\n input: HookInput,\n emit?: (block: string) => Promise<void>,\n budgetMs = LEAN_HOOK_BUDGET_MS,\n): Promise<string> {\n const started = Date.now();\n const repositoryRoot = resolve(input.cwd ?? process.cwd());\n const prompt = input.prompt?.trim() ?? \"\";\n if (prompt === \"\" || prompt.startsWith(\"/\")) return \"\";\n const sessionId = input.session_id ?? \"default\";\n try {\n const { manifest } = await loadFreshIndex(repositoryRoot);\n const discovery = await discoverTaskPaths(\n repositoryRoot,\n prompt,\n manifest,\n loadProjectArchitectureMap(repositoryRoot, manifest),\n // The rerank must answer while rendering still fits in the hook budget.\n { semantic: false, llmDeadlineAt: started + budgetMs - RENDER_RESERVE_MS },\n );\n const terms = taskTerms(prompt);\n const indexed = new Set(Object.keys(manifest.entries));\n const pointers: LeanPointer[] = discovery.paths\n .filter((path) => !NON_SOURCE_PATH.test(path))\n .map((path) => {\n const test = findCoveringTest(path, indexed);\n const table = manifest.entries[path]?.symbolTable ?? [];\n // Symbols the BYOK rerank named come first, then term matches.\n const picked = discovery.symbols?.[path] ?? [];\n const ordered = [\n ...picked.flatMap((name) => table.filter((symbol) => symbol.name === name)),\n ...table.filter(\n (symbol) =>\n !picked.includes(symbol.name) &&\n terms.some((term) => symbol.name.toLowerCase().includes(term)),\n ),\n ];\n return {\n path,\n symbols: ordered.map(({ name, startLine, endLine }) => ({ name, startLine, endLine })),\n ...(test ? { test } : {}),\n };\n });\n const ledger = new SessionCapsuleLedger(repositoryRoot);\n // The host keeps the block for this turn only, so nothing counts as already sent.\n if (input.ephemeral_context) await ledger.reset(sessionId);\n const { text, emitted } = await renderLeanBlock({\n repositoryRoot,\n prompt,\n pointers,\n ledger,\n sessionId,\n testCommand: pointers.length > 0 ? await detectTestCommand(repositoryRoot) : null,\n });\n if (Date.now() - started >= budgetMs) return \"\";\n if (text) await emit?.(text);\n await ledger.markSent(sessionId, emitted);\n return text;\n } catch (error) {\n await logHookError(repositoryRoot, sessionId, error).catch(() => {});\n return \"\";\n }\n}\n\nasync function logHookError(root: string, sessionId: string, error: unknown): Promise<void> {\n const directory = join(root, \".codebuddy\", \"cache\", \"hooks\");\n await mkdir(directory, { recursive: true });\n const message = (error as Error)?.message ?? String(error);\n await appendFile(\n join(directory, \"errors.log\"),\n `${new Date().toISOString()} ${message.replace(/\\s+/g, \" \")} session=${sessionId}\\n`,\n );\n}\n\n/**\n * The lean block for one prompt: a single pointer line, plus current-source\n * spans when the prompt is read-only. Capsules already delivered this session\n * are left out; the caller marks `emitted` as sent once the block is written.\n */\nexport async function renderLeanBlock(input: {\n repositoryRoot: string;\n prompt: string;\n pointers: LeanPointer[];\n ledger: SessionCapsuleLedger;\n sessionId: string;\n /** Sent once per session, after the pointers. */\n testCommand?: string | null;\n}): Promise<{ text: string; emitted: ContextCapsule[] }> {\n const loaded: LoadedPointer[] = [];\n for (const pointer of input.pointers) {\n if (loaded.length === LEAN_LIMITS.paths) break;\n const source = await readBoundedSource(input.repositoryRoot, pointer.path);\n if (source === null) continue; // deleted, oversized or binary since indexing\n loaded.push({\n path: pointer.path,\n symbols: pointer.symbols.slice(0, LEAN_LIMITS.symbolsPerFile),\n ...(pointer.test ? { test: pointer.test } : {}),\n lines: source.split(/\\r?\\n/),\n });\n }\n if (loaded.length === 0) return { text: \"\", emitted: [] };\n\n const capsules: ContextCapsule[] = [\n {\n key: \"lean:pointers\",\n text: LEAN_POINTER_PREFIX + loaded.map(describePointer).join(\", \"),\n },\n ...(input.testCommand\n ? [{ key: \"lean:test-command\", text: `CodeBuddy test command: ${input.testCommand}` }]\n : []),\n ];\n if (isReadOnlyPrompt(input.prompt)) {\n let tokens = 0;\n for (const span of loaded.flatMap(spansOf)) {\n const cost = estimateTokens(span.text);\n if (tokens + cost > LEAN_LIMITS.spanTokens) break;\n tokens += cost;\n capsules.push(span);\n }\n }\n const seen = await input.ledger.seen(input.sessionId, capsules);\n const emitted = capsules.filter((capsule) => !seen.has(capsule.key));\n return { text: emitted.map((capsule) => capsule.text).join(\"\\n\"), emitted };\n}\n\nfunction describePointer(pointer: LoadedPointer): string {\n const ranges = pointer.symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`).join(\", \");\n const test = pointer.test ? `test: ${pointer.test}` : \"\";\n const detail = [ranges, test].filter(Boolean).join(\"; \");\n return detail ? `${pointer.path} (${detail})` : pointer.path;\n}\n\n/** Whole small files; otherwise the matched symbol spans. */\nfunction spansOf(pointer: LoadedPointer): ContextCapsule[] {\n const ranges =\n pointer.lines.length <= LEAN_LIMITS.wholeFileLines\n ? [{ start: 1, end: pointer.lines.length }]\n : pointer.symbols.map((s) => ({ start: s.startLine, end: s.endLine }));\n const language = extname(pointer.path).slice(1);\n return ranges.map(({ start, end }) => {\n const body = pointer.lines.slice(start - 1, end).join(\"\\n\");\n const hash = createHash(\"sha1\").update(body).digest(\"hex\").slice(0, 12);\n return {\n key: `lean:span:${pointer.path}:${start}-${end}:${hash}`,\n text: `${pointer.path}:L${start}-${end}\\n\\`\\`\\`${language}\\n${body}\\n\\`\\`\\``,\n };\n });\n}\n\nconst FORCED_READ_ONLY =\n /\\b(without editing|without changing|don't change|do not change|don't edit|do not edit)\\b/i;\nconst QUESTION_START = /^(how|what|why|where|which|who|explain|describe|summarize)\\b/i;\nconst EDIT_VERB =\n /\\b(add|change|fix|implement|update|remove|delete|rename|refactor|write|create|make|edit|replace|move|migrate|support|build)\\b/i;\n\n/**\n * Whether a prompt asks a question without asking for edits. Conservative: a\n * false positive costs up to the span budget, a false negative costs nothing.\n */\nexport function isReadOnlyPrompt(prompt: string): boolean {\n const text = prompt.trim();\n if (text === \"\" || text.startsWith(\"/\")) return false;\n if (FORCED_READ_ONLY.test(text)) return true;\n const asks = QUESTION_START.test(text) || text.endsWith(\"?\");\n return asks && !EDIT_VERB.test(text);\n}\n","import { runLeanHook } from \"./lean.js\";\nimport { failSoft, readHookInput, writeStdout } from \"./stdio.js\";\n\n/** `codebuddy hooks lean`: read the hook payload, print the lean block, never fail. */\nexport async function runLeanHookCommand(): Promise<void> {\n await failSoft(async () => {\n await runLeanHook(await readHookInput(), writeStdout);\n });\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/llm/summaries.ts"],"sourcesContent":["import { mkdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport {\n loadProjectArchitectureMap,\n NON_SOURCE_PATH,\n readBoundedSource,\n TEST_PATH,\n} from \"../context/discovery.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { loadFreshIndex } from \"../indexer/engine.js\";\nimport type { IndexEntry, IndexManifest } from \"../indexer/types.js\";\nimport { redactSensitive, scanSensitive } from \"../memory-extract/sensitive.js\";\nimport { chatJson } from \"./client.js\";\nimport { type EnabledLlmConfig, resolveLlmConfig } from \"./config.js\";\n\n/**\n * One-line descriptions of files and their main symbols, written by the BYOK\n * model off the prompt path (`codebuddy llm summarize`, `watch`) and cached by\n * content hash. Retrieval reads them so a paraphrased task can match a file\n * whose own text never uses the task's words. Generated text is a hint for\n * ordering only; it never satisfies the evidence floor on its own.\n */\nexport const SUMMARY_PROMPT_VERSION = 1;\nconst LIMITS = {\n fileChars: 160,\n symbolChars: 100,\n signatures: 12,\n headLines: 20,\n /** ~600 tokens per file: long-line (minified) files are cut, never sent whole. */\n headChars: 1_600,\n signatureChars: 160,\n batchTokens: 5_000,\n batchFiles: 8,\n outputTokensPerFile: 120,\n tokensPerMinute: 6_000,\n timeoutMs: 20_000,\n} as const;\n\nexport type LlmSummary = {\n hash: string;\n model: string;\n version: number;\n summary: string;\n symbols: Record<string, string>;\n};\n\ntype Store = Record<string, LlmSummary>;\nconst storePath = (root: string) => join(root, \".codebuddy\", \"cache\", \"llm-summaries.json\");\n\nasync function readStore(root: string): Promise<Store> {\n try {\n return JSON.parse(await readFile(storePath(root), \"utf8\")) as Store;\n } catch {\n return {};\n }\n}\n\nasync function writeStore(root: string, store: Store): Promise<void> {\n await mkdir(join(root, \".codebuddy\", \"cache\"), { recursive: true });\n await writeAtomic(storePath(root), `${JSON.stringify(store)}\\n`);\n}\n\n/** Summaries whose content hash still matches the current index. */\nexport async function loadLlmSummaries(\n root: string,\n manifest: IndexManifest,\n): Promise<Map<string, LlmSummary>> {\n const store = await readStore(root);\n return new Map(\n Object.entries(store).filter(([path, entry]) => manifest.entries[path]?.hash === entry.hash),\n );\n}\n\nconst outputSchema = z.object({\n files: z.array(\n z.object({\n path: z.string(),\n summary: z.string(),\n symbols: z.array(z.object({ name: z.string(), summary: z.string() })).default([]),\n }),\n ),\n});\n\nconst SYSTEM = [\n \"You describe source files for a code search index.\",\n 'Answer with JSON only: {\"files\":[{\"path\":\"...\",\"summary\":\"...\",\"symbols\":[{\"name\":\"...\",\"summary\":\"...\"}]}]}.',\n `For each input file: one sentence (max ${LIMITS.fileChars} chars) saying what it does, using the words a developer would search for;`,\n `for its main symbols (only names listed in \"signatures\"), one short phrase each (max ${LIMITS.symbolChars} chars).`,\n \"Never invent paths or symbol names.\",\n].join(\" \");\n\nexport type SummarizeResult = {\n summarized: number;\n skipped: number;\n remaining: number;\n stoppedBy: \"done\" | \"limit\" | \"budget\" | \"cooldown\" | \"disabled\" | \"aborted\" | \"error\";\n};\n\nexport async function summarizeRepository(\n root: string,\n options: {\n limit?: number;\n onProgress?: (done: number, total: number) => void;\n signal?: AbortSignal;\n /** Injected config (tests, CLI); defaults to `resolveLlmConfig`. */\n config?: EnabledLlmConfig;\n /** Wait between batches to stay under the free tier's tokens/minute. */\n pace?: boolean;\n batchFiles?: number;\n } = {},\n): Promise<SummarizeResult> {\n const resolved = options.config ?? (await resolveLlmConfig(root));\n if (!resolved.enabled || !resolved.features.summaries) {\n return { summarized: 0, skipped: 0, remaining: 0, stoppedBy: \"disabled\" };\n }\n const config = resolved;\n const { manifest } = await loadFreshIndex(root);\n const store = await readStore(root);\n for (const path of Object.keys(store)) if (!manifest.entries[path]) delete store[path];\n\n const pending = await orderByImportance(\n root,\n manifest,\n Object.values(manifest.entries).filter((entry) => {\n const done = store[entry.path];\n return !(\n done?.hash === entry.hash &&\n done.model === config.model &&\n done.version === SUMMARY_PROMPT_VERSION\n );\n }),\n );\n const limit = options.limit ?? Number.POSITIVE_INFINITY;\n let summarized = 0;\n let skipped = 0;\n let index = 0;\n let stoppedBy: SummarizeResult[\"stoppedBy\"] = \"done\";\n\n while (index < pending.length) {\n if (options.signal?.aborted) {\n stoppedBy = \"aborted\";\n break;\n }\n if (summarized >= limit) {\n stoppedBy = \"limit\";\n break;\n }\n const batch: Array<{ entry: IndexEntry; input: FileInput }> = [];\n let tokens = 0;\n while (index < pending.length && batch.length < (options.batchFiles ?? LIMITS.batchFiles)) {\n if (summarized + batch.length >= limit) break;\n const entry = pending[index] as IndexEntry;\n const input = await fileInput(root, entry);\n if (input === null) {\n skipped++;\n index++;\n continue;\n }\n const cost = Math.ceil(JSON.stringify(input).length / 4);\n if (batch.length > 0 && tokens + cost > LIMITS.batchTokens - 600) break;\n batch.push({ entry, input });\n tokens += cost;\n index++;\n }\n if (batch.length === 0) continue;\n\n const started = Date.now();\n const result = await chatJson(config, {\n system: SYSTEM,\n user: JSON.stringify({ files: batch.map(({ input }) => input) }),\n schema: outputSchema,\n maxTokens: LIMITS.outputTokensPerFile * batch.length,\n deadlineMs: LIMITS.timeoutMs,\n purpose: \"background\",\n repositoryRoot: root,\n });\n if (!result.ok) {\n // A batch the provider rejects outright (bad output, or e.g. 413 for one\n // file) is skipped, not retried forever; limits and outages stop the run.\n if (result.reason === \"invalid\" || (result.reason === \"http\" && batch.length === 1)) {\n skipped += batch.length;\n continue;\n }\n index -= batch.length;\n stoppedBy =\n result.reason === \"budget\"\n ? \"budget\"\n : result.reason === \"cooldown\" || result.reason === \"rate-limited\"\n ? \"cooldown\"\n : \"error\";\n break;\n }\n for (const { entry } of batch) {\n const answer = result.value.files.find((file) => file.path === entry.path);\n if (!answer) {\n skipped++;\n continue;\n }\n const known = new Set((entry.symbolTable ?? []).map((symbol) => symbol.name));\n store[entry.path] = {\n hash: entry.hash,\n model: config.model,\n version: SUMMARY_PROMPT_VERSION,\n summary: answer.summary.trim().slice(0, LIMITS.fileChars),\n symbols: Object.fromEntries(\n answer.symbols\n .filter((symbol) => known.has(symbol.name))\n .map((symbol) => [symbol.name, symbol.summary.trim().slice(0, LIMITS.symbolChars)]),\n ),\n };\n summarized++;\n }\n await writeStore(root, store);\n options.onProgress?.(index, pending.length);\n if (options.pace !== false) {\n const wait = (result.tokens / LIMITS.tokensPerMinute) * 60_000 - (Date.now() - started);\n if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));\n }\n }\n await writeStore(root, store);\n return { summarized, skipped, remaining: pending.length - index, stoppedBy };\n}\n\ntype FileInput = {\n path: string;\n language: string;\n summary: string;\n signatures: string[];\n head: string;\n};\n\n/** What is sent for one file; null for unreadable or secret-bearing files. */\nasync function fileInput(root: string, entry: IndexEntry): Promise<FileInput | null> {\n const source = await readBoundedSource(root, entry.path);\n if (source === null || scanSensitive(source).sensitive) return null;\n return {\n path: entry.path,\n language: entry.language,\n summary: redactSensitive(entry.summary),\n signatures: (entry.symbolTable ?? [])\n .slice(0, LIMITS.signatures)\n .map((symbol) => redactSensitive(symbol.signature.slice(0, LIMITS.signatureChars))),\n head: redactSensitive(\n source.split(/\\r?\\n/).slice(0, LIMITS.headLines).join(\"\\n\").slice(0, LIMITS.headChars),\n ),\n };\n}\n\n/** Most-imported source first; tests and non-source files last. */\nasync function orderByImportance(\n root: string,\n manifest: IndexManifest,\n entries: IndexEntry[],\n): Promise<IndexEntry[]> {\n const fanIn = new Map<string, number>();\n try {\n for (const edge of (await loadProjectArchitectureMap(root, manifest)).edges) {\n fanIn.set(edge.to, (fanIn.get(edge.to) ?? 0) + 1);\n }\n } catch {\n // No map: path order is still a usable order.\n }\n const tier = (path: string) => (TEST_PATH.test(path) || NON_SOURCE_PATH.test(path) ? 1 : 0);\n return [...entries].sort(\n (left, right) =>\n tier(left.path) - tier(right.path) ||\n (fanIn.get(right.path) ?? 0) - (fanIn.get(left.path) ?? 0) ||\n left.path.localeCompare(right.path),\n );\n}\n"],"mappings":"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","names":[]}