@dreb/semantic-search 2.0.5 → 2.0.7

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 (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/server.js +13 -3
  3. package/dist/chunker.d.ts.map +1 -1
  4. package/dist/chunker.js.map +1 -1
  5. package/dist/db.d.ts.map +1 -1
  6. package/dist/db.js +0 -1
  7. package/dist/db.js.map +1 -1
  8. package/dist/embedder.d.ts.map +1 -1
  9. package/dist/embedder.js +3 -6
  10. package/dist/embedder.js.map +1 -1
  11. package/dist/format.d.ts.map +1 -1
  12. package/dist/format.js.map +1 -1
  13. package/dist/index-manager.d.ts +4 -0
  14. package/dist/index-manager.d.ts.map +1 -1
  15. package/dist/index-manager.js +2 -3
  16. package/dist/index-manager.js.map +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/mcp-server.d.ts.map +1 -1
  19. package/dist/mcp-server.js.map +1 -1
  20. package/dist/metrics/bm25.d.ts.map +1 -1
  21. package/dist/metrics/bm25.js.map +1 -1
  22. package/dist/metrics/git-recency.d.ts.map +1 -1
  23. package/dist/metrics/git-recency.js.map +1 -1
  24. package/dist/metrics/import-graph.d.ts.map +1 -1
  25. package/dist/metrics/import-graph.js.map +1 -1
  26. package/dist/metrics/path-match.d.ts.map +1 -1
  27. package/dist/metrics/path-match.js.map +1 -1
  28. package/dist/metrics/symbol-match.d.ts.map +1 -1
  29. package/dist/metrics/symbol-match.js.map +1 -1
  30. package/dist/metrics/tokenize.d.ts.map +1 -1
  31. package/dist/metrics/tokenize.js.map +1 -1
  32. package/dist/poem.d.ts.map +1 -1
  33. package/dist/poem.js.map +1 -1
  34. package/dist/query-classifier.d.ts.map +1 -1
  35. package/dist/query-classifier.js.map +1 -1
  36. package/dist/scanner.d.ts.map +1 -1
  37. package/dist/scanner.js.map +1 -1
  38. package/dist/search.d.ts.map +1 -1
  39. package/dist/search.js +3 -5
  40. package/dist/search.js.map +1 -1
  41. package/dist/text-chunker.d.ts.map +1 -1
  42. package/dist/text-chunker.js.map +1 -1
  43. package/dist/tree-sitter-chunker.d.ts.map +1 -1
  44. package/dist/tree-sitter-chunker.js.map +1 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/vector-store.d.ts.map +1 -1
  47. package/dist/vector-store.js.map +1 -1
  48. package/package.json +3 -3
  49. package/skills/search/SKILL.md +5 -0
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,OAAO,KAAK,EAAe,qBAAqB,EAAgB,YAAY,EAAe,MAAM,YAAY,CAAC;AAe9G,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAMD,MAAM,WAAW,mBAAmB;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,WAAW,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CAChD;AAMD,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,WAAW,CAAoC;IAEvD,YAAY,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAG7D;IAED,oEAAoE;IACpE,MAAM,CAAC,WAAW,IAAI,OAAO,CAE5B;IAED;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAqH5E;IAED,wDAAwD;IACxD,QAAQ,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAGnD;IAED;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAUhC;IAED,yBAAyB;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAc3B;IAMD,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,mBAAmB;YAqBb,mBAAmB;CA4BjC","sourcesContent":["/**\n * Main search API.\n *\n * Orchestrates: check/build index → compute all 6 metrics → classify query\n * → duplicate columns → POEM rank → assemble results.\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { SearchDatabase } from \"./db.js\";\nimport { Embedder } from \"./embedder.js\";\nimport { IndexManager } from \"./index-manager.js\";\nimport { computeBm25Scores } from \"./metrics/bm25.js\";\nimport { computeGitRecencyScores } from \"./metrics/git-recency.js\";\nimport { computeImportGraphScores } from \"./metrics/import-graph.js\";\nimport { computePathMatchScores } from \"./metrics/path-match.js\";\nimport { computeSymbolMatchScores } from \"./metrics/symbol-match.js\";\nimport { poemRank } from \"./poem.js\";\nimport { classifyQuery } from \"./query-classifier.js\";\nimport type { IndexConfig, IndexProgressCallback, MetricScores, SearchResult, StoredChunk } from \"./types.js\";\nimport { topKSimilar } from \"./vector-store.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MODEL_NAME = \"Xenova/all-MiniLM-L6-v2\";\nconst DEFAULT_RESULT_LIMIT = 20;\nconst METRIC_CANDIDATE_LIMIT = 1000;\n\n// ============================================================================\n// Search Options\n// ============================================================================\n\nexport interface SearchOptions {\n\t/** Maximum number of results to return. Default: 20. */\n\tlimit?: number;\n\t/** Restrict search to files under this path (relative to project root). */\n\tpathFilter?: string;\n\t/** Progress callback for indexing operations. */\n\tonProgress?: IndexProgressCallback;\n}\n\n// ============================================================================\n// Search Engine Options\n// ============================================================================\n\nexport interface SearchEngineOptions {\n\t/** Absolute path to the index database directory. Default: `<projectRoot>/.search-index` */\n\tindexDir?: string;\n\t/** Absolute path to a global memory directory to include in the index. */\n\tglobalMemoryDir?: string;\n\t/** Absolute path to the model cache directory. Default: `~/.cache/semantic-search/models` */\n\tmodelCacheDir?: string;\n\t/** Provider of additional directories to include in scans (bypasses gitignore). */\n\tvisibleDirs?: (projectRoot: string) => string[];\n}\n\n// ============================================================================\n// Search Engine\n// ============================================================================\n\nexport class SearchEngine {\n\tprivate readonly projectRoot: string;\n\tprivate readonly options: SearchEngineOptions;\n\tprivate indexManager: IndexManager | null = null;\n\tprivate embedderPromise: Promise<Embedder> | null = null;\n\tprivate searchQueue: Promise<void> = Promise.resolve();\n\n\tconstructor(projectRoot: string, options?: SearchEngineOptions) {\n\t\tthis.projectRoot = projectRoot;\n\t\tthis.options = options ?? {};\n\t}\n\n\t/** Check if semantic search is available (requires node:sqlite). */\n\tstatic isAvailable(): boolean {\n\t\treturn IndexManager.isAvailable();\n\t}\n\n\t/**\n\t * Search the codebase with a natural language or identifier query.\n\t *\n\t * On first call, builds the index (scans, chunks, embeds). Subsequent calls\n\t * incrementally update changed files before searching.\n\t */\n\tasync search(query: string, options?: SearchOptions): Promise<SearchResult[]> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst limit = options?.limit ?? DEFAULT_RESULT_LIMIT;\n\t\t\tconst onProgress = options?.onProgress;\n\n\t\t\t// Ensure index is built and up to date\n\t\t\tconst indexManager = this.getIndexManager();\n\t\t\tconst db = indexManager.getDb();\n\n\t\t\t// Share our embedder with IndexManager so it doesn't create a second one\n\t\t\tconst embedder = await this.getOrCreateEmbedder();\n\t\t\tindexManager.setEmbedder(embedder);\n\n\t\t\tawait indexManager.buildIndex(onProgress);\n\t\t\tawait indexManager.ensureEmbeddings(onProgress);\n\n\t\t\t// Get all chunks (potentially filtered by path)\n\t\t\tlet allChunks = db.getAllChunks();\n\t\t\tif (options?.pathFilter) {\n\t\t\t\tconst filter = options.pathFilter;\n\t\t\t\tallChunks = allChunks.filter((c) => c.filePath.startsWith(filter));\n\t\t\t}\n\n\t\t\tif (allChunks.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\t// Classify query type for POEM column weighting\n\t\t\tconst queryType = classifyQuery(query);\n\n\t\t\t// Compute all 6 metrics\n\t\t\tonProgress?.(\"searching\", 0, 6);\n\n\t\t\t// 1. BM25 (FTS5)\n\t\t\tconst bm25Scores = computeBm25Scores(db, sanitizeFtsQuery(query), METRIC_CANDIDATE_LIMIT);\n\t\t\tonProgress?.(\"searching\", 1, 6);\n\n\t\t\t// 2. Cosine similarity (vector search)\n\t\t\tconst cosineScores = await this.computeVectorScores(db, query, METRIC_CANDIDATE_LIMIT, onProgress);\n\t\t\tonProgress?.(\"searching\", 2, 6);\n\n\t\t\t// 3. Path match\n\t\t\tconst pathScores = computePathMatchScores(query, allChunks);\n\t\t\tonProgress?.(\"searching\", 3, 6);\n\n\t\t\t// 4. Symbol match\n\t\t\tconst symbols = db.getAllSymbols();\n\t\t\tconst symbolScores = computeSymbolMatchScores(query, symbols);\n\t\t\tonProgress?.(\"searching\", 4, 6);\n\n\t\t\t// 5. Import graph (use BM25 + cosine as seed scores, aggregated per file)\n\t\t\t// Only use files with strong scores as seeds — low-scoring files (e.g. from\n\t\t\t// common OR terms matching everywhere) pollute the seed set and prevent\n\t\t\t// meaningful propagation.\n\t\t\tconst fileSeedScores = aggregateFileScores(allChunks, bm25Scores, cosineScores);\n\t\t\tconst seedThreshold = computeSeedThreshold(fileSeedScores);\n\t\t\tconst filteredSeeds = new Map<number, number>();\n\t\t\tfor (const [fileId, score] of fileSeedScores) {\n\t\t\t\tif (score >= seedThreshold) filteredSeeds.set(fileId, score);\n\t\t\t}\n\t\t\tconst fileIdToChunkIds = buildFileChunkMap(allChunks);\n\t\t\tconst importScores = computeImportGraphScores(db, filteredSeeds, fileIdToChunkIds);\n\t\t\tonProgress?.(\"searching\", 5, 6);\n\n\t\t\t// 6. Git recency\n\t\t\tconst recencyScores = await computeGitRecencyScores(this.projectRoot, allChunks);\n\t\t\tonProgress?.(\"searching\", 6, 6);\n\n\t\t\t// Build MetricScores for each candidate chunk\n\t\t\tconst candidateIds = collectCandidateIds(\n\t\t\t\tbm25Scores,\n\t\t\t\tcosineScores,\n\t\t\t\tpathScores,\n\t\t\t\tsymbolScores,\n\t\t\t\timportScores,\n\t\t\t\trecencyScores,\n\t\t\t);\n\t\t\tconst candidates = new Map<number, MetricScores>();\n\n\t\t\tfor (const id of candidateIds) {\n\t\t\t\tcandidates.set(id, {\n\t\t\t\t\tbm25: bm25Scores.get(id) ?? 0,\n\t\t\t\t\tcosine: cosineScores.get(id) ?? 0,\n\t\t\t\t\tpathMatch: pathScores.get(id) ?? 0,\n\t\t\t\t\tsymbolMatch: symbolScores.get(id) ?? 0,\n\t\t\t\t\timportGraph: importScores.get(id) ?? 0,\n\t\t\t\t\tgitRecency: recencyScores.get(id) ?? 0,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (candidates.size === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\t// POEM rank\n\t\t\tconst ranked = poemRank(candidates, queryType);\n\n\t\t\t// Assemble results\n\t\t\tconst chunkMap = new Map<number, StoredChunk>();\n\t\t\tfor (const chunk of allChunks) {\n\t\t\t\tchunkMap.set(chunk.id, chunk);\n\t\t\t}\n\n\t\t\tconst results: SearchResult[] = [];\n\t\t\tfor (const candidate of ranked.slice(0, limit)) {\n\t\t\t\tconst chunk = chunkMap.get(candidate.id);\n\t\t\t\tif (chunk) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tchunk,\n\t\t\t\t\t\tscores: candidate.scores,\n\t\t\t\t\t\trank: candidate.rank,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn results;\n\t\t});\n\t}\n\n\t/** Get index stats without opening a new connection. */\n\tgetStats(): { files: number; chunks: number } | null {\n\t\tif (!this.indexManager) return null;\n\t\treturn this.indexManager.getStats();\n\t}\n\n\t/**\n\t * Reset the search index — delete the DB and close the IndexManager.\n\t *\n\t * Preserves the embedder (expensive ONNX model, unrelated to index state).\n\t * The next `search()` call will lazily re-create the IndexManager and build\n\t * a fresh index from scratch.\n\t */\n\tasync resetIndex(): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.indexManager?.close();\n\t\t\tthis.indexManager = null;\n\t\t\tconst config = this.getIndexConfig();\n\t\t\tconst dbPath = path.join(config.indexDir, \"search.db\");\n\t\t\tif (existsSync(dbPath)) {\n\t\t\t\tunlinkSync(dbPath);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Dispose resources. */\n\tasync close(): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.indexManager?.close();\n\t\t\tthis.indexManager = null;\n\t\t\t// Dispose embedder if it was created\n\t\t\tif (this.embedderPromise) {\n\t\t\t\ttry {\n\t\t\t\t\tconst embedder = await this.embedderPromise;\n\t\t\t\t\tembedder.dispose();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.embedderPromise = null;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t// ========================================================================\n\t// Private\n\t// ========================================================================\n\n\tprivate enqueue<T>(work: () => Promise<T>): Promise<T> {\n\t\tlet resolve!: () => void;\n\t\tconst gate = new Promise<void>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tconst waitFor = this.searchQueue;\n\t\tthis.searchQueue = gate;\n\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\tawait waitFor;\n\t\t\t\treturn await work();\n\t\t\t} finally {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\tprivate getIndexManager(): IndexManager {\n\t\tif (!this.indexManager) {\n\t\t\tconst config = this.getIndexConfig();\n\t\t\tthis.indexManager = new IndexManager(config);\n\t\t\tthis.indexManager.open();\n\t\t}\n\t\treturn this.indexManager;\n\t}\n\n\tprivate getIndexConfig(): IndexConfig {\n\t\tconst visibleDirsFn = this.options.visibleDirs;\n\t\treturn {\n\t\t\tprojectRoot: this.projectRoot,\n\t\t\tindexDir: this.options.indexDir ?? path.join(this.projectRoot, \".search-index\"),\n\t\t\tglobalMemoryDir: this.options.globalMemoryDir,\n\t\t\tvisibleDirs: visibleDirsFn ? visibleDirsFn(this.projectRoot) : undefined,\n\t\t\tmodelName: DEFAULT_MODEL_NAME,\n\t\t};\n\t}\n\n\tprivate getOrCreateEmbedder(): Promise<Embedder> {\n\t\tif (!this.embedderPromise) {\n\t\t\tthis.embedderPromise = (async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst config = this.getIndexConfig();\n\t\t\t\t\tconst embedder = new Embedder({\n\t\t\t\t\t\tmodelCacheDir:\n\t\t\t\t\t\t\tthis.options.modelCacheDir ?? path.join(homedir(), \".cache\", \"semantic-search\", \"models\"),\n\t\t\t\t\t\tmodelName: config.modelName,\n\t\t\t\t\t});\n\t\t\t\t\tawait embedder.initialize();\n\t\t\t\t\treturn embedder;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.embedderPromise = null;\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t})();\n\t\t}\n\t\treturn this.embedderPromise;\n\t}\n\n\tprivate async computeVectorScores(\n\t\tdb: SearchDatabase,\n\t\tquery: string,\n\t\tlimit: number,\n\t\t_onProgress?: IndexProgressCallback,\n\t): Promise<Map<number, number>> {\n\t\tconst config = this.getIndexConfig();\n\t\tconst embedder = await this.getOrCreateEmbedder();\n\n\t\t// Embed the query\n\t\tconst queryVector = await embedder.embedQuery(query);\n\n\t\t// Get all stored embeddings\n\t\tconst storedVectors = db.getAllEmbeddings(config.modelName);\n\n\t\tif (storedVectors.size === 0) {\n\t\t\treturn new Map();\n\t\t}\n\n\t\tconst topK = topKSimilar(queryVector, storedVectors, limit);\n\n\t\t// Convert to Map, clamping negative similarities to 0\n\t\tconst scores = new Map<number, number>();\n\t\tfor (const { id, score } of topK) {\n\t\t\tscores.set(id, Math.max(0, score));\n\t\t}\n\t\treturn scores;\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Collect all unique chunk IDs that appear in any metric's results. */\nfunction collectCandidateIds(...scoreMaps: Map<number, number>[]): Set<number> {\n\tconst ids = new Set<number>();\n\tfor (const map of scoreMaps) {\n\t\tfor (const id of map.keys()) {\n\t\t\tids.add(id);\n\t\t}\n\t}\n\treturn ids;\n}\n\n/** Aggregate chunk-level scores to file-level scores (max per file). */\nfunction aggregateFileScores(chunks: StoredChunk[], ...scoreMaps: Map<number, number>[]): Map<number, number> {\n\tconst fileScores = new Map<number, number>();\n\n\tfor (const chunk of chunks) {\n\t\tlet maxScore = 0;\n\t\tfor (const map of scoreMaps) {\n\t\t\tconst s = map.get(chunk.id);\n\t\t\tif (s !== undefined && s > maxScore) maxScore = s;\n\t\t}\n\t\tif (maxScore > 0) {\n\t\t\tconst existing = fileScores.get(chunk.fileId);\n\t\t\tif (existing === undefined || maxScore > existing) {\n\t\t\t\tfileScores.set(chunk.fileId, maxScore);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fileScores;\n}\n\n/**\n * Compute a dynamic threshold for import graph seeds.\n * Uses the median score — only the top half of files are strong enough seeds.\n * Falls back to 0.1 minimum to avoid accepting near-zero scores.\n */\nfunction computeSeedThreshold(fileScores: Map<number, number>): number {\n\tif (fileScores.size === 0) return 0;\n\tconst sorted = [...fileScores.values()].sort((a, b) => b - a);\n\tconst median = sorted[Math.floor(sorted.length / 2)];\n\treturn Math.max(median, 0.1);\n}\n\n/** Build a map of fileId → chunk IDs for that file. */\nfunction buildFileChunkMap(chunks: StoredChunk[]): Map<number, number[]> {\n\tconst map = new Map<number, number[]>();\n\tfor (const chunk of chunks) {\n\t\tconst existing = map.get(chunk.fileId);\n\t\tif (existing) existing.push(chunk.id);\n\t\telse map.set(chunk.fileId, [chunk.id]);\n\t}\n\treturn map;\n}\n\n/** Common English stopwords to exclude from FTS queries. */\nconst STOPWORDS = new Set([\n\t\"a\",\n\t\"an\",\n\t\"and\",\n\t\"are\",\n\t\"as\",\n\t\"at\",\n\t\"be\",\n\t\"but\",\n\t\"by\",\n\t\"for\",\n\t\"from\",\n\t\"had\",\n\t\"has\",\n\t\"have\",\n\t\"he\",\n\t\"her\",\n\t\"his\",\n\t\"how\",\n\t\"i\",\n\t\"if\",\n\t\"in\",\n\t\"into\",\n\t\"is\",\n\t\"it\",\n\t\"its\",\n\t\"me\",\n\t\"my\",\n\t\"no\",\n\t\"not\",\n\t\"of\",\n\t\"on\",\n\t\"or\",\n\t\"our\",\n\t\"she\",\n\t\"so\",\n\t\"than\",\n\t\"that\",\n\t\"the\",\n\t\"their\",\n\t\"them\",\n\t\"then\",\n\t\"there\",\n\t\"these\",\n\t\"they\",\n\t\"this\",\n\t\"to\",\n\t\"up\",\n\t\"us\",\n\t\"was\",\n\t\"we\",\n\t\"what\",\n\t\"when\",\n\t\"where\",\n\t\"which\",\n\t\"who\",\n\t\"will\",\n\t\"with\",\n\t\"would\",\n\t\"you\",\n\t\"your\",\n]);\n\n/**\n * Sanitize a query string for FTS5 MATCH syntax.\n * FTS5 chokes on certain characters — strip operators and wrap terms.\n *\n * Removes stopwords and uses OR between terms so multi-word queries return\n * partial matches (FTS5's default implicit AND is too restrictive).\n */\nfunction sanitizeFtsQuery(query: string): string {\n\t// Remove FTS5 operators and special chars\n\tconst cleaned = query\n\t\t.replace(/[*\"():^{}[\\]~!@#$%&=+|<>]/g, \" \")\n\t\t.replace(/\\bAND\\b|\\bOR\\b|\\bNOT\\b|\\bNEAR\\b/gi, \" \")\n\t\t.trim();\n\n\t// Split into tokens, strip leading hyphens (FTS5 NOT operator), remove stopwords, join with OR\n\tconst tokens = cleaned\n\t\t.split(/\\s+/)\n\t\t.map((t) => t.replace(/^-+/, \"\"))\n\t\t.filter((t) => t.length > 0 && !STOPWORDS.has(t.toLowerCase()));\n\tif (tokens.length === 0) return '\"\"';\n\treturn tokens.join(\" OR \");\n}\n"]}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,OAAO,KAAK,EAAe,qBAAqB,EAAgB,YAAY,EAAe,MAAM,YAAY,CAAC;AAe9G,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAMD,MAAM,WAAW,mBAAmB;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,WAAW,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CAChD;AAMD,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,WAAW,CAAoC;gBAE3C,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB;IAK9D,oEAAoE;IACpE,MAAM,CAAC,WAAW,IAAI,OAAO;IAI7B;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAuH7E,wDAAwD;IACxD,QAAQ,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKpD;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjC,yBAAyB;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5B,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,mBAAmB;YAqBb,mBAAmB;CA4BjC"}
package/dist/search.js CHANGED
@@ -27,12 +27,10 @@ const METRIC_CANDIDATE_LIMIT = 1000;
27
27
  // Search Engine
28
28
  // ============================================================================
29
29
  export class SearchEngine {
30
- projectRoot;
31
- options;
32
- indexManager = null;
33
- embedderPromise = null;
34
- searchQueue = Promise.resolve();
35
30
  constructor(projectRoot, options) {
31
+ this.indexManager = null;
32
+ this.embedderPromise = null;
33
+ this.searchQueue = Promise.resolve();
36
34
  this.projectRoot = projectRoot;
37
35
  this.options = options ?? {};
38
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main search API.\n *\n * Orchestrates: check/build index → compute all 6 metrics → classify query\n * → duplicate columns → POEM rank → assemble results.\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { SearchDatabase } from \"./db.js\";\nimport { Embedder } from \"./embedder.js\";\nimport { IndexManager } from \"./index-manager.js\";\nimport { computeBm25Scores } from \"./metrics/bm25.js\";\nimport { computeGitRecencyScores } from \"./metrics/git-recency.js\";\nimport { computeImportGraphScores } from \"./metrics/import-graph.js\";\nimport { computePathMatchScores } from \"./metrics/path-match.js\";\nimport { computeSymbolMatchScores } from \"./metrics/symbol-match.js\";\nimport { poemRank } from \"./poem.js\";\nimport { classifyQuery } from \"./query-classifier.js\";\nimport type { IndexConfig, IndexProgressCallback, MetricScores, SearchResult, StoredChunk } from \"./types.js\";\nimport { topKSimilar } from \"./vector-store.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MODEL_NAME = \"Xenova/all-MiniLM-L6-v2\";\nconst DEFAULT_RESULT_LIMIT = 20;\nconst METRIC_CANDIDATE_LIMIT = 1000;\n\n// ============================================================================\n// Search Options\n// ============================================================================\n\nexport interface SearchOptions {\n\t/** Maximum number of results to return. Default: 20. */\n\tlimit?: number;\n\t/** Restrict search to files under this path (relative to project root). */\n\tpathFilter?: string;\n\t/** Progress callback for indexing operations. */\n\tonProgress?: IndexProgressCallback;\n}\n\n// ============================================================================\n// Search Engine Options\n// ============================================================================\n\nexport interface SearchEngineOptions {\n\t/** Absolute path to the index database directory. Default: `<projectRoot>/.search-index` */\n\tindexDir?: string;\n\t/** Absolute path to a global memory directory to include in the index. */\n\tglobalMemoryDir?: string;\n\t/** Absolute path to the model cache directory. Default: `~/.cache/semantic-search/models` */\n\tmodelCacheDir?: string;\n\t/** Provider of additional directories to include in scans (bypasses gitignore). */\n\tvisibleDirs?: (projectRoot: string) => string[];\n}\n\n// ============================================================================\n// Search Engine\n// ============================================================================\n\nexport class SearchEngine {\n\tprivate readonly projectRoot: string;\n\tprivate readonly options: SearchEngineOptions;\n\tprivate indexManager: IndexManager | null = null;\n\tprivate embedderPromise: Promise<Embedder> | null = null;\n\tprivate searchQueue: Promise<void> = Promise.resolve();\n\n\tconstructor(projectRoot: string, options?: SearchEngineOptions) {\n\t\tthis.projectRoot = projectRoot;\n\t\tthis.options = options ?? {};\n\t}\n\n\t/** Check if semantic search is available (requires node:sqlite). */\n\tstatic isAvailable(): boolean {\n\t\treturn IndexManager.isAvailable();\n\t}\n\n\t/**\n\t * Search the codebase with a natural language or identifier query.\n\t *\n\t * On first call, builds the index (scans, chunks, embeds). Subsequent calls\n\t * incrementally update changed files before searching.\n\t */\n\tasync search(query: string, options?: SearchOptions): Promise<SearchResult[]> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst limit = options?.limit ?? DEFAULT_RESULT_LIMIT;\n\t\t\tconst onProgress = options?.onProgress;\n\n\t\t\t// Ensure index is built and up to date\n\t\t\tconst indexManager = this.getIndexManager();\n\t\t\tconst db = indexManager.getDb();\n\n\t\t\t// Share our embedder with IndexManager so it doesn't create a second one\n\t\t\tconst embedder = await this.getOrCreateEmbedder();\n\t\t\tindexManager.setEmbedder(embedder);\n\n\t\t\tawait indexManager.buildIndex(onProgress);\n\t\t\tawait indexManager.ensureEmbeddings(onProgress);\n\n\t\t\t// Get all chunks (potentially filtered by path)\n\t\t\tlet allChunks = db.getAllChunks();\n\t\t\tif (options?.pathFilter) {\n\t\t\t\tconst filter = options.pathFilter;\n\t\t\t\tallChunks = allChunks.filter((c) => c.filePath.startsWith(filter));\n\t\t\t}\n\n\t\t\tif (allChunks.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\t// Classify query type for POEM column weighting\n\t\t\tconst queryType = classifyQuery(query);\n\n\t\t\t// Compute all 6 metrics\n\t\t\tonProgress?.(\"searching\", 0, 6);\n\n\t\t\t// 1. BM25 (FTS5)\n\t\t\tconst bm25Scores = computeBm25Scores(db, sanitizeFtsQuery(query), METRIC_CANDIDATE_LIMIT);\n\t\t\tonProgress?.(\"searching\", 1, 6);\n\n\t\t\t// 2. Cosine similarity (vector search)\n\t\t\tconst cosineScores = await this.computeVectorScores(db, query, METRIC_CANDIDATE_LIMIT, onProgress);\n\t\t\tonProgress?.(\"searching\", 2, 6);\n\n\t\t\t// 3. Path match\n\t\t\tconst pathScores = computePathMatchScores(query, allChunks);\n\t\t\tonProgress?.(\"searching\", 3, 6);\n\n\t\t\t// 4. Symbol match\n\t\t\tconst symbols = db.getAllSymbols();\n\t\t\tconst symbolScores = computeSymbolMatchScores(query, symbols);\n\t\t\tonProgress?.(\"searching\", 4, 6);\n\n\t\t\t// 5. Import graph (use BM25 + cosine as seed scores, aggregated per file)\n\t\t\t// Only use files with strong scores as seeds — low-scoring files (e.g. from\n\t\t\t// common OR terms matching everywhere) pollute the seed set and prevent\n\t\t\t// meaningful propagation.\n\t\t\tconst fileSeedScores = aggregateFileScores(allChunks, bm25Scores, cosineScores);\n\t\t\tconst seedThreshold = computeSeedThreshold(fileSeedScores);\n\t\t\tconst filteredSeeds = new Map<number, number>();\n\t\t\tfor (const [fileId, score] of fileSeedScores) {\n\t\t\t\tif (score >= seedThreshold) filteredSeeds.set(fileId, score);\n\t\t\t}\n\t\t\tconst fileIdToChunkIds = buildFileChunkMap(allChunks);\n\t\t\tconst importScores = computeImportGraphScores(db, filteredSeeds, fileIdToChunkIds);\n\t\t\tonProgress?.(\"searching\", 5, 6);\n\n\t\t\t// 6. Git recency\n\t\t\tconst recencyScores = await computeGitRecencyScores(this.projectRoot, allChunks);\n\t\t\tonProgress?.(\"searching\", 6, 6);\n\n\t\t\t// Build MetricScores for each candidate chunk\n\t\t\tconst candidateIds = collectCandidateIds(\n\t\t\t\tbm25Scores,\n\t\t\t\tcosineScores,\n\t\t\t\tpathScores,\n\t\t\t\tsymbolScores,\n\t\t\t\timportScores,\n\t\t\t\trecencyScores,\n\t\t\t);\n\t\t\tconst candidates = new Map<number, MetricScores>();\n\n\t\t\tfor (const id of candidateIds) {\n\t\t\t\tcandidates.set(id, {\n\t\t\t\t\tbm25: bm25Scores.get(id) ?? 0,\n\t\t\t\t\tcosine: cosineScores.get(id) ?? 0,\n\t\t\t\t\tpathMatch: pathScores.get(id) ?? 0,\n\t\t\t\t\tsymbolMatch: symbolScores.get(id) ?? 0,\n\t\t\t\t\timportGraph: importScores.get(id) ?? 0,\n\t\t\t\t\tgitRecency: recencyScores.get(id) ?? 0,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (candidates.size === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\t// POEM rank\n\t\t\tconst ranked = poemRank(candidates, queryType);\n\n\t\t\t// Assemble results\n\t\t\tconst chunkMap = new Map<number, StoredChunk>();\n\t\t\tfor (const chunk of allChunks) {\n\t\t\t\tchunkMap.set(chunk.id, chunk);\n\t\t\t}\n\n\t\t\tconst results: SearchResult[] = [];\n\t\t\tfor (const candidate of ranked.slice(0, limit)) {\n\t\t\t\tconst chunk = chunkMap.get(candidate.id);\n\t\t\t\tif (chunk) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tchunk,\n\t\t\t\t\t\tscores: candidate.scores,\n\t\t\t\t\t\trank: candidate.rank,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn results;\n\t\t});\n\t}\n\n\t/** Get index stats without opening a new connection. */\n\tgetStats(): { files: number; chunks: number } | null {\n\t\tif (!this.indexManager) return null;\n\t\treturn this.indexManager.getStats();\n\t}\n\n\t/**\n\t * Reset the search index — delete the DB and close the IndexManager.\n\t *\n\t * Preserves the embedder (expensive ONNX model, unrelated to index state).\n\t * The next `search()` call will lazily re-create the IndexManager and build\n\t * a fresh index from scratch.\n\t */\n\tasync resetIndex(): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.indexManager?.close();\n\t\t\tthis.indexManager = null;\n\t\t\tconst config = this.getIndexConfig();\n\t\t\tconst dbPath = path.join(config.indexDir, \"search.db\");\n\t\t\tif (existsSync(dbPath)) {\n\t\t\t\tunlinkSync(dbPath);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Dispose resources. */\n\tasync close(): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.indexManager?.close();\n\t\t\tthis.indexManager = null;\n\t\t\t// Dispose embedder if it was created\n\t\t\tif (this.embedderPromise) {\n\t\t\t\ttry {\n\t\t\t\t\tconst embedder = await this.embedderPromise;\n\t\t\t\t\tembedder.dispose();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.embedderPromise = null;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t// ========================================================================\n\t// Private\n\t// ========================================================================\n\n\tprivate enqueue<T>(work: () => Promise<T>): Promise<T> {\n\t\tlet resolve!: () => void;\n\t\tconst gate = new Promise<void>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tconst waitFor = this.searchQueue;\n\t\tthis.searchQueue = gate;\n\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\tawait waitFor;\n\t\t\t\treturn await work();\n\t\t\t} finally {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\tprivate getIndexManager(): IndexManager {\n\t\tif (!this.indexManager) {\n\t\t\tconst config = this.getIndexConfig();\n\t\t\tthis.indexManager = new IndexManager(config);\n\t\t\tthis.indexManager.open();\n\t\t}\n\t\treturn this.indexManager;\n\t}\n\n\tprivate getIndexConfig(): IndexConfig {\n\t\tconst visibleDirsFn = this.options.visibleDirs;\n\t\treturn {\n\t\t\tprojectRoot: this.projectRoot,\n\t\t\tindexDir: this.options.indexDir ?? path.join(this.projectRoot, \".search-index\"),\n\t\t\tglobalMemoryDir: this.options.globalMemoryDir,\n\t\t\tvisibleDirs: visibleDirsFn ? visibleDirsFn(this.projectRoot) : undefined,\n\t\t\tmodelName: DEFAULT_MODEL_NAME,\n\t\t};\n\t}\n\n\tprivate getOrCreateEmbedder(): Promise<Embedder> {\n\t\tif (!this.embedderPromise) {\n\t\t\tthis.embedderPromise = (async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst config = this.getIndexConfig();\n\t\t\t\t\tconst embedder = new Embedder({\n\t\t\t\t\t\tmodelCacheDir:\n\t\t\t\t\t\t\tthis.options.modelCacheDir ?? path.join(homedir(), \".cache\", \"semantic-search\", \"models\"),\n\t\t\t\t\t\tmodelName: config.modelName,\n\t\t\t\t\t});\n\t\t\t\t\tawait embedder.initialize();\n\t\t\t\t\treturn embedder;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.embedderPromise = null;\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t})();\n\t\t}\n\t\treturn this.embedderPromise;\n\t}\n\n\tprivate async computeVectorScores(\n\t\tdb: SearchDatabase,\n\t\tquery: string,\n\t\tlimit: number,\n\t\t_onProgress?: IndexProgressCallback,\n\t): Promise<Map<number, number>> {\n\t\tconst config = this.getIndexConfig();\n\t\tconst embedder = await this.getOrCreateEmbedder();\n\n\t\t// Embed the query\n\t\tconst queryVector = await embedder.embedQuery(query);\n\n\t\t// Get all stored embeddings\n\t\tconst storedVectors = db.getAllEmbeddings(config.modelName);\n\n\t\tif (storedVectors.size === 0) {\n\t\t\treturn new Map();\n\t\t}\n\n\t\tconst topK = topKSimilar(queryVector, storedVectors, limit);\n\n\t\t// Convert to Map, clamping negative similarities to 0\n\t\tconst scores = new Map<number, number>();\n\t\tfor (const { id, score } of topK) {\n\t\t\tscores.set(id, Math.max(0, score));\n\t\t}\n\t\treturn scores;\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Collect all unique chunk IDs that appear in any metric's results. */\nfunction collectCandidateIds(...scoreMaps: Map<number, number>[]): Set<number> {\n\tconst ids = new Set<number>();\n\tfor (const map of scoreMaps) {\n\t\tfor (const id of map.keys()) {\n\t\t\tids.add(id);\n\t\t}\n\t}\n\treturn ids;\n}\n\n/** Aggregate chunk-level scores to file-level scores (max per file). */\nfunction aggregateFileScores(chunks: StoredChunk[], ...scoreMaps: Map<number, number>[]): Map<number, number> {\n\tconst fileScores = new Map<number, number>();\n\n\tfor (const chunk of chunks) {\n\t\tlet maxScore = 0;\n\t\tfor (const map of scoreMaps) {\n\t\t\tconst s = map.get(chunk.id);\n\t\t\tif (s !== undefined && s > maxScore) maxScore = s;\n\t\t}\n\t\tif (maxScore > 0) {\n\t\t\tconst existing = fileScores.get(chunk.fileId);\n\t\t\tif (existing === undefined || maxScore > existing) {\n\t\t\t\tfileScores.set(chunk.fileId, maxScore);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fileScores;\n}\n\n/**\n * Compute a dynamic threshold for import graph seeds.\n * Uses the median score — only the top half of files are strong enough seeds.\n * Falls back to 0.1 minimum to avoid accepting near-zero scores.\n */\nfunction computeSeedThreshold(fileScores: Map<number, number>): number {\n\tif (fileScores.size === 0) return 0;\n\tconst sorted = [...fileScores.values()].sort((a, b) => b - a);\n\tconst median = sorted[Math.floor(sorted.length / 2)];\n\treturn Math.max(median, 0.1);\n}\n\n/** Build a map of fileId → chunk IDs for that file. */\nfunction buildFileChunkMap(chunks: StoredChunk[]): Map<number, number[]> {\n\tconst map = new Map<number, number[]>();\n\tfor (const chunk of chunks) {\n\t\tconst existing = map.get(chunk.fileId);\n\t\tif (existing) existing.push(chunk.id);\n\t\telse map.set(chunk.fileId, [chunk.id]);\n\t}\n\treturn map;\n}\n\n/** Common English stopwords to exclude from FTS queries. */\nconst STOPWORDS = new Set([\n\t\"a\",\n\t\"an\",\n\t\"and\",\n\t\"are\",\n\t\"as\",\n\t\"at\",\n\t\"be\",\n\t\"but\",\n\t\"by\",\n\t\"for\",\n\t\"from\",\n\t\"had\",\n\t\"has\",\n\t\"have\",\n\t\"he\",\n\t\"her\",\n\t\"his\",\n\t\"how\",\n\t\"i\",\n\t\"if\",\n\t\"in\",\n\t\"into\",\n\t\"is\",\n\t\"it\",\n\t\"its\",\n\t\"me\",\n\t\"my\",\n\t\"no\",\n\t\"not\",\n\t\"of\",\n\t\"on\",\n\t\"or\",\n\t\"our\",\n\t\"she\",\n\t\"so\",\n\t\"than\",\n\t\"that\",\n\t\"the\",\n\t\"their\",\n\t\"them\",\n\t\"then\",\n\t\"there\",\n\t\"these\",\n\t\"they\",\n\t\"this\",\n\t\"to\",\n\t\"up\",\n\t\"us\",\n\t\"was\",\n\t\"we\",\n\t\"what\",\n\t\"when\",\n\t\"where\",\n\t\"which\",\n\t\"who\",\n\t\"will\",\n\t\"with\",\n\t\"would\",\n\t\"you\",\n\t\"your\",\n]);\n\n/**\n * Sanitize a query string for FTS5 MATCH syntax.\n * FTS5 chokes on certain characters — strip operators and wrap terms.\n *\n * Removes stopwords and uses OR between terms so multi-word queries return\n * partial matches (FTS5's default implicit AND is too restrictive).\n */\nfunction sanitizeFtsQuery(query: string): string {\n\t// Remove FTS5 operators and special chars\n\tconst cleaned = query\n\t\t.replace(/[*\"():^{}[\\]~!@#$%&=+|<>]/g, \" \")\n\t\t.replace(/\\bAND\\b|\\bOR\\b|\\bNOT\\b|\\bNEAR\\b/gi, \" \")\n\t\t.trim();\n\n\t// Split into tokens, strip leading hyphens (FTS5 NOT operator), remove stopwords, join with OR\n\tconst tokens = cleaned\n\t\t.split(/\\s+/)\n\t\t.map((t) => t.replace(/^-+/, \"\"))\n\t\t.filter((t) => t.length > 0 && !STOPWORDS.has(t.toLowerCase()));\n\tif (tokens.length === 0) return '\"\"';\n\treturn tokens.join(\" OR \");\n}\n"]}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AACrD,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAChC,MAAM,sBAAsB,GAAG,IAAI,CAAC;AA8BpC,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,MAAM,OAAO,YAAY;IAOxB,YAAY,WAAmB,EAAE,OAA6B;QAJtD,iBAAY,GAAwB,IAAI,CAAC;QACzC,oBAAe,GAA6B,IAAI,CAAC;QACjD,gBAAW,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;QAGtD,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,oEAAoE;IACpE,MAAM,CAAC,WAAW;QACjB,OAAO,YAAY,CAAC,WAAW,EAAE,CAAC;IACnC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,OAAuB;QAClD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC9B,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,oBAAoB,CAAC;YACrD,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,CAAC;YAEvC,uCAAuC;YACvC,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC;YAEhC,yEAAyE;YACzE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAClD,YAAY,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;YAEnC,MAAM,YAAY,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YAC1C,MAAM,YAAY,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAEhD,gDAAgD;YAChD,IAAI,SAAS,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC;YAClC,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;gBAClC,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;YACpE,CAAC;YAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,OAAO,EAAE,CAAC;YACX,CAAC;YAED,gDAAgD;YAChD,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YAEvC,wBAAwB;YACxB,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,iBAAiB;YACjB,MAAM,UAAU,GAAG,iBAAiB,CAAC,EAAE,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,sBAAsB,CAAC,CAAC;YAC1F,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,uCAAuC;YACvC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,UAAU,CAAC,CAAC;YACnG,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,gBAAgB;YAChB,MAAM,UAAU,GAAG,sBAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAC5D,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,kBAAkB;YAClB,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,YAAY,GAAG,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YAC9D,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,0EAA0E;YAC1E,4EAA4E;YAC5E,wEAAwE;YACxE,0BAA0B;YAC1B,MAAM,cAAc,GAAG,mBAAmB,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;YAChF,MAAM,aAAa,GAAG,oBAAoB,CAAC,cAAc,CAAC,CAAC;YAC3D,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;YAChD,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,cAAc,EAAE,CAAC;gBAC9C,IAAI,KAAK,IAAI,aAAa;oBAAE,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACtD,MAAM,YAAY,GAAG,wBAAwB,CAAC,EAAE,EAAE,aAAa,EAAE,gBAAgB,CAAC,CAAC;YACnF,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,iBAAiB;YACjB,MAAM,aAAa,GAAG,MAAM,uBAAuB,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YACjF,UAAU,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhC,8CAA8C;YAC9C,MAAM,YAAY,GAAG,mBAAmB,CACvC,UAAU,EACV,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,CACb,CAAC;YACF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAwB,CAAC;YAEnD,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;gBAC/B,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE;oBAClB,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;oBAC7B,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;oBACjC,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;oBAClC,WAAW,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;oBACtC,WAAW,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;oBACtC,UAAU,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;iBACtC,CAAC,CAAC;YACJ,CAAC;YAED,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC3B,OAAO,EAAE,CAAC;YACX,CAAC;YAED,YAAY;YACZ,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YAE/C,mBAAmB;YACnB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;YAChD,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;gBAC/B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC/B,CAAC;YAED,MAAM,OAAO,GAAmB,EAAE,CAAC;YACnC,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;gBAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;gBACzC,IAAI,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,IAAI,CAAC;wBACZ,KAAK;wBACL,MAAM,EAAE,SAAS,CAAC,MAAM;wBACxB,IAAI,EAAE,SAAS,CAAC,IAAI;qBACpB,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;YAED,OAAO,OAAO,CAAC;QAChB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,wDAAwD;IACxD,QAAQ;QACP,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC9B,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YACvD,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,UAAU,CAAC,MAAM,CAAC,CAAC;YACpB,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,yBAAyB;IACzB,KAAK,CAAC,KAAK;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC9B,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,qCAAqC;YACrC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACJ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC;oBAC5C,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACpB,CAAC;wBAAS,CAAC;oBACV,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;gBAC7B,CAAC;YACF,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,UAAU;IACV,2EAA2E;IAEnE,OAAO,CAAI,IAAsB;QACxC,IAAI,OAAoB,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE;YACpC,OAAO,GAAG,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAExB,OAAO,CAAC,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC;gBACJ,MAAM,OAAO,CAAC;gBACd,OAAO,MAAM,IAAI,EAAE,CAAC;YACrB,CAAC;oBAAS,CAAC;gBACV,OAAO,EAAE,CAAC;YACX,CAAC;QACF,CAAC,CAAC,EAAE,CAAC;IACN,CAAC;IAEO,eAAe;QACtB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;YACrC,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC;IAC1B,CAAC;IAEO,cAAc;QACrB,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QAC/C,OAAO;YACN,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC;YAC/E,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe;YAC7C,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS;YACxE,SAAS,EAAE,kBAAkB;SAC7B,CAAC;IACH,CAAC;IAEO,mBAAmB;QAC1B,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3B,IAAI,CAAC,eAAe,GAAG,CAAC,KAAK,IAAI,EAAE;gBAClC,IAAI,CAAC;oBACJ,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;oBACrC,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC;wBAC7B,aAAa,EACZ,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,CAAC;wBAC1F,SAAS,EAAE,MAAM,CAAC,SAAS;qBAC3B,CAAC,CAAC;oBACH,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;oBAC5B,OAAO,QAAQ,CAAC;gBACjB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;oBAC5B,MAAM,GAAG,CAAC;gBACX,CAAC;YACF,CAAC,CAAC,EAAE,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC,eAAe,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAChC,EAAkB,EAClB,KAAa,EACb,KAAa,EACb,WAAmC;QAEnC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAElD,kBAAkB;QAClB,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAErD,4BAA4B;QAC5B,MAAM,aAAa,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAE5D,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,GAAG,EAAE,CAAC;QAClB,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;QAE5D,sDAAsD;QACtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;YAClC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;CACD;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,wEAAwE;AACxE,SAAS,mBAAmB,CAAC,GAAG,SAAgC;IAC/D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC7B,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7B,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACb,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,wEAAwE;AACxE,SAAS,mBAAmB,CAAC,MAAqB,EAAE,GAAG,SAAgC;IACtF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE7C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,GAAG,QAAQ;gBAAE,QAAQ,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;gBACnD,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YACxC,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,UAAU,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,UAA+B;IAC5D,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,uDAAuD;AACvD,SAAS,iBAAiB,CAAC,MAAqB;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;;YACjC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,4DAA4D;AAC5D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACzB,GAAG;IACH,IAAI;IACJ,KAAK;IACL,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,IAAI;IACJ,KAAK;IACL,KAAK;IACL,KAAK;IACL,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK;IACL,IAAI;IACJ,MAAM;IACN,MAAM;IACN,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,MAAM;IACN,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,MAAM;CACN,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,KAAa;IACtC,0CAA0C;IAC1C,MAAM,OAAO,GAAG,KAAK;SACnB,OAAO,CAAC,4BAA4B,EAAE,GAAG,CAAC;SAC1C,OAAO,CAAC,mCAAmC,EAAE,GAAG,CAAC;SACjD,IAAI,EAAE,CAAC;IAET,+FAA+F;IAC/F,MAAM,MAAM,GAAG,OAAO;SACpB,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * Main search API.\n *\n * Orchestrates: check/build index → compute all 6 metrics → classify query\n * → duplicate columns → POEM rank → assemble results.\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport type { SearchDatabase } from \"./db.js\";\nimport { Embedder } from \"./embedder.js\";\nimport { IndexManager } from \"./index-manager.js\";\nimport { computeBm25Scores } from \"./metrics/bm25.js\";\nimport { computeGitRecencyScores } from \"./metrics/git-recency.js\";\nimport { computeImportGraphScores } from \"./metrics/import-graph.js\";\nimport { computePathMatchScores } from \"./metrics/path-match.js\";\nimport { computeSymbolMatchScores } from \"./metrics/symbol-match.js\";\nimport { poemRank } from \"./poem.js\";\nimport { classifyQuery } from \"./query-classifier.js\";\nimport type { IndexConfig, IndexProgressCallback, MetricScores, SearchResult, StoredChunk } from \"./types.js\";\nimport { topKSimilar } from \"./vector-store.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MODEL_NAME = \"Xenova/all-MiniLM-L6-v2\";\nconst DEFAULT_RESULT_LIMIT = 20;\nconst METRIC_CANDIDATE_LIMIT = 1000;\n\n// ============================================================================\n// Search Options\n// ============================================================================\n\nexport interface SearchOptions {\n\t/** Maximum number of results to return. Default: 20. */\n\tlimit?: number;\n\t/** Restrict search to files under this path (relative to project root). */\n\tpathFilter?: string;\n\t/** Progress callback for indexing operations. */\n\tonProgress?: IndexProgressCallback;\n}\n\n// ============================================================================\n// Search Engine Options\n// ============================================================================\n\nexport interface SearchEngineOptions {\n\t/** Absolute path to the index database directory. Default: `<projectRoot>/.search-index` */\n\tindexDir?: string;\n\t/** Absolute path to a global memory directory to include in the index. */\n\tglobalMemoryDir?: string;\n\t/** Absolute path to the model cache directory. Default: `~/.cache/semantic-search/models` */\n\tmodelCacheDir?: string;\n\t/** Provider of additional directories to include in scans (bypasses gitignore). */\n\tvisibleDirs?: (projectRoot: string) => string[];\n}\n\n// ============================================================================\n// Search Engine\n// ============================================================================\n\nexport class SearchEngine {\n\tprivate readonly projectRoot: string;\n\tprivate readonly options: SearchEngineOptions;\n\tprivate indexManager: IndexManager | null = null;\n\tprivate embedderPromise: Promise<Embedder> | null = null;\n\tprivate searchQueue: Promise<void> = Promise.resolve();\n\n\tconstructor(projectRoot: string, options?: SearchEngineOptions) {\n\t\tthis.projectRoot = projectRoot;\n\t\tthis.options = options ?? {};\n\t}\n\n\t/** Check if semantic search is available (requires node:sqlite). */\n\tstatic isAvailable(): boolean {\n\t\treturn IndexManager.isAvailable();\n\t}\n\n\t/**\n\t * Search the codebase with a natural language or identifier query.\n\t *\n\t * On first call, builds the index (scans, chunks, embeds). Subsequent calls\n\t * incrementally update changed files before searching.\n\t */\n\tasync search(query: string, options?: SearchOptions): Promise<SearchResult[]> {\n\t\treturn this.enqueue(async () => {\n\t\t\tconst limit = options?.limit ?? DEFAULT_RESULT_LIMIT;\n\t\t\tconst onProgress = options?.onProgress;\n\n\t\t\t// Ensure index is built and up to date\n\t\t\tconst indexManager = this.getIndexManager();\n\t\t\tconst db = indexManager.getDb();\n\n\t\t\t// Share our embedder with IndexManager so it doesn't create a second one\n\t\t\tconst embedder = await this.getOrCreateEmbedder();\n\t\t\tindexManager.setEmbedder(embedder);\n\n\t\t\tawait indexManager.buildIndex(onProgress);\n\t\t\tawait indexManager.ensureEmbeddings(onProgress);\n\n\t\t\t// Get all chunks (potentially filtered by path)\n\t\t\tlet allChunks = db.getAllChunks();\n\t\t\tif (options?.pathFilter) {\n\t\t\t\tconst filter = options.pathFilter;\n\t\t\t\tallChunks = allChunks.filter((c) => c.filePath.startsWith(filter));\n\t\t\t}\n\n\t\t\tif (allChunks.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\t// Classify query type for POEM column weighting\n\t\t\tconst queryType = classifyQuery(query);\n\n\t\t\t// Compute all 6 metrics\n\t\t\tonProgress?.(\"searching\", 0, 6);\n\n\t\t\t// 1. BM25 (FTS5)\n\t\t\tconst bm25Scores = computeBm25Scores(db, sanitizeFtsQuery(query), METRIC_CANDIDATE_LIMIT);\n\t\t\tonProgress?.(\"searching\", 1, 6);\n\n\t\t\t// 2. Cosine similarity (vector search)\n\t\t\tconst cosineScores = await this.computeVectorScores(db, query, METRIC_CANDIDATE_LIMIT, onProgress);\n\t\t\tonProgress?.(\"searching\", 2, 6);\n\n\t\t\t// 3. Path match\n\t\t\tconst pathScores = computePathMatchScores(query, allChunks);\n\t\t\tonProgress?.(\"searching\", 3, 6);\n\n\t\t\t// 4. Symbol match\n\t\t\tconst symbols = db.getAllSymbols();\n\t\t\tconst symbolScores = computeSymbolMatchScores(query, symbols);\n\t\t\tonProgress?.(\"searching\", 4, 6);\n\n\t\t\t// 5. Import graph (use BM25 + cosine as seed scores, aggregated per file)\n\t\t\t// Only use files with strong scores as seeds — low-scoring files (e.g. from\n\t\t\t// common OR terms matching everywhere) pollute the seed set and prevent\n\t\t\t// meaningful propagation.\n\t\t\tconst fileSeedScores = aggregateFileScores(allChunks, bm25Scores, cosineScores);\n\t\t\tconst seedThreshold = computeSeedThreshold(fileSeedScores);\n\t\t\tconst filteredSeeds = new Map<number, number>();\n\t\t\tfor (const [fileId, score] of fileSeedScores) {\n\t\t\t\tif (score >= seedThreshold) filteredSeeds.set(fileId, score);\n\t\t\t}\n\t\t\tconst fileIdToChunkIds = buildFileChunkMap(allChunks);\n\t\t\tconst importScores = computeImportGraphScores(db, filteredSeeds, fileIdToChunkIds);\n\t\t\tonProgress?.(\"searching\", 5, 6);\n\n\t\t\t// 6. Git recency\n\t\t\tconst recencyScores = await computeGitRecencyScores(this.projectRoot, allChunks);\n\t\t\tonProgress?.(\"searching\", 6, 6);\n\n\t\t\t// Build MetricScores for each candidate chunk\n\t\t\tconst candidateIds = collectCandidateIds(\n\t\t\t\tbm25Scores,\n\t\t\t\tcosineScores,\n\t\t\t\tpathScores,\n\t\t\t\tsymbolScores,\n\t\t\t\timportScores,\n\t\t\t\trecencyScores,\n\t\t\t);\n\t\t\tconst candidates = new Map<number, MetricScores>();\n\n\t\t\tfor (const id of candidateIds) {\n\t\t\t\tcandidates.set(id, {\n\t\t\t\t\tbm25: bm25Scores.get(id) ?? 0,\n\t\t\t\t\tcosine: cosineScores.get(id) ?? 0,\n\t\t\t\t\tpathMatch: pathScores.get(id) ?? 0,\n\t\t\t\t\tsymbolMatch: symbolScores.get(id) ?? 0,\n\t\t\t\t\timportGraph: importScores.get(id) ?? 0,\n\t\t\t\t\tgitRecency: recencyScores.get(id) ?? 0,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (candidates.size === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\t// POEM rank\n\t\t\tconst ranked = poemRank(candidates, queryType);\n\n\t\t\t// Assemble results\n\t\t\tconst chunkMap = new Map<number, StoredChunk>();\n\t\t\tfor (const chunk of allChunks) {\n\t\t\t\tchunkMap.set(chunk.id, chunk);\n\t\t\t}\n\n\t\t\tconst results: SearchResult[] = [];\n\t\t\tfor (const candidate of ranked.slice(0, limit)) {\n\t\t\t\tconst chunk = chunkMap.get(candidate.id);\n\t\t\t\tif (chunk) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tchunk,\n\t\t\t\t\t\tscores: candidate.scores,\n\t\t\t\t\t\trank: candidate.rank,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn results;\n\t\t});\n\t}\n\n\t/** Get index stats without opening a new connection. */\n\tgetStats(): { files: number; chunks: number } | null {\n\t\tif (!this.indexManager) return null;\n\t\treturn this.indexManager.getStats();\n\t}\n\n\t/**\n\t * Reset the search index — delete the DB and close the IndexManager.\n\t *\n\t * Preserves the embedder (expensive ONNX model, unrelated to index state).\n\t * The next `search()` call will lazily re-create the IndexManager and build\n\t * a fresh index from scratch.\n\t */\n\tasync resetIndex(): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.indexManager?.close();\n\t\t\tthis.indexManager = null;\n\t\t\tconst config = this.getIndexConfig();\n\t\t\tconst dbPath = path.join(config.indexDir, \"search.db\");\n\t\t\tif (existsSync(dbPath)) {\n\t\t\t\tunlinkSync(dbPath);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Dispose resources. */\n\tasync close(): Promise<void> {\n\t\treturn this.enqueue(async () => {\n\t\t\tthis.indexManager?.close();\n\t\t\tthis.indexManager = null;\n\t\t\t// Dispose embedder if it was created\n\t\t\tif (this.embedderPromise) {\n\t\t\t\ttry {\n\t\t\t\t\tconst embedder = await this.embedderPromise;\n\t\t\t\t\tembedder.dispose();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.embedderPromise = null;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t// ========================================================================\n\t// Private\n\t// ========================================================================\n\n\tprivate enqueue<T>(work: () => Promise<T>): Promise<T> {\n\t\tlet resolve!: () => void;\n\t\tconst gate = new Promise<void>((r) => {\n\t\t\tresolve = r;\n\t\t});\n\t\tconst waitFor = this.searchQueue;\n\t\tthis.searchQueue = gate;\n\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\tawait waitFor;\n\t\t\t\treturn await work();\n\t\t\t} finally {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\tprivate getIndexManager(): IndexManager {\n\t\tif (!this.indexManager) {\n\t\t\tconst config = this.getIndexConfig();\n\t\t\tthis.indexManager = new IndexManager(config);\n\t\t\tthis.indexManager.open();\n\t\t}\n\t\treturn this.indexManager;\n\t}\n\n\tprivate getIndexConfig(): IndexConfig {\n\t\tconst visibleDirsFn = this.options.visibleDirs;\n\t\treturn {\n\t\t\tprojectRoot: this.projectRoot,\n\t\t\tindexDir: this.options.indexDir ?? path.join(this.projectRoot, \".search-index\"),\n\t\t\tglobalMemoryDir: this.options.globalMemoryDir,\n\t\t\tvisibleDirs: visibleDirsFn ? visibleDirsFn(this.projectRoot) : undefined,\n\t\t\tmodelName: DEFAULT_MODEL_NAME,\n\t\t};\n\t}\n\n\tprivate getOrCreateEmbedder(): Promise<Embedder> {\n\t\tif (!this.embedderPromise) {\n\t\t\tthis.embedderPromise = (async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst config = this.getIndexConfig();\n\t\t\t\t\tconst embedder = new Embedder({\n\t\t\t\t\t\tmodelCacheDir:\n\t\t\t\t\t\t\tthis.options.modelCacheDir ?? path.join(homedir(), \".cache\", \"semantic-search\", \"models\"),\n\t\t\t\t\t\tmodelName: config.modelName,\n\t\t\t\t\t});\n\t\t\t\t\tawait embedder.initialize();\n\t\t\t\t\treturn embedder;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthis.embedderPromise = null;\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t})();\n\t\t}\n\t\treturn this.embedderPromise;\n\t}\n\n\tprivate async computeVectorScores(\n\t\tdb: SearchDatabase,\n\t\tquery: string,\n\t\tlimit: number,\n\t\t_onProgress?: IndexProgressCallback,\n\t): Promise<Map<number, number>> {\n\t\tconst config = this.getIndexConfig();\n\t\tconst embedder = await this.getOrCreateEmbedder();\n\n\t\t// Embed the query\n\t\tconst queryVector = await embedder.embedQuery(query);\n\n\t\t// Get all stored embeddings\n\t\tconst storedVectors = db.getAllEmbeddings(config.modelName);\n\n\t\tif (storedVectors.size === 0) {\n\t\t\treturn new Map();\n\t\t}\n\n\t\tconst topK = topKSimilar(queryVector, storedVectors, limit);\n\n\t\t// Convert to Map, clamping negative similarities to 0\n\t\tconst scores = new Map<number, number>();\n\t\tfor (const { id, score } of topK) {\n\t\t\tscores.set(id, Math.max(0, score));\n\t\t}\n\t\treturn scores;\n\t}\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** Collect all unique chunk IDs that appear in any metric's results. */\nfunction collectCandidateIds(...scoreMaps: Map<number, number>[]): Set<number> {\n\tconst ids = new Set<number>();\n\tfor (const map of scoreMaps) {\n\t\tfor (const id of map.keys()) {\n\t\t\tids.add(id);\n\t\t}\n\t}\n\treturn ids;\n}\n\n/** Aggregate chunk-level scores to file-level scores (max per file). */\nfunction aggregateFileScores(chunks: StoredChunk[], ...scoreMaps: Map<number, number>[]): Map<number, number> {\n\tconst fileScores = new Map<number, number>();\n\n\tfor (const chunk of chunks) {\n\t\tlet maxScore = 0;\n\t\tfor (const map of scoreMaps) {\n\t\t\tconst s = map.get(chunk.id);\n\t\t\tif (s !== undefined && s > maxScore) maxScore = s;\n\t\t}\n\t\tif (maxScore > 0) {\n\t\t\tconst existing = fileScores.get(chunk.fileId);\n\t\t\tif (existing === undefined || maxScore > existing) {\n\t\t\t\tfileScores.set(chunk.fileId, maxScore);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn fileScores;\n}\n\n/**\n * Compute a dynamic threshold for import graph seeds.\n * Uses the median score — only the top half of files are strong enough seeds.\n * Falls back to 0.1 minimum to avoid accepting near-zero scores.\n */\nfunction computeSeedThreshold(fileScores: Map<number, number>): number {\n\tif (fileScores.size === 0) return 0;\n\tconst sorted = [...fileScores.values()].sort((a, b) => b - a);\n\tconst median = sorted[Math.floor(sorted.length / 2)];\n\treturn Math.max(median, 0.1);\n}\n\n/** Build a map of fileId → chunk IDs for that file. */\nfunction buildFileChunkMap(chunks: StoredChunk[]): Map<number, number[]> {\n\tconst map = new Map<number, number[]>();\n\tfor (const chunk of chunks) {\n\t\tconst existing = map.get(chunk.fileId);\n\t\tif (existing) existing.push(chunk.id);\n\t\telse map.set(chunk.fileId, [chunk.id]);\n\t}\n\treturn map;\n}\n\n/** Common English stopwords to exclude from FTS queries. */\nconst STOPWORDS = new Set([\n\t\"a\",\n\t\"an\",\n\t\"and\",\n\t\"are\",\n\t\"as\",\n\t\"at\",\n\t\"be\",\n\t\"but\",\n\t\"by\",\n\t\"for\",\n\t\"from\",\n\t\"had\",\n\t\"has\",\n\t\"have\",\n\t\"he\",\n\t\"her\",\n\t\"his\",\n\t\"how\",\n\t\"i\",\n\t\"if\",\n\t\"in\",\n\t\"into\",\n\t\"is\",\n\t\"it\",\n\t\"its\",\n\t\"me\",\n\t\"my\",\n\t\"no\",\n\t\"not\",\n\t\"of\",\n\t\"on\",\n\t\"or\",\n\t\"our\",\n\t\"she\",\n\t\"so\",\n\t\"than\",\n\t\"that\",\n\t\"the\",\n\t\"their\",\n\t\"them\",\n\t\"then\",\n\t\"there\",\n\t\"these\",\n\t\"they\",\n\t\"this\",\n\t\"to\",\n\t\"up\",\n\t\"us\",\n\t\"was\",\n\t\"we\",\n\t\"what\",\n\t\"when\",\n\t\"where\",\n\t\"which\",\n\t\"who\",\n\t\"will\",\n\t\"with\",\n\t\"would\",\n\t\"you\",\n\t\"your\",\n]);\n\n/**\n * Sanitize a query string for FTS5 MATCH syntax.\n * FTS5 chokes on certain characters — strip operators and wrap terms.\n *\n * Removes stopwords and uses OR between terms so multi-word queries return\n * partial matches (FTS5's default implicit AND is too restrictive).\n */\nfunction sanitizeFtsQuery(query: string): string {\n\t// Remove FTS5 operators and special chars\n\tconst cleaned = query\n\t\t.replace(/[*\"():^{}[\\]~!@#$%&=+|<>]/g, \" \")\n\t\t.replace(/\\bAND\\b|\\bOR\\b|\\bNOT\\b|\\bNEAR\\b/gi, \" \")\n\t\t.trim();\n\n\t// Split into tokens, strip leading hyphens (FTS5 NOT operator), remove stopwords, join with OR\n\tconst tokens = cleaned\n\t\t.split(/\\s+/)\n\t\t.map((t) => t.replace(/^-+/, \"\"))\n\t\t.filter((t) => t.length > 0 && !STOPWORDS.has(t.toLowerCase()));\n\tif (tokens.length === 0) return '\"\"';\n\treturn tokens.join(\" OR \");\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"text-chunker.d.ts","sourceRoot":"","sources":["../src/text-chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmBtD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,KAAK,EAAE,CAkChG","sourcesContent":["/**\n * Text file chunker for the semantic search subsystem.\n *\n * Splits non-code files (markdown, YAML, JSON, TOML, plaintext) into\n * semantically meaningful chunks using format-specific boundary detection.\n */\n\nimport type { Chunk, TextFileType } from \"./types.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Files smaller than this are returned as a single chunk. */\nconst MIN_SPLIT_SIZE = 500;\n\n/** Maximum characters per chunk — oversized sections are split at paragraph boundaries. */\nconst MAX_CHUNK_SIZE = 8000;\n\n/** Minimum characters for a plaintext paragraph chunk (small ones get merged). */\nconst MIN_PARAGRAPH_SIZE = 200;\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Chunk a non-code text file by format-specific boundaries.\n *\n * Returns at least one chunk for any non-empty input. Empty files produce\n * a single chunk of kind 'file'.\n */\nexport function chunkTextFile(content: string, filePath: string, fileType: TextFileType): Chunk[] {\n\t// Empty or trivially small files → single chunk\n\tif (content.length < MIN_SPLIT_SIZE) {\n\t\treturn [wholeFileChunk(content, filePath, fileType)];\n\t}\n\n\tlet chunks: Chunk[];\n\tswitch (fileType) {\n\t\tcase \"markdown\":\n\t\t\tchunks = chunkMarkdown(content, filePath);\n\t\t\tbreak;\n\t\tcase \"yaml\":\n\t\t\tchunks = chunkYaml(content, filePath);\n\t\t\tbreak;\n\t\tcase \"json\":\n\t\t\tchunks = chunkJson(content, filePath);\n\t\t\tbreak;\n\t\tcase \"toml\":\n\t\t\tchunks = chunkToml(content, filePath);\n\t\t\tbreak;\n\t\tcase \"plaintext\":\n\t\t\tchunks = chunkPlaintext(content, filePath);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tchunks = [];\n\t}\n\n\t// Fallback: if format-specific parsing produced nothing, return whole file\n\tif (chunks.length === 0) {\n\t\treturn [wholeFileChunk(content, filePath, fileType)];\n\t}\n\n\t// Enforce max chunk size — split oversized chunks at paragraph boundaries\n\treturn chunks.flatMap((chunk) => enforceMaxSize(chunk));\n}\n\n// ============================================================================\n// Markdown\n// ============================================================================\n\n/** Heading regex: lines starting with 1–6 # characters followed by a space. */\nconst HEADING_RE = /^(#{1,6})\\s+(.+)$/;\n\nfunction chunkMarkdown(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Identify heading positions and levels\n\tconst headings: Array<{ line: number; level: number; text: string }> = [];\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst match = HEADING_RE.exec(lines[i]);\n\t\tif (match) {\n\t\t\theadings.push({ line: i, level: match[1].length, text: match[2].trim() });\n\t\t}\n\t}\n\n\t// No headings → treat as plaintext\n\tif (headings.length === 0) {\n\t\treturn chunkPlaintext(content, filePath);\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\t// Content before the first heading (preamble)\n\tif (headings[0].line > 0) {\n\t\tconst preambleLines = lines.slice(0, headings[0].line);\n\t\tconst preambleContent = preambleLines.join(\"\\n\");\n\t\tif (preambleContent.trim().length > 0) {\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: headings[0].line, // 1-indexed, line before the first heading\n\t\t\t\tkind: \"heading_section\",\n\t\t\t\tname: null,\n\t\t\t\tcontent: preambleContent,\n\t\t\t\tfileType: \"markdown\",\n\t\t\t});\n\t\t}\n\t}\n\n\t// Each heading owns all lines until the next heading of same or higher level\n\tfor (let i = 0; i < headings.length; i++) {\n\t\tconst start = headings[i].line;\n\t\tlet end: number;\n\n\t\t// Find the next heading at the same or higher (lower number) level\n\t\tlet nextSameOrHigher = -1;\n\t\tfor (let j = i + 1; j < headings.length; j++) {\n\t\t\tif (headings[j].level <= headings[i].level) {\n\t\t\t\tnextSameOrHigher = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (nextSameOrHigher !== -1) {\n\t\t\tend = headings[nextSameOrHigher].line - 1;\n\t\t} else {\n\t\t\tend = lines.length - 1;\n\t\t}\n\n\t\tconst sectionLines = lines.slice(start, end + 1);\n\t\tconst sectionContent = sectionLines.join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1, // 1-indexed\n\t\t\tendLine: end + 1, // 1-indexed, inclusive\n\t\t\tkind: \"heading_section\",\n\t\t\tname: headings[i].text,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"markdown\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// YAML\n// ============================================================================\n\n/**\n * Top-level YAML key: a line that starts with a non-space, non-comment\n * character and contains a colon. Excludes YAML directives (---/...).\n */\nconst YAML_TOP_KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\\s*:/;\n\nfunction chunkYaml(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Find top-level key positions\n\tconst keys: Array<{ line: number; name: string }> = [];\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\t// Skip comments, blank lines, YAML directives\n\t\tif (line.startsWith(\"#\") || line.startsWith(\"---\") || line.startsWith(\"...\") || line.trim() === \"\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst match = YAML_TOP_KEY_RE.exec(line);\n\t\tif (match) {\n\t\t\tkeys.push({ line: i, name: match[1] });\n\t\t}\n\t}\n\n\tif (keys.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\t// Preamble (comments, directives before first key) is included in the\n\t// first key's chunk via the `start = 0` logic below for `i === 0`.\n\n\tfor (let i = 0; i < keys.length; i++) {\n\t\t// Include any preceding comments/blank lines that belong to this key\n\t\t// by looking backwards from the key to find attached comments\n\t\tlet start = keys[i].line;\n\t\tif (i === 0) {\n\t\t\t// First key includes any preamble (comments, directives)\n\t\t\tstart = 0;\n\t\t} else {\n\t\t\t// Look back for comment lines directly above\n\t\t\tlet scan = keys[i].line - 1;\n\t\t\twhile (scan > keys[i - 1].line) {\n\t\t\t\tconst trimmed = lines[scan].trim();\n\t\t\t\tif (trimmed.startsWith(\"#\") || trimmed === \"\") {\n\t\t\t\t\tscan--;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart = scan + 1;\n\t\t}\n\n\t\tconst end = i < keys.length - 1 ? keys[i + 1].line - 1 : lines.length - 1;\n\n\t\t// Trim trailing blank lines to find the real end\n\t\tlet realEnd = end;\n\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") {\n\t\t\trealEnd--;\n\t\t}\n\t\t// But keep at least the key line\n\t\tif (realEnd < keys[i].line) realEnd = keys[i].line;\n\n\t\t// Include trailing blank lines within the range for line counting,\n\t\t// but use them as separators\n\t\tconst sectionLines = lines.slice(start, end + 1);\n\t\tconst sectionContent = sectionLines.join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: end + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: keys[i].name,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"yaml\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// JSON\n// ============================================================================\n\nfunction chunkJson(content: string, filePath: string): Chunk[] {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch {\n\t\t// Invalid JSON → return as whole file\n\t\treturn [];\n\t}\n\n\t// Only split top-level objects. Arrays and primitives → single chunk.\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\treturn [];\n\t}\n\n\tconst topKeys = Object.keys(parsed);\n\tif (topKeys.length === 0) {\n\t\treturn [];\n\t}\n\n\t// For JSON we can't rely on simple line scanning because values can span\n\t// multiple lines with arbitrary nesting. Instead, re-serialize each\n\t// top-level key and locate its position in the original content.\n\tconst lines = content.split(\"\\n\");\n\tconst chunks: Chunk[] = [];\n\n\t// Strategy: scan the original text to find each top-level key's line range.\n\t// A top-level key in formatted JSON appears as ` \"key\":` at indent level 1.\n\t// In minified JSON with a single line, we fall back to serialized slicing.\n\tif (lines.length === 1) {\n\t\t// Minified JSON — produce one chunk per top-level key using re-serialized content\n\t\tconst obj = parsed as Record<string, unknown>;\n\t\tfor (const key of topKeys) {\n\t\t\tconst serialized = JSON.stringify({ [key]: obj[key] }, null, 2);\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: 1,\n\t\t\t\tkind: \"top_level_key\",\n\t\t\t\tname: key,\n\t\t\t\tcontent: serialized,\n\t\t\t\tfileType: \"json\",\n\t\t\t});\n\t\t}\n\t\treturn chunks;\n\t}\n\n\t// Multi-line JSON: find each top-level key by scanning for `\"key\":` at\n\t// brace depth 1.\n\tconst keyPositions: Array<{ key: string; startLine: number }> = [];\n\tlet depth = 0;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tfor (let c = 0; c < line.length; c++) {\n\t\t\tconst ch = line[c];\n\t\t\tif (ch === '\"') {\n\t\t\t\t// Skip string content\n\t\t\t\tc++;\n\t\t\t\twhile (c < line.length && line[c] !== '\"') {\n\t\t\t\t\tif (line[c] === \"\\\\\") c++; // skip escaped char\n\t\t\t\t\tc++;\n\t\t\t\t}\n\t\t\t\t// Check if this string at depth 1 is a key (followed by :)\n\t\t\t\tif (depth === 1) {\n\t\t\t\t\t// Extract the key name\n\t\t\t\t\tconst keyMatch = /^\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"\\s*:/.exec(line);\n\t\t\t\t\tif (keyMatch) {\n\t\t\t\t\t\tconst foundKey = keyMatch[1].replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n\t\t\t\t\t\tif (topKeys.includes(foundKey) && !keyPositions.some((kp) => kp.key === foundKey)) {\n\t\t\t\t\t\t\tkeyPositions.push({ key: foundKey, startLine: i });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak; // Move to next line — we found the key on this line\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ch === \"{\" || ch === \"[\") {\n\t\t\t\tdepth++;\n\t\t\t} else if (ch === \"}\" || ch === \"]\") {\n\t\t\t\tdepth--;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build chunks from key positions\n\tfor (let i = 0; i < keyPositions.length; i++) {\n\t\tconst start = keyPositions[i].startLine;\n\t\tconst end = i < keyPositions.length - 1 ? keyPositions[i + 1].startLine - 1 : lines.length - 1;\n\n\t\t// Trim the trailing closing brace of the root object from the last chunk\n\t\tlet realEnd = end;\n\t\tif (i === keyPositions.length - 1) {\n\t\t\t// Walk back from end to skip the root closing brace and trailing whitespace\n\t\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") realEnd--;\n\t\t\tif (realEnd > start && lines[realEnd].trim() === \"}\") realEnd--;\n\t\t}\n\n\t\t// Trim trailing commas and blank lines from non-last chunks too\n\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") realEnd--;\n\n\t\tconst sectionContent = lines.slice(start, realEnd + 1).join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: realEnd + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: keyPositions[i].key,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"json\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// TOML\n// ============================================================================\n\n/** TOML section header: [section] or [[array-of-tables]]. */\nconst TOML_SECTION_RE = /^\\[{1,2}([^\\]]+)\\]{1,2}\\s*$/;\n\n/** TOML top-level key-value pair (not indented, before any section). */\nconst TOML_KV_RE = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\\s*=/;\n\nfunction chunkToml(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Phase 1: Identify all top-level boundaries (sections and top-level KV pairs before sections)\n\tconst boundaries: Array<{\n\t\tline: number;\n\t\tkind: \"section\" | \"kv\";\n\t\tname: string;\n\t}> = [];\n\n\tlet firstSectionLine = lines.length;\n\n\t// Find section headers first\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst match = TOML_SECTION_RE.exec(lines[i]);\n\t\tif (match) {\n\t\t\tif (i < firstSectionLine) firstSectionLine = i;\n\t\t\tboundaries.push({ line: i, kind: \"section\", name: match[1].trim() });\n\t\t}\n\t}\n\n\t// Find top-level key-value pairs (lines before the first section)\n\tconst kvGroups: Array<{ startLine: number; name: string }> = [];\n\tfor (let i = 0; i < firstSectionLine; i++) {\n\t\tconst line = lines[i];\n\t\tif (line.trim() === \"\" || line.trim().startsWith(\"#\")) continue;\n\t\tconst match = TOML_KV_RE.exec(line);\n\t\tif (match) {\n\t\t\tkvGroups.push({ startLine: i, name: match[1] });\n\t\t}\n\t}\n\n\t// Merge KV pairs into boundaries\n\tfor (const kv of kvGroups) {\n\t\tboundaries.push({ line: kv.startLine, kind: \"kv\", name: kv.name });\n\t}\n\n\t// Sort boundaries by line number\n\tboundaries.sort((a, b) => a.line - b.line);\n\n\tif (boundaries.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\tfor (let i = 0; i < boundaries.length; i++) {\n\t\tconst boundary = boundaries[i];\n\t\tlet start = boundary.line;\n\t\tconst end = i < boundaries.length - 1 ? boundaries[i + 1].line - 1 : lines.length - 1;\n\n\t\tif (i === 0) {\n\t\t\t// First boundary — include any leading comments/preamble\n\t\t\tstart = 0;\n\t\t} else {\n\t\t\t// Look back for comment/blank lines attached to this boundary\n\t\t\tlet scan = boundary.line - 1;\n\t\t\tconst prevEnd = boundaries[i - 1].line;\n\t\t\twhile (scan > prevEnd) {\n\t\t\t\tconst trimmed = lines[scan].trim();\n\t\t\t\tif (trimmed.startsWith(\"#\") || trimmed === \"\") {\n\t\t\t\t\tscan--;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart = scan + 1;\n\t\t}\n\n\t\tconst sectionContent = lines.slice(start, end + 1).join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: end + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: boundary.name,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"toml\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// Plaintext\n// ============================================================================\n\nfunction chunkPlaintext(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Split into paragraphs at double-newline boundaries\n\tconst paragraphs: Array<{ startLine: number; endLine: number; content: string }> = [];\n\tlet paraStart = -1;\n\tlet consecutiveBlanks = 0;\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst isBlank = lines[i].trim() === \"\";\n\n\t\tif (isBlank) {\n\t\t\tconsecutiveBlanks++;\n\t\t\tif (consecutiveBlanks >= 2 && paraStart !== -1) {\n\t\t\t\t// End current paragraph at the last non-blank line\n\t\t\t\tlet paraEnd = i - consecutiveBlanks;\n\t\t\t\tif (paraEnd < paraStart) paraEnd = paraStart;\n\t\t\t\tparagraphs.push({\n\t\t\t\t\tstartLine: paraStart,\n\t\t\t\t\tendLine: paraEnd,\n\t\t\t\t\tcontent: lines.slice(paraStart, paraEnd + 1).join(\"\\n\"),\n\t\t\t\t});\n\t\t\t\tparaStart = -1;\n\t\t\t}\n\t\t} else {\n\t\t\tif (paraStart === -1) {\n\t\t\t\tparaStart = i;\n\t\t\t}\n\t\t\tconsecutiveBlanks = 0;\n\t\t}\n\t}\n\n\t// Don't forget the last paragraph\n\tif (paraStart !== -1) {\n\t\tlet paraEnd = lines.length - 1;\n\t\twhile (paraEnd > paraStart && lines[paraEnd].trim() === \"\") paraEnd--;\n\t\tparagraphs.push({\n\t\t\tstartLine: paraStart,\n\t\t\tendLine: paraEnd,\n\t\t\tcontent: lines.slice(paraStart, paraEnd + 1).join(\"\\n\"),\n\t\t});\n\t}\n\n\tif (paragraphs.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Group small paragraphs together to meet the minimum size\n\tconst chunks: Chunk[] = [];\n\tlet groupStart = paragraphs[0].startLine;\n\tlet groupEnd = paragraphs[0].endLine;\n\tlet groupContent = paragraphs[0].content;\n\n\tfor (let i = 1; i < paragraphs.length; i++) {\n\t\tconst para = paragraphs[i];\n\n\t\tif (groupContent.length < MIN_PARAGRAPH_SIZE) {\n\t\t\t// Merge with current group\n\t\t\tgroupEnd = para.endLine;\n\t\t\tgroupContent += `\\n\\n${para.content}`;\n\t\t} else {\n\t\t\t// Emit current group, start new one\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: groupStart + 1,\n\t\t\t\tendLine: groupEnd + 1,\n\t\t\t\tkind: \"paragraph\",\n\t\t\t\tname: extractParagraphName(groupContent),\n\t\t\t\tcontent: groupContent,\n\t\t\t\tfileType: \"plaintext\",\n\t\t\t});\n\t\t\tgroupStart = para.startLine;\n\t\t\tgroupEnd = para.endLine;\n\t\t\tgroupContent = para.content;\n\t\t}\n\t}\n\n\t// Emit final group\n\t// If the final group is too small and there are existing chunks, merge with the last one\n\tif (groupContent.length < MIN_PARAGRAPH_SIZE && chunks.length > 0) {\n\t\tconst last = chunks[chunks.length - 1];\n\t\tlast.endLine = groupEnd + 1;\n\t\tlast.content += `\\n\\n${groupContent}`;\n\t} else {\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: groupStart + 1,\n\t\t\tendLine: groupEnd + 1,\n\t\t\tkind: \"paragraph\",\n\t\t\tname: extractParagraphName(groupContent),\n\t\t\tcontent: groupContent,\n\t\t\tfileType: \"plaintext\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n/** Extract a short name from the first line of a paragraph, truncated. */\nfunction extractParagraphName(content: string): string | null {\n\tconst firstLine = content.split(\"\\n\")[0].trim();\n\tif (firstLine.length === 0) return null;\n\tif (firstLine.length <= 60) return firstLine;\n\treturn `${firstLine.slice(0, 57)}...`;\n}\n\n// ============================================================================\n// Chunk Size Enforcement\n// ============================================================================\n\n/**\n * If a chunk exceeds MAX_CHUNK_SIZE, split it at paragraph boundaries\n * (double newlines). If no paragraph boundaries exist, split at line\n * boundaries near the limit.\n */\nfunction enforceMaxSize(chunk: Chunk): Chunk[] {\n\tif (chunk.content.length <= MAX_CHUNK_SIZE) {\n\t\treturn [chunk];\n\t}\n\n\tconst lines = chunk.content.split(\"\\n\");\n\tconst subChunks: Chunk[] = [];\n\tlet currentLines: string[] = [];\n\tlet currentSize = 0;\n\tlet chunkStartLine = chunk.startLine;\n\tlet partIndex = 0;\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineSize = line.length + 1; // +1 for newline\n\n\t\t// Check if adding this line would exceed the limit\n\t\tif (currentSize + lineSize > MAX_CHUNK_SIZE && currentLines.length > 0) {\n\t\t\t// Try to find a paragraph boundary (blank line) to split at\n\t\t\tlet splitAt = currentLines.length;\n\t\t\tfor (let j = currentLines.length - 1; j > 0; j--) {\n\t\t\t\tif (currentLines[j].trim() === \"\") {\n\t\t\t\t\tsplitAt = j;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Emit the sub-chunk up to the split point\n\t\t\tconst emitLines = currentLines.slice(0, splitAt);\n\t\t\tconst emitContent = emitLines.join(\"\\n\");\n\t\t\tconst emitEndLine = chunkStartLine + splitAt - 1;\n\n\t\t\tsubChunks.push({\n\t\t\t\tfilePath: chunk.filePath,\n\t\t\t\tstartLine: chunkStartLine,\n\t\t\t\tendLine: emitEndLine,\n\t\t\t\tkind: chunk.kind,\n\t\t\t\tname: partIndex === 0 ? chunk.name : chunk.name ? `${chunk.name} (cont.)` : null,\n\t\t\t\tcontent: emitContent,\n\t\t\t\tfileType: chunk.fileType,\n\t\t\t});\n\t\t\tpartIndex++;\n\n\t\t\t// Keep remaining lines from the split\n\t\t\tconst remaining = currentLines.slice(splitAt);\n\t\t\tcurrentLines = [...remaining, line];\n\t\t\tchunkStartLine = emitEndLine + 1;\n\t\t\tcurrentSize = currentLines.join(\"\\n\").length;\n\t\t} else {\n\t\t\tcurrentLines.push(line);\n\t\t\tcurrentSize += lineSize;\n\t\t}\n\t}\n\n\t// Emit remaining lines\n\tif (currentLines.length > 0) {\n\t\tconst emitContent = currentLines.join(\"\\n\");\n\t\tsubChunks.push({\n\t\t\tfilePath: chunk.filePath,\n\t\t\tstartLine: chunkStartLine,\n\t\t\tendLine: chunk.endLine,\n\t\t\tkind: chunk.kind,\n\t\t\tname: partIndex === 0 ? chunk.name : chunk.name ? `${chunk.name} (cont.)` : null,\n\t\t\tcontent: emitContent,\n\t\t\tfileType: chunk.fileType,\n\t\t});\n\t}\n\n\treturn subChunks.length > 0 ? subChunks : [chunk];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction wholeFileChunk(content: string, filePath: string, fileType: TextFileType): Chunk {\n\tconst lineCount = content.split(\"\\n\").length;\n\treturn {\n\t\tfilePath,\n\t\tstartLine: 1,\n\t\tendLine: lineCount,\n\t\tkind: \"file\",\n\t\tname: null,\n\t\tcontent,\n\t\tfileType,\n\t};\n}\n"]}
1
+ {"version":3,"file":"text-chunker.d.ts","sourceRoot":"","sources":["../src/text-chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmBtD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,KAAK,EAAE,CAkChG"}
@@ -1 +1 @@
1
- {"version":3,"file":"text-chunker.js","sourceRoot":"","sources":["../src/text-chunker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,8DAA8D;AAC9D,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,6FAA2F;AAC3F,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,kFAAkF;AAClF,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,+EAA+E;AAC/E,aAAa;AACb,+EAA+E;AAE/E;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,QAAgB,EAAE,QAAsB,EAAW;IACjG,kDAAgD;IAChD,IAAI,OAAO,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QACrC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,UAAU;YACd,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC1C,MAAM;QACP,KAAK,MAAM;YACV,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACtC,MAAM;QACP,KAAK,MAAM;YACV,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACtC,MAAM;QACP,KAAK,MAAM;YACV,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACtC,MAAM;QACP,KAAK,WAAW;YACf,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC3C,MAAM;QACP;YACC,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;IAED,2EAA2E;IAC3E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,4EAA0E;IAC1E,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,CACxD;AAED,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E,iFAA+E;AAC/E,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAEvC,SAAS,aAAa,CAAC,OAAe,EAAE,QAAgB,EAAW;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,wCAAwC;IACxC,MAAM,QAAQ,GAAyD,EAAE,CAAC;IAC1E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,IAAI,KAAK,EAAE,CAAC;YACX,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3E,CAAC;IACF,CAAC;IAED,qCAAmC;IACnC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,8CAA8C;IAC9C,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,eAAe,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC;gBACX,QAAQ;gBACR,SAAS,EAAE,CAAC;gBACZ,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,2CAA2C;gBACtE,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,eAAe;gBACxB,QAAQ,EAAE,UAAU;aACpB,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,6EAA6E;IAC7E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/B,IAAI,GAAW,CAAC;QAEhB,mEAAmE;QACnE,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;gBAC5C,gBAAgB,GAAG,CAAC,CAAC;gBACrB,MAAM;YACP,CAAC;QACF,CAAC;QAED,IAAI,gBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC;YAC7B,GAAG,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACP,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QACxB,CAAC;QAED,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE/C,MAAM,CAAC,IAAI,CAAC;YACX,QAAQ;YACR,SAAS,EAAE,KAAK,GAAG,CAAC,EAAE,YAAY;YAClC,OAAO,EAAE,GAAG,GAAG,CAAC,EAAE,uBAAuB;YACzC,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;YACtB,OAAO,EAAE,cAAc;YACvB,QAAQ,EAAE,UAAU;SACpB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAED,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,eAAe,GAAG,iCAAiC,CAAC;AAE1D,SAAS,SAAS,CAAC,OAAe,EAAE,QAAgB,EAAW;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,+BAA+B;IAC/B,MAAM,IAAI,GAA0C,EAAE,CAAC;IACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,8CAA8C;QAC9C,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACpG,SAAS;QACV,CAAC;QACD,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,sEAAsE;IACtE,mEAAmE;IAEnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,qEAAqE;QACrE,8DAA8D;QAC9D,IAAI,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACb,yDAAyD;YACzD,KAAK,GAAG,CAAC,CAAC;QACX,CAAC;aAAM,CAAC;YACP,6CAA6C;YAC7C,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;YAC5B,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;oBAC/C,IAAI,EAAE,CAAC;gBACR,CAAC;qBAAM,CAAC;oBACP,MAAM;gBACP,CAAC;YACF,CAAC;YACD,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAE1E,iDAAiD;QACjD,IAAI,OAAO,GAAG,GAAG,CAAC;QAClB,OAAO,OAAO,GAAG,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxD,OAAO,EAAE,CAAC;QACX,CAAC;QACD,iCAAiC;QACjC,IAAI,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;YAAE,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAEnD,mEAAmE;QACnE,6BAA6B;QAC7B,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE/C,MAAM,CAAC,IAAI,CAAC;YACX,QAAQ;YACR,SAAS,EAAE,KAAK,GAAG,CAAC;YACpB,OAAO,EAAE,GAAG,GAAG,CAAC;YAChB,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;YAClB,OAAO,EAAE,cAAc;YACvB,QAAQ,EAAE,MAAM;SAChB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAED,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E,SAAS,SAAS,CAAC,OAAe,EAAE,QAAgB,EAAW;IAC9D,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACR,wCAAsC;QACtC,OAAO,EAAE,CAAC;IACX,CAAC;IAED,wEAAsE;IACtE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACX,CAAC;IAED,yEAAyE;IACzE,oEAAoE;IACpE,iEAAiE;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,oFAAkF;QAClF,MAAM,GAAG,GAAG,MAAiC,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAChE,MAAM,CAAC,IAAI,CAAC;gBACX,QAAQ;gBACR,SAAS,EAAE,CAAC;gBACZ,OAAO,EAAE,CAAC;gBACV,IAAI,EAAE,eAAe;gBACrB,IAAI,EAAE,GAAG;gBACT,OAAO,EAAE,UAAU;gBACnB,QAAQ,EAAE,MAAM;aAChB,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAED,uEAAuE;IACvE,iBAAiB;IACjB,MAAM,YAAY,GAA8C,EAAE,CAAC;IACnE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBAChB,sBAAsB;gBACtB,CAAC,EAAE,CAAC;gBACJ,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBAC3C,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;wBAAE,CAAC,EAAE,CAAC,CAAC,oBAAoB;oBAC/C,CAAC,EAAE,CAAC;gBACL,CAAC;gBACD,2DAA2D;gBAC3D,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;oBACjB,uBAAuB;oBACvB,MAAM,QAAQ,GAAG,oCAAoC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACjE,IAAI,QAAQ,EAAE,CAAC;wBACd,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;wBACzE,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,QAAQ,CAAC,EAAE,CAAC;4BACnF,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;wBACpD,CAAC;wBACD,MAAM,CAAC,sDAAoD;oBAC5D,CAAC;gBACF,CAAC;YACF,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACrC,KAAK,EAAE,CAAC;YACT,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACrC,KAAK,EAAE,CAAC;YACT,CAAC;QACF,CAAC;IACF,CAAC;IAED,kCAAkC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxC,MAAM,GAAG,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAE/F,yEAAyE;QACzE,IAAI,OAAO,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,KAAK,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnC,4EAA4E;YAC5E,OAAO,OAAO,GAAG,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,OAAO,EAAE,CAAC;YAClE,IAAI,OAAO,GAAG,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,GAAG;gBAAE,OAAO,EAAE,CAAC;QACjE,CAAC;QAED,gEAAgE;QAChE,OAAO,OAAO,GAAG,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QAElE,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAElE,MAAM,CAAC,IAAI,CAAC;YACX,QAAQ;YACR,SAAS,EAAE,KAAK,GAAG,CAAC;YACpB,OAAO,EAAE,OAAO,GAAG,CAAC;YACpB,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG;YACzB,OAAO,EAAE,cAAc;YACvB,QAAQ,EAAE,MAAM;SAChB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAED,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E,6DAA6D;AAC7D,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAEtD,wEAAwE;AACxE,MAAM,UAAU,GAAG,iCAAiC,CAAC;AAErD,SAAS,SAAS,CAAC,OAAe,EAAE,QAAgB,EAAW;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,+FAA+F;IAC/F,MAAM,UAAU,GAIX,EAAE,CAAC;IAER,IAAI,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC;IAEpC,6BAA6B;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,GAAG,gBAAgB;gBAAE,gBAAgB,GAAG,CAAC,CAAC;YAC/C,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC;IACF,CAAC;IAED,kEAAkE;IAClE,MAAM,QAAQ,GAA+C,EAAE,CAAC;IAChE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAChE,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,EAAE,CAAC;YACX,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjD,CAAC;IACF,CAAC;IAED,iCAAiC;IACjC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,iCAAiC;IACjC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAE3C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC1B,MAAM,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAEtF,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACb,2DAAyD;YACzD,KAAK,GAAG,CAAC,CAAC;QACX,CAAC;aAAM,CAAC;YACP,8DAA8D;YAC9D,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC;YAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACvC,OAAO,IAAI,GAAG,OAAO,EAAE,CAAC;gBACvB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;oBAC/C,IAAI,EAAE,CAAC;gBACR,CAAC;qBAAM,CAAC;oBACP,MAAM;gBACP,CAAC;YACF,CAAC;YACD,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE9D,MAAM,CAAC,IAAI,CAAC;YACX,QAAQ;YACR,SAAS,EAAE,KAAK,GAAG,CAAC;YACpB,OAAO,EAAE,GAAG,GAAG,CAAC;YAChB,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,OAAO,EAAE,cAAc;YACvB,QAAQ,EAAE,MAAM;SAChB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAED,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,SAAS,cAAc,CAAC,OAAe,EAAE,QAAgB,EAAW;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,qDAAqD;IACrD,MAAM,UAAU,GAAmE,EAAE,CAAC;IACtF,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC;IACnB,IAAI,iBAAiB,GAAG,CAAC,CAAC;IAE1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;QAEvC,IAAI,OAAO,EAAE,CAAC;YACb,iBAAiB,EAAE,CAAC;YACpB,IAAI,iBAAiB,IAAI,CAAC,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;gBAChD,mDAAmD;gBACnD,IAAI,OAAO,GAAG,CAAC,GAAG,iBAAiB,CAAC;gBACpC,IAAI,OAAO,GAAG,SAAS;oBAAE,OAAO,GAAG,SAAS,CAAC;gBAC7C,UAAU,CAAC,IAAI,CAAC;oBACf,SAAS,EAAE,SAAS;oBACpB,OAAO,EAAE,OAAO;oBAChB,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;iBACvD,CAAC,CAAC;gBACH,SAAS,GAAG,CAAC,CAAC,CAAC;YAChB,CAAC;QACF,CAAC;aAAM,CAAC;YACP,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;gBACtB,SAAS,GAAG,CAAC,CAAC;YACf,CAAC;YACD,iBAAiB,GAAG,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;IAED,kCAAkC;IAClC,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;QACtB,IAAI,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/B,OAAO,OAAO,GAAG,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QACtE,UAAU,CAAC,IAAI,CAAC;YACf,SAAS,EAAE,SAAS;YACpB,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;SACvD,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACX,CAAC;IAED,2DAA2D;IAC3D,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzC,IAAI,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACrC,IAAI,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAEzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAE3B,IAAI,YAAY,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;YAC9C,2BAA2B;YAC3B,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;YACxB,YAAY,IAAI,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACvC,CAAC;aAAM,CAAC;YACP,oCAAoC;YACpC,MAAM,CAAC,IAAI,CAAC;gBACX,QAAQ;gBACR,SAAS,EAAE,UAAU,GAAG,CAAC;gBACzB,OAAO,EAAE,QAAQ,GAAG,CAAC;gBACrB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,oBAAoB,CAAC,YAAY,CAAC;gBACxC,OAAO,EAAE,YAAY;gBACrB,QAAQ,EAAE,WAAW;aACrB,CAAC,CAAC;YACH,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;YAC5B,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC;YACxB,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,CAAC;IACF,CAAC;IAED,mBAAmB;IACnB,yFAAyF;IACzF,IAAI,YAAY,CAAC,MAAM,GAAG,kBAAkB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,QAAQ,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,YAAY,EAAE,CAAC;IACvC,CAAC;SAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC;YACX,QAAQ;YACR,SAAS,EAAE,UAAU,GAAG,CAAC;YACzB,OAAO,EAAE,QAAQ,GAAG,CAAC;YACrB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,oBAAoB,CAAC,YAAY,CAAC;YACxC,OAAO,EAAE,YAAY;YACrB,QAAQ,EAAE,WAAW;SACrB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd;AAED,0EAA0E;AAC1E,SAAS,oBAAoB,CAAC,OAAe,EAAiB;IAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,SAAS,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IAC7C,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC;AAAA,CACtC;AAED,+EAA+E;AAC/E,yBAAyB;AACzB,+EAA+E;AAE/E;;;;GAIG;AACH,SAAS,cAAc,CAAC,KAAY,EAAW;IAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,IAAI,cAAc,EAAE,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,SAAS,GAAY,EAAE,CAAC;IAC9B,IAAI,YAAY,GAAa,EAAE,CAAC;IAChC,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,iBAAiB;QAEnD,mDAAmD;QACnD,IAAI,WAAW,GAAG,QAAQ,GAAG,cAAc,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxE,4DAA4D;YAC5D,IAAI,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC;YAClC,KAAK,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAClD,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;oBACnC,OAAO,GAAG,CAAC,CAAC;oBACZ,MAAM;gBACP,CAAC;YACF,CAAC;YAED,2CAA2C;YAC3C,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACjD,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,MAAM,WAAW,GAAG,cAAc,GAAG,OAAO,GAAG,CAAC,CAAC;YAEjD,SAAS,CAAC,IAAI,CAAC;gBACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,SAAS,EAAE,cAAc;gBACzB,OAAO,EAAE,WAAW;gBACpB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI;gBAChF,OAAO,EAAE,WAAW;gBACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACxB,CAAC,CAAC;YACH,SAAS,EAAE,CAAC;YAEZ,sCAAsC;YACtC,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC9C,YAAY,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,CAAC;YACpC,cAAc,GAAG,WAAW,GAAG,CAAC,CAAC;YACjC,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC9C,CAAC;aAAM,CAAC;YACP,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,WAAW,IAAI,QAAQ,CAAC;QACzB,CAAC;IACF,CAAC;IAED,uBAAuB;IACvB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,SAAS,CAAC,IAAI,CAAC;YACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,cAAc;YACzB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI;YAChF,OAAO,EAAE,WAAW;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACxB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAAA,CAClD;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,SAAS,cAAc,CAAC,OAAe,EAAE,QAAgB,EAAE,QAAsB,EAAS;IACzF,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IAC7C,OAAO;QACN,QAAQ;QACR,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,IAAI;QACV,OAAO;QACP,QAAQ;KACR,CAAC;AAAA,CACF","sourcesContent":["/**\n * Text file chunker for the semantic search subsystem.\n *\n * Splits non-code files (markdown, YAML, JSON, TOML, plaintext) into\n * semantically meaningful chunks using format-specific boundary detection.\n */\n\nimport type { Chunk, TextFileType } from \"./types.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Files smaller than this are returned as a single chunk. */\nconst MIN_SPLIT_SIZE = 500;\n\n/** Maximum characters per chunk — oversized sections are split at paragraph boundaries. */\nconst MAX_CHUNK_SIZE = 8000;\n\n/** Minimum characters for a plaintext paragraph chunk (small ones get merged). */\nconst MIN_PARAGRAPH_SIZE = 200;\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Chunk a non-code text file by format-specific boundaries.\n *\n * Returns at least one chunk for any non-empty input. Empty files produce\n * a single chunk of kind 'file'.\n */\nexport function chunkTextFile(content: string, filePath: string, fileType: TextFileType): Chunk[] {\n\t// Empty or trivially small files → single chunk\n\tif (content.length < MIN_SPLIT_SIZE) {\n\t\treturn [wholeFileChunk(content, filePath, fileType)];\n\t}\n\n\tlet chunks: Chunk[];\n\tswitch (fileType) {\n\t\tcase \"markdown\":\n\t\t\tchunks = chunkMarkdown(content, filePath);\n\t\t\tbreak;\n\t\tcase \"yaml\":\n\t\t\tchunks = chunkYaml(content, filePath);\n\t\t\tbreak;\n\t\tcase \"json\":\n\t\t\tchunks = chunkJson(content, filePath);\n\t\t\tbreak;\n\t\tcase \"toml\":\n\t\t\tchunks = chunkToml(content, filePath);\n\t\t\tbreak;\n\t\tcase \"plaintext\":\n\t\t\tchunks = chunkPlaintext(content, filePath);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tchunks = [];\n\t}\n\n\t// Fallback: if format-specific parsing produced nothing, return whole file\n\tif (chunks.length === 0) {\n\t\treturn [wholeFileChunk(content, filePath, fileType)];\n\t}\n\n\t// Enforce max chunk size — split oversized chunks at paragraph boundaries\n\treturn chunks.flatMap((chunk) => enforceMaxSize(chunk));\n}\n\n// ============================================================================\n// Markdown\n// ============================================================================\n\n/** Heading regex: lines starting with 1–6 # characters followed by a space. */\nconst HEADING_RE = /^(#{1,6})\\s+(.+)$/;\n\nfunction chunkMarkdown(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Identify heading positions and levels\n\tconst headings: Array<{ line: number; level: number; text: string }> = [];\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst match = HEADING_RE.exec(lines[i]);\n\t\tif (match) {\n\t\t\theadings.push({ line: i, level: match[1].length, text: match[2].trim() });\n\t\t}\n\t}\n\n\t// No headings → treat as plaintext\n\tif (headings.length === 0) {\n\t\treturn chunkPlaintext(content, filePath);\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\t// Content before the first heading (preamble)\n\tif (headings[0].line > 0) {\n\t\tconst preambleLines = lines.slice(0, headings[0].line);\n\t\tconst preambleContent = preambleLines.join(\"\\n\");\n\t\tif (preambleContent.trim().length > 0) {\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: headings[0].line, // 1-indexed, line before the first heading\n\t\t\t\tkind: \"heading_section\",\n\t\t\t\tname: null,\n\t\t\t\tcontent: preambleContent,\n\t\t\t\tfileType: \"markdown\",\n\t\t\t});\n\t\t}\n\t}\n\n\t// Each heading owns all lines until the next heading of same or higher level\n\tfor (let i = 0; i < headings.length; i++) {\n\t\tconst start = headings[i].line;\n\t\tlet end: number;\n\n\t\t// Find the next heading at the same or higher (lower number) level\n\t\tlet nextSameOrHigher = -1;\n\t\tfor (let j = i + 1; j < headings.length; j++) {\n\t\t\tif (headings[j].level <= headings[i].level) {\n\t\t\t\tnextSameOrHigher = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (nextSameOrHigher !== -1) {\n\t\t\tend = headings[nextSameOrHigher].line - 1;\n\t\t} else {\n\t\t\tend = lines.length - 1;\n\t\t}\n\n\t\tconst sectionLines = lines.slice(start, end + 1);\n\t\tconst sectionContent = sectionLines.join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1, // 1-indexed\n\t\t\tendLine: end + 1, // 1-indexed, inclusive\n\t\t\tkind: \"heading_section\",\n\t\t\tname: headings[i].text,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"markdown\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// YAML\n// ============================================================================\n\n/**\n * Top-level YAML key: a line that starts with a non-space, non-comment\n * character and contains a colon. Excludes YAML directives (---/...).\n */\nconst YAML_TOP_KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\\s*:/;\n\nfunction chunkYaml(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Find top-level key positions\n\tconst keys: Array<{ line: number; name: string }> = [];\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\t// Skip comments, blank lines, YAML directives\n\t\tif (line.startsWith(\"#\") || line.startsWith(\"---\") || line.startsWith(\"...\") || line.trim() === \"\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst match = YAML_TOP_KEY_RE.exec(line);\n\t\tif (match) {\n\t\t\tkeys.push({ line: i, name: match[1] });\n\t\t}\n\t}\n\n\tif (keys.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\t// Preamble (comments, directives before first key) is included in the\n\t// first key's chunk via the `start = 0` logic below for `i === 0`.\n\n\tfor (let i = 0; i < keys.length; i++) {\n\t\t// Include any preceding comments/blank lines that belong to this key\n\t\t// by looking backwards from the key to find attached comments\n\t\tlet start = keys[i].line;\n\t\tif (i === 0) {\n\t\t\t// First key includes any preamble (comments, directives)\n\t\t\tstart = 0;\n\t\t} else {\n\t\t\t// Look back for comment lines directly above\n\t\t\tlet scan = keys[i].line - 1;\n\t\t\twhile (scan > keys[i - 1].line) {\n\t\t\t\tconst trimmed = lines[scan].trim();\n\t\t\t\tif (trimmed.startsWith(\"#\") || trimmed === \"\") {\n\t\t\t\t\tscan--;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart = scan + 1;\n\t\t}\n\n\t\tconst end = i < keys.length - 1 ? keys[i + 1].line - 1 : lines.length - 1;\n\n\t\t// Trim trailing blank lines to find the real end\n\t\tlet realEnd = end;\n\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") {\n\t\t\trealEnd--;\n\t\t}\n\t\t// But keep at least the key line\n\t\tif (realEnd < keys[i].line) realEnd = keys[i].line;\n\n\t\t// Include trailing blank lines within the range for line counting,\n\t\t// but use them as separators\n\t\tconst sectionLines = lines.slice(start, end + 1);\n\t\tconst sectionContent = sectionLines.join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: end + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: keys[i].name,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"yaml\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// JSON\n// ============================================================================\n\nfunction chunkJson(content: string, filePath: string): Chunk[] {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch {\n\t\t// Invalid JSON → return as whole file\n\t\treturn [];\n\t}\n\n\t// Only split top-level objects. Arrays and primitives → single chunk.\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\treturn [];\n\t}\n\n\tconst topKeys = Object.keys(parsed);\n\tif (topKeys.length === 0) {\n\t\treturn [];\n\t}\n\n\t// For JSON we can't rely on simple line scanning because values can span\n\t// multiple lines with arbitrary nesting. Instead, re-serialize each\n\t// top-level key and locate its position in the original content.\n\tconst lines = content.split(\"\\n\");\n\tconst chunks: Chunk[] = [];\n\n\t// Strategy: scan the original text to find each top-level key's line range.\n\t// A top-level key in formatted JSON appears as ` \"key\":` at indent level 1.\n\t// In minified JSON with a single line, we fall back to serialized slicing.\n\tif (lines.length === 1) {\n\t\t// Minified JSON — produce one chunk per top-level key using re-serialized content\n\t\tconst obj = parsed as Record<string, unknown>;\n\t\tfor (const key of topKeys) {\n\t\t\tconst serialized = JSON.stringify({ [key]: obj[key] }, null, 2);\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: 1,\n\t\t\t\tkind: \"top_level_key\",\n\t\t\t\tname: key,\n\t\t\t\tcontent: serialized,\n\t\t\t\tfileType: \"json\",\n\t\t\t});\n\t\t}\n\t\treturn chunks;\n\t}\n\n\t// Multi-line JSON: find each top-level key by scanning for `\"key\":` at\n\t// brace depth 1.\n\tconst keyPositions: Array<{ key: string; startLine: number }> = [];\n\tlet depth = 0;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tfor (let c = 0; c < line.length; c++) {\n\t\t\tconst ch = line[c];\n\t\t\tif (ch === '\"') {\n\t\t\t\t// Skip string content\n\t\t\t\tc++;\n\t\t\t\twhile (c < line.length && line[c] !== '\"') {\n\t\t\t\t\tif (line[c] === \"\\\\\") c++; // skip escaped char\n\t\t\t\t\tc++;\n\t\t\t\t}\n\t\t\t\t// Check if this string at depth 1 is a key (followed by :)\n\t\t\t\tif (depth === 1) {\n\t\t\t\t\t// Extract the key name\n\t\t\t\t\tconst keyMatch = /^\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"\\s*:/.exec(line);\n\t\t\t\t\tif (keyMatch) {\n\t\t\t\t\t\tconst foundKey = keyMatch[1].replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n\t\t\t\t\t\tif (topKeys.includes(foundKey) && !keyPositions.some((kp) => kp.key === foundKey)) {\n\t\t\t\t\t\t\tkeyPositions.push({ key: foundKey, startLine: i });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak; // Move to next line — we found the key on this line\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ch === \"{\" || ch === \"[\") {\n\t\t\t\tdepth++;\n\t\t\t} else if (ch === \"}\" || ch === \"]\") {\n\t\t\t\tdepth--;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build chunks from key positions\n\tfor (let i = 0; i < keyPositions.length; i++) {\n\t\tconst start = keyPositions[i].startLine;\n\t\tconst end = i < keyPositions.length - 1 ? keyPositions[i + 1].startLine - 1 : lines.length - 1;\n\n\t\t// Trim the trailing closing brace of the root object from the last chunk\n\t\tlet realEnd = end;\n\t\tif (i === keyPositions.length - 1) {\n\t\t\t// Walk back from end to skip the root closing brace and trailing whitespace\n\t\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") realEnd--;\n\t\t\tif (realEnd > start && lines[realEnd].trim() === \"}\") realEnd--;\n\t\t}\n\n\t\t// Trim trailing commas and blank lines from non-last chunks too\n\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") realEnd--;\n\n\t\tconst sectionContent = lines.slice(start, realEnd + 1).join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: realEnd + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: keyPositions[i].key,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"json\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// TOML\n// ============================================================================\n\n/** TOML section header: [section] or [[array-of-tables]]. */\nconst TOML_SECTION_RE = /^\\[{1,2}([^\\]]+)\\]{1,2}\\s*$/;\n\n/** TOML top-level key-value pair (not indented, before any section). */\nconst TOML_KV_RE = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\\s*=/;\n\nfunction chunkToml(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Phase 1: Identify all top-level boundaries (sections and top-level KV pairs before sections)\n\tconst boundaries: Array<{\n\t\tline: number;\n\t\tkind: \"section\" | \"kv\";\n\t\tname: string;\n\t}> = [];\n\n\tlet firstSectionLine = lines.length;\n\n\t// Find section headers first\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst match = TOML_SECTION_RE.exec(lines[i]);\n\t\tif (match) {\n\t\t\tif (i < firstSectionLine) firstSectionLine = i;\n\t\t\tboundaries.push({ line: i, kind: \"section\", name: match[1].trim() });\n\t\t}\n\t}\n\n\t// Find top-level key-value pairs (lines before the first section)\n\tconst kvGroups: Array<{ startLine: number; name: string }> = [];\n\tfor (let i = 0; i < firstSectionLine; i++) {\n\t\tconst line = lines[i];\n\t\tif (line.trim() === \"\" || line.trim().startsWith(\"#\")) continue;\n\t\tconst match = TOML_KV_RE.exec(line);\n\t\tif (match) {\n\t\t\tkvGroups.push({ startLine: i, name: match[1] });\n\t\t}\n\t}\n\n\t// Merge KV pairs into boundaries\n\tfor (const kv of kvGroups) {\n\t\tboundaries.push({ line: kv.startLine, kind: \"kv\", name: kv.name });\n\t}\n\n\t// Sort boundaries by line number\n\tboundaries.sort((a, b) => a.line - b.line);\n\n\tif (boundaries.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\tfor (let i = 0; i < boundaries.length; i++) {\n\t\tconst boundary = boundaries[i];\n\t\tlet start = boundary.line;\n\t\tconst end = i < boundaries.length - 1 ? boundaries[i + 1].line - 1 : lines.length - 1;\n\n\t\tif (i === 0) {\n\t\t\t// First boundary — include any leading comments/preamble\n\t\t\tstart = 0;\n\t\t} else {\n\t\t\t// Look back for comment/blank lines attached to this boundary\n\t\t\tlet scan = boundary.line - 1;\n\t\t\tconst prevEnd = boundaries[i - 1].line;\n\t\t\twhile (scan > prevEnd) {\n\t\t\t\tconst trimmed = lines[scan].trim();\n\t\t\t\tif (trimmed.startsWith(\"#\") || trimmed === \"\") {\n\t\t\t\t\tscan--;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart = scan + 1;\n\t\t}\n\n\t\tconst sectionContent = lines.slice(start, end + 1).join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: end + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: boundary.name,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"toml\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// Plaintext\n// ============================================================================\n\nfunction chunkPlaintext(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Split into paragraphs at double-newline boundaries\n\tconst paragraphs: Array<{ startLine: number; endLine: number; content: string }> = [];\n\tlet paraStart = -1;\n\tlet consecutiveBlanks = 0;\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst isBlank = lines[i].trim() === \"\";\n\n\t\tif (isBlank) {\n\t\t\tconsecutiveBlanks++;\n\t\t\tif (consecutiveBlanks >= 2 && paraStart !== -1) {\n\t\t\t\t// End current paragraph at the last non-blank line\n\t\t\t\tlet paraEnd = i - consecutiveBlanks;\n\t\t\t\tif (paraEnd < paraStart) paraEnd = paraStart;\n\t\t\t\tparagraphs.push({\n\t\t\t\t\tstartLine: paraStart,\n\t\t\t\t\tendLine: paraEnd,\n\t\t\t\t\tcontent: lines.slice(paraStart, paraEnd + 1).join(\"\\n\"),\n\t\t\t\t});\n\t\t\t\tparaStart = -1;\n\t\t\t}\n\t\t} else {\n\t\t\tif (paraStart === -1) {\n\t\t\t\tparaStart = i;\n\t\t\t}\n\t\t\tconsecutiveBlanks = 0;\n\t\t}\n\t}\n\n\t// Don't forget the last paragraph\n\tif (paraStart !== -1) {\n\t\tlet paraEnd = lines.length - 1;\n\t\twhile (paraEnd > paraStart && lines[paraEnd].trim() === \"\") paraEnd--;\n\t\tparagraphs.push({\n\t\t\tstartLine: paraStart,\n\t\t\tendLine: paraEnd,\n\t\t\tcontent: lines.slice(paraStart, paraEnd + 1).join(\"\\n\"),\n\t\t});\n\t}\n\n\tif (paragraphs.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Group small paragraphs together to meet the minimum size\n\tconst chunks: Chunk[] = [];\n\tlet groupStart = paragraphs[0].startLine;\n\tlet groupEnd = paragraphs[0].endLine;\n\tlet groupContent = paragraphs[0].content;\n\n\tfor (let i = 1; i < paragraphs.length; i++) {\n\t\tconst para = paragraphs[i];\n\n\t\tif (groupContent.length < MIN_PARAGRAPH_SIZE) {\n\t\t\t// Merge with current group\n\t\t\tgroupEnd = para.endLine;\n\t\t\tgroupContent += `\\n\\n${para.content}`;\n\t\t} else {\n\t\t\t// Emit current group, start new one\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: groupStart + 1,\n\t\t\t\tendLine: groupEnd + 1,\n\t\t\t\tkind: \"paragraph\",\n\t\t\t\tname: extractParagraphName(groupContent),\n\t\t\t\tcontent: groupContent,\n\t\t\t\tfileType: \"plaintext\",\n\t\t\t});\n\t\t\tgroupStart = para.startLine;\n\t\t\tgroupEnd = para.endLine;\n\t\t\tgroupContent = para.content;\n\t\t}\n\t}\n\n\t// Emit final group\n\t// If the final group is too small and there are existing chunks, merge with the last one\n\tif (groupContent.length < MIN_PARAGRAPH_SIZE && chunks.length > 0) {\n\t\tconst last = chunks[chunks.length - 1];\n\t\tlast.endLine = groupEnd + 1;\n\t\tlast.content += `\\n\\n${groupContent}`;\n\t} else {\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: groupStart + 1,\n\t\t\tendLine: groupEnd + 1,\n\t\t\tkind: \"paragraph\",\n\t\t\tname: extractParagraphName(groupContent),\n\t\t\tcontent: groupContent,\n\t\t\tfileType: \"plaintext\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n/** Extract a short name from the first line of a paragraph, truncated. */\nfunction extractParagraphName(content: string): string | null {\n\tconst firstLine = content.split(\"\\n\")[0].trim();\n\tif (firstLine.length === 0) return null;\n\tif (firstLine.length <= 60) return firstLine;\n\treturn `${firstLine.slice(0, 57)}...`;\n}\n\n// ============================================================================\n// Chunk Size Enforcement\n// ============================================================================\n\n/**\n * If a chunk exceeds MAX_CHUNK_SIZE, split it at paragraph boundaries\n * (double newlines). If no paragraph boundaries exist, split at line\n * boundaries near the limit.\n */\nfunction enforceMaxSize(chunk: Chunk): Chunk[] {\n\tif (chunk.content.length <= MAX_CHUNK_SIZE) {\n\t\treturn [chunk];\n\t}\n\n\tconst lines = chunk.content.split(\"\\n\");\n\tconst subChunks: Chunk[] = [];\n\tlet currentLines: string[] = [];\n\tlet currentSize = 0;\n\tlet chunkStartLine = chunk.startLine;\n\tlet partIndex = 0;\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineSize = line.length + 1; // +1 for newline\n\n\t\t// Check if adding this line would exceed the limit\n\t\tif (currentSize + lineSize > MAX_CHUNK_SIZE && currentLines.length > 0) {\n\t\t\t// Try to find a paragraph boundary (blank line) to split at\n\t\t\tlet splitAt = currentLines.length;\n\t\t\tfor (let j = currentLines.length - 1; j > 0; j--) {\n\t\t\t\tif (currentLines[j].trim() === \"\") {\n\t\t\t\t\tsplitAt = j;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Emit the sub-chunk up to the split point\n\t\t\tconst emitLines = currentLines.slice(0, splitAt);\n\t\t\tconst emitContent = emitLines.join(\"\\n\");\n\t\t\tconst emitEndLine = chunkStartLine + splitAt - 1;\n\n\t\t\tsubChunks.push({\n\t\t\t\tfilePath: chunk.filePath,\n\t\t\t\tstartLine: chunkStartLine,\n\t\t\t\tendLine: emitEndLine,\n\t\t\t\tkind: chunk.kind,\n\t\t\t\tname: partIndex === 0 ? chunk.name : chunk.name ? `${chunk.name} (cont.)` : null,\n\t\t\t\tcontent: emitContent,\n\t\t\t\tfileType: chunk.fileType,\n\t\t\t});\n\t\t\tpartIndex++;\n\n\t\t\t// Keep remaining lines from the split\n\t\t\tconst remaining = currentLines.slice(splitAt);\n\t\t\tcurrentLines = [...remaining, line];\n\t\t\tchunkStartLine = emitEndLine + 1;\n\t\t\tcurrentSize = currentLines.join(\"\\n\").length;\n\t\t} else {\n\t\t\tcurrentLines.push(line);\n\t\t\tcurrentSize += lineSize;\n\t\t}\n\t}\n\n\t// Emit remaining lines\n\tif (currentLines.length > 0) {\n\t\tconst emitContent = currentLines.join(\"\\n\");\n\t\tsubChunks.push({\n\t\t\tfilePath: chunk.filePath,\n\t\t\tstartLine: chunkStartLine,\n\t\t\tendLine: chunk.endLine,\n\t\t\tkind: chunk.kind,\n\t\t\tname: partIndex === 0 ? chunk.name : chunk.name ? `${chunk.name} (cont.)` : null,\n\t\t\tcontent: emitContent,\n\t\t\tfileType: chunk.fileType,\n\t\t});\n\t}\n\n\treturn subChunks.length > 0 ? subChunks : [chunk];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction wholeFileChunk(content: string, filePath: string, fileType: TextFileType): Chunk {\n\tconst lineCount = content.split(\"\\n\").length;\n\treturn {\n\t\tfilePath,\n\t\tstartLine: 1,\n\t\tendLine: lineCount,\n\t\tkind: \"file\",\n\t\tname: null,\n\t\tcontent,\n\t\tfileType,\n\t};\n}\n"]}
1
+ {"version":3,"file":"text-chunker.js","sourceRoot":"","sources":["../src/text-chunker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Text file chunker for the semantic search subsystem.\n *\n * Splits non-code files (markdown, YAML, JSON, TOML, plaintext) into\n * semantically meaningful chunks using format-specific boundary detection.\n */\n\nimport type { Chunk, TextFileType } from \"./types.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Files smaller than this are returned as a single chunk. */\nconst MIN_SPLIT_SIZE = 500;\n\n/** Maximum characters per chunk — oversized sections are split at paragraph boundaries. */\nconst MAX_CHUNK_SIZE = 8000;\n\n/** Minimum characters for a plaintext paragraph chunk (small ones get merged). */\nconst MIN_PARAGRAPH_SIZE = 200;\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Chunk a non-code text file by format-specific boundaries.\n *\n * Returns at least one chunk for any non-empty input. Empty files produce\n * a single chunk of kind 'file'.\n */\nexport function chunkTextFile(content: string, filePath: string, fileType: TextFileType): Chunk[] {\n\t// Empty or trivially small files → single chunk\n\tif (content.length < MIN_SPLIT_SIZE) {\n\t\treturn [wholeFileChunk(content, filePath, fileType)];\n\t}\n\n\tlet chunks: Chunk[];\n\tswitch (fileType) {\n\t\tcase \"markdown\":\n\t\t\tchunks = chunkMarkdown(content, filePath);\n\t\t\tbreak;\n\t\tcase \"yaml\":\n\t\t\tchunks = chunkYaml(content, filePath);\n\t\t\tbreak;\n\t\tcase \"json\":\n\t\t\tchunks = chunkJson(content, filePath);\n\t\t\tbreak;\n\t\tcase \"toml\":\n\t\t\tchunks = chunkToml(content, filePath);\n\t\t\tbreak;\n\t\tcase \"plaintext\":\n\t\t\tchunks = chunkPlaintext(content, filePath);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tchunks = [];\n\t}\n\n\t// Fallback: if format-specific parsing produced nothing, return whole file\n\tif (chunks.length === 0) {\n\t\treturn [wholeFileChunk(content, filePath, fileType)];\n\t}\n\n\t// Enforce max chunk size — split oversized chunks at paragraph boundaries\n\treturn chunks.flatMap((chunk) => enforceMaxSize(chunk));\n}\n\n// ============================================================================\n// Markdown\n// ============================================================================\n\n/** Heading regex: lines starting with 1–6 # characters followed by a space. */\nconst HEADING_RE = /^(#{1,6})\\s+(.+)$/;\n\nfunction chunkMarkdown(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Identify heading positions and levels\n\tconst headings: Array<{ line: number; level: number; text: string }> = [];\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst match = HEADING_RE.exec(lines[i]);\n\t\tif (match) {\n\t\t\theadings.push({ line: i, level: match[1].length, text: match[2].trim() });\n\t\t}\n\t}\n\n\t// No headings → treat as plaintext\n\tif (headings.length === 0) {\n\t\treturn chunkPlaintext(content, filePath);\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\t// Content before the first heading (preamble)\n\tif (headings[0].line > 0) {\n\t\tconst preambleLines = lines.slice(0, headings[0].line);\n\t\tconst preambleContent = preambleLines.join(\"\\n\");\n\t\tif (preambleContent.trim().length > 0) {\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: headings[0].line, // 1-indexed, line before the first heading\n\t\t\t\tkind: \"heading_section\",\n\t\t\t\tname: null,\n\t\t\t\tcontent: preambleContent,\n\t\t\t\tfileType: \"markdown\",\n\t\t\t});\n\t\t}\n\t}\n\n\t// Each heading owns all lines until the next heading of same or higher level\n\tfor (let i = 0; i < headings.length; i++) {\n\t\tconst start = headings[i].line;\n\t\tlet end: number;\n\n\t\t// Find the next heading at the same or higher (lower number) level\n\t\tlet nextSameOrHigher = -1;\n\t\tfor (let j = i + 1; j < headings.length; j++) {\n\t\t\tif (headings[j].level <= headings[i].level) {\n\t\t\t\tnextSameOrHigher = j;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (nextSameOrHigher !== -1) {\n\t\t\tend = headings[nextSameOrHigher].line - 1;\n\t\t} else {\n\t\t\tend = lines.length - 1;\n\t\t}\n\n\t\tconst sectionLines = lines.slice(start, end + 1);\n\t\tconst sectionContent = sectionLines.join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1, // 1-indexed\n\t\t\tendLine: end + 1, // 1-indexed, inclusive\n\t\t\tkind: \"heading_section\",\n\t\t\tname: headings[i].text,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"markdown\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// YAML\n// ============================================================================\n\n/**\n * Top-level YAML key: a line that starts with a non-space, non-comment\n * character and contains a colon. Excludes YAML directives (---/...).\n */\nconst YAML_TOP_KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\\s*:/;\n\nfunction chunkYaml(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Find top-level key positions\n\tconst keys: Array<{ line: number; name: string }> = [];\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\t// Skip comments, blank lines, YAML directives\n\t\tif (line.startsWith(\"#\") || line.startsWith(\"---\") || line.startsWith(\"...\") || line.trim() === \"\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst match = YAML_TOP_KEY_RE.exec(line);\n\t\tif (match) {\n\t\t\tkeys.push({ line: i, name: match[1] });\n\t\t}\n\t}\n\n\tif (keys.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\t// Preamble (comments, directives before first key) is included in the\n\t// first key's chunk via the `start = 0` logic below for `i === 0`.\n\n\tfor (let i = 0; i < keys.length; i++) {\n\t\t// Include any preceding comments/blank lines that belong to this key\n\t\t// by looking backwards from the key to find attached comments\n\t\tlet start = keys[i].line;\n\t\tif (i === 0) {\n\t\t\t// First key includes any preamble (comments, directives)\n\t\t\tstart = 0;\n\t\t} else {\n\t\t\t// Look back for comment lines directly above\n\t\t\tlet scan = keys[i].line - 1;\n\t\t\twhile (scan > keys[i - 1].line) {\n\t\t\t\tconst trimmed = lines[scan].trim();\n\t\t\t\tif (trimmed.startsWith(\"#\") || trimmed === \"\") {\n\t\t\t\t\tscan--;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart = scan + 1;\n\t\t}\n\n\t\tconst end = i < keys.length - 1 ? keys[i + 1].line - 1 : lines.length - 1;\n\n\t\t// Trim trailing blank lines to find the real end\n\t\tlet realEnd = end;\n\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") {\n\t\t\trealEnd--;\n\t\t}\n\t\t// But keep at least the key line\n\t\tif (realEnd < keys[i].line) realEnd = keys[i].line;\n\n\t\t// Include trailing blank lines within the range for line counting,\n\t\t// but use them as separators\n\t\tconst sectionLines = lines.slice(start, end + 1);\n\t\tconst sectionContent = sectionLines.join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: end + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: keys[i].name,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"yaml\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// JSON\n// ============================================================================\n\nfunction chunkJson(content: string, filePath: string): Chunk[] {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch {\n\t\t// Invalid JSON → return as whole file\n\t\treturn [];\n\t}\n\n\t// Only split top-level objects. Arrays and primitives → single chunk.\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\treturn [];\n\t}\n\n\tconst topKeys = Object.keys(parsed);\n\tif (topKeys.length === 0) {\n\t\treturn [];\n\t}\n\n\t// For JSON we can't rely on simple line scanning because values can span\n\t// multiple lines with arbitrary nesting. Instead, re-serialize each\n\t// top-level key and locate its position in the original content.\n\tconst lines = content.split(\"\\n\");\n\tconst chunks: Chunk[] = [];\n\n\t// Strategy: scan the original text to find each top-level key's line range.\n\t// A top-level key in formatted JSON appears as ` \"key\":` at indent level 1.\n\t// In minified JSON with a single line, we fall back to serialized slicing.\n\tif (lines.length === 1) {\n\t\t// Minified JSON — produce one chunk per top-level key using re-serialized content\n\t\tconst obj = parsed as Record<string, unknown>;\n\t\tfor (const key of topKeys) {\n\t\t\tconst serialized = JSON.stringify({ [key]: obj[key] }, null, 2);\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: 1,\n\t\t\t\tkind: \"top_level_key\",\n\t\t\t\tname: key,\n\t\t\t\tcontent: serialized,\n\t\t\t\tfileType: \"json\",\n\t\t\t});\n\t\t}\n\t\treturn chunks;\n\t}\n\n\t// Multi-line JSON: find each top-level key by scanning for `\"key\":` at\n\t// brace depth 1.\n\tconst keyPositions: Array<{ key: string; startLine: number }> = [];\n\tlet depth = 0;\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tfor (let c = 0; c < line.length; c++) {\n\t\t\tconst ch = line[c];\n\t\t\tif (ch === '\"') {\n\t\t\t\t// Skip string content\n\t\t\t\tc++;\n\t\t\t\twhile (c < line.length && line[c] !== '\"') {\n\t\t\t\t\tif (line[c] === \"\\\\\") c++; // skip escaped char\n\t\t\t\t\tc++;\n\t\t\t\t}\n\t\t\t\t// Check if this string at depth 1 is a key (followed by :)\n\t\t\t\tif (depth === 1) {\n\t\t\t\t\t// Extract the key name\n\t\t\t\t\tconst keyMatch = /^\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"\\s*:/.exec(line);\n\t\t\t\t\tif (keyMatch) {\n\t\t\t\t\t\tconst foundKey = keyMatch[1].replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n\t\t\t\t\t\tif (topKeys.includes(foundKey) && !keyPositions.some((kp) => kp.key === foundKey)) {\n\t\t\t\t\t\t\tkeyPositions.push({ key: foundKey, startLine: i });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak; // Move to next line — we found the key on this line\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ch === \"{\" || ch === \"[\") {\n\t\t\t\tdepth++;\n\t\t\t} else if (ch === \"}\" || ch === \"]\") {\n\t\t\t\tdepth--;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build chunks from key positions\n\tfor (let i = 0; i < keyPositions.length; i++) {\n\t\tconst start = keyPositions[i].startLine;\n\t\tconst end = i < keyPositions.length - 1 ? keyPositions[i + 1].startLine - 1 : lines.length - 1;\n\n\t\t// Trim the trailing closing brace of the root object from the last chunk\n\t\tlet realEnd = end;\n\t\tif (i === keyPositions.length - 1) {\n\t\t\t// Walk back from end to skip the root closing brace and trailing whitespace\n\t\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") realEnd--;\n\t\t\tif (realEnd > start && lines[realEnd].trim() === \"}\") realEnd--;\n\t\t}\n\n\t\t// Trim trailing commas and blank lines from non-last chunks too\n\t\twhile (realEnd > start && lines[realEnd].trim() === \"\") realEnd--;\n\n\t\tconst sectionContent = lines.slice(start, realEnd + 1).join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: realEnd + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: keyPositions[i].key,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"json\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// TOML\n// ============================================================================\n\n/** TOML section header: [section] or [[array-of-tables]]. */\nconst TOML_SECTION_RE = /^\\[{1,2}([^\\]]+)\\]{1,2}\\s*$/;\n\n/** TOML top-level key-value pair (not indented, before any section). */\nconst TOML_KV_RE = /^([a-zA-Z_][a-zA-Z0-9_.-]*)\\s*=/;\n\nfunction chunkToml(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Phase 1: Identify all top-level boundaries (sections and top-level KV pairs before sections)\n\tconst boundaries: Array<{\n\t\tline: number;\n\t\tkind: \"section\" | \"kv\";\n\t\tname: string;\n\t}> = [];\n\n\tlet firstSectionLine = lines.length;\n\n\t// Find section headers first\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst match = TOML_SECTION_RE.exec(lines[i]);\n\t\tif (match) {\n\t\t\tif (i < firstSectionLine) firstSectionLine = i;\n\t\t\tboundaries.push({ line: i, kind: \"section\", name: match[1].trim() });\n\t\t}\n\t}\n\n\t// Find top-level key-value pairs (lines before the first section)\n\tconst kvGroups: Array<{ startLine: number; name: string }> = [];\n\tfor (let i = 0; i < firstSectionLine; i++) {\n\t\tconst line = lines[i];\n\t\tif (line.trim() === \"\" || line.trim().startsWith(\"#\")) continue;\n\t\tconst match = TOML_KV_RE.exec(line);\n\t\tif (match) {\n\t\t\tkvGroups.push({ startLine: i, name: match[1] });\n\t\t}\n\t}\n\n\t// Merge KV pairs into boundaries\n\tfor (const kv of kvGroups) {\n\t\tboundaries.push({ line: kv.startLine, kind: \"kv\", name: kv.name });\n\t}\n\n\t// Sort boundaries by line number\n\tboundaries.sort((a, b) => a.line - b.line);\n\n\tif (boundaries.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst chunks: Chunk[] = [];\n\n\tfor (let i = 0; i < boundaries.length; i++) {\n\t\tconst boundary = boundaries[i];\n\t\tlet start = boundary.line;\n\t\tconst end = i < boundaries.length - 1 ? boundaries[i + 1].line - 1 : lines.length - 1;\n\n\t\tif (i === 0) {\n\t\t\t// First boundary — include any leading comments/preamble\n\t\t\tstart = 0;\n\t\t} else {\n\t\t\t// Look back for comment/blank lines attached to this boundary\n\t\t\tlet scan = boundary.line - 1;\n\t\t\tconst prevEnd = boundaries[i - 1].line;\n\t\t\twhile (scan > prevEnd) {\n\t\t\t\tconst trimmed = lines[scan].trim();\n\t\t\t\tif (trimmed.startsWith(\"#\") || trimmed === \"\") {\n\t\t\t\t\tscan--;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart = scan + 1;\n\t\t}\n\n\t\tconst sectionContent = lines.slice(start, end + 1).join(\"\\n\");\n\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: start + 1,\n\t\t\tendLine: end + 1,\n\t\t\tkind: \"top_level_key\",\n\t\t\tname: boundary.name,\n\t\t\tcontent: sectionContent,\n\t\t\tfileType: \"toml\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n// ============================================================================\n// Plaintext\n// ============================================================================\n\nfunction chunkPlaintext(content: string, filePath: string): Chunk[] {\n\tconst lines = content.split(\"\\n\");\n\n\t// Split into paragraphs at double-newline boundaries\n\tconst paragraphs: Array<{ startLine: number; endLine: number; content: string }> = [];\n\tlet paraStart = -1;\n\tlet consecutiveBlanks = 0;\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst isBlank = lines[i].trim() === \"\";\n\n\t\tif (isBlank) {\n\t\t\tconsecutiveBlanks++;\n\t\t\tif (consecutiveBlanks >= 2 && paraStart !== -1) {\n\t\t\t\t// End current paragraph at the last non-blank line\n\t\t\t\tlet paraEnd = i - consecutiveBlanks;\n\t\t\t\tif (paraEnd < paraStart) paraEnd = paraStart;\n\t\t\t\tparagraphs.push({\n\t\t\t\t\tstartLine: paraStart,\n\t\t\t\t\tendLine: paraEnd,\n\t\t\t\t\tcontent: lines.slice(paraStart, paraEnd + 1).join(\"\\n\"),\n\t\t\t\t});\n\t\t\t\tparaStart = -1;\n\t\t\t}\n\t\t} else {\n\t\t\tif (paraStart === -1) {\n\t\t\t\tparaStart = i;\n\t\t\t}\n\t\t\tconsecutiveBlanks = 0;\n\t\t}\n\t}\n\n\t// Don't forget the last paragraph\n\tif (paraStart !== -1) {\n\t\tlet paraEnd = lines.length - 1;\n\t\twhile (paraEnd > paraStart && lines[paraEnd].trim() === \"\") paraEnd--;\n\t\tparagraphs.push({\n\t\t\tstartLine: paraStart,\n\t\t\tendLine: paraEnd,\n\t\t\tcontent: lines.slice(paraStart, paraEnd + 1).join(\"\\n\"),\n\t\t});\n\t}\n\n\tif (paragraphs.length === 0) {\n\t\treturn [];\n\t}\n\n\t// Group small paragraphs together to meet the minimum size\n\tconst chunks: Chunk[] = [];\n\tlet groupStart = paragraphs[0].startLine;\n\tlet groupEnd = paragraphs[0].endLine;\n\tlet groupContent = paragraphs[0].content;\n\n\tfor (let i = 1; i < paragraphs.length; i++) {\n\t\tconst para = paragraphs[i];\n\n\t\tif (groupContent.length < MIN_PARAGRAPH_SIZE) {\n\t\t\t// Merge with current group\n\t\t\tgroupEnd = para.endLine;\n\t\t\tgroupContent += `\\n\\n${para.content}`;\n\t\t} else {\n\t\t\t// Emit current group, start new one\n\t\t\tchunks.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: groupStart + 1,\n\t\t\t\tendLine: groupEnd + 1,\n\t\t\t\tkind: \"paragraph\",\n\t\t\t\tname: extractParagraphName(groupContent),\n\t\t\t\tcontent: groupContent,\n\t\t\t\tfileType: \"plaintext\",\n\t\t\t});\n\t\t\tgroupStart = para.startLine;\n\t\t\tgroupEnd = para.endLine;\n\t\t\tgroupContent = para.content;\n\t\t}\n\t}\n\n\t// Emit final group\n\t// If the final group is too small and there are existing chunks, merge with the last one\n\tif (groupContent.length < MIN_PARAGRAPH_SIZE && chunks.length > 0) {\n\t\tconst last = chunks[chunks.length - 1];\n\t\tlast.endLine = groupEnd + 1;\n\t\tlast.content += `\\n\\n${groupContent}`;\n\t} else {\n\t\tchunks.push({\n\t\t\tfilePath,\n\t\t\tstartLine: groupStart + 1,\n\t\t\tendLine: groupEnd + 1,\n\t\t\tkind: \"paragraph\",\n\t\t\tname: extractParagraphName(groupContent),\n\t\t\tcontent: groupContent,\n\t\t\tfileType: \"plaintext\",\n\t\t});\n\t}\n\n\treturn chunks;\n}\n\n/** Extract a short name from the first line of a paragraph, truncated. */\nfunction extractParagraphName(content: string): string | null {\n\tconst firstLine = content.split(\"\\n\")[0].trim();\n\tif (firstLine.length === 0) return null;\n\tif (firstLine.length <= 60) return firstLine;\n\treturn `${firstLine.slice(0, 57)}...`;\n}\n\n// ============================================================================\n// Chunk Size Enforcement\n// ============================================================================\n\n/**\n * If a chunk exceeds MAX_CHUNK_SIZE, split it at paragraph boundaries\n * (double newlines). If no paragraph boundaries exist, split at line\n * boundaries near the limit.\n */\nfunction enforceMaxSize(chunk: Chunk): Chunk[] {\n\tif (chunk.content.length <= MAX_CHUNK_SIZE) {\n\t\treturn [chunk];\n\t}\n\n\tconst lines = chunk.content.split(\"\\n\");\n\tconst subChunks: Chunk[] = [];\n\tlet currentLines: string[] = [];\n\tlet currentSize = 0;\n\tlet chunkStartLine = chunk.startLine;\n\tlet partIndex = 0;\n\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tconst lineSize = line.length + 1; // +1 for newline\n\n\t\t// Check if adding this line would exceed the limit\n\t\tif (currentSize + lineSize > MAX_CHUNK_SIZE && currentLines.length > 0) {\n\t\t\t// Try to find a paragraph boundary (blank line) to split at\n\t\t\tlet splitAt = currentLines.length;\n\t\t\tfor (let j = currentLines.length - 1; j > 0; j--) {\n\t\t\t\tif (currentLines[j].trim() === \"\") {\n\t\t\t\t\tsplitAt = j;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Emit the sub-chunk up to the split point\n\t\t\tconst emitLines = currentLines.slice(0, splitAt);\n\t\t\tconst emitContent = emitLines.join(\"\\n\");\n\t\t\tconst emitEndLine = chunkStartLine + splitAt - 1;\n\n\t\t\tsubChunks.push({\n\t\t\t\tfilePath: chunk.filePath,\n\t\t\t\tstartLine: chunkStartLine,\n\t\t\t\tendLine: emitEndLine,\n\t\t\t\tkind: chunk.kind,\n\t\t\t\tname: partIndex === 0 ? chunk.name : chunk.name ? `${chunk.name} (cont.)` : null,\n\t\t\t\tcontent: emitContent,\n\t\t\t\tfileType: chunk.fileType,\n\t\t\t});\n\t\t\tpartIndex++;\n\n\t\t\t// Keep remaining lines from the split\n\t\t\tconst remaining = currentLines.slice(splitAt);\n\t\t\tcurrentLines = [...remaining, line];\n\t\t\tchunkStartLine = emitEndLine + 1;\n\t\t\tcurrentSize = currentLines.join(\"\\n\").length;\n\t\t} else {\n\t\t\tcurrentLines.push(line);\n\t\t\tcurrentSize += lineSize;\n\t\t}\n\t}\n\n\t// Emit remaining lines\n\tif (currentLines.length > 0) {\n\t\tconst emitContent = currentLines.join(\"\\n\");\n\t\tsubChunks.push({\n\t\t\tfilePath: chunk.filePath,\n\t\t\tstartLine: chunkStartLine,\n\t\t\tendLine: chunk.endLine,\n\t\t\tkind: chunk.kind,\n\t\t\tname: partIndex === 0 ? chunk.name : chunk.name ? `${chunk.name} (cont.)` : null,\n\t\t\tcontent: emitContent,\n\t\t\tfileType: chunk.fileType,\n\t\t});\n\t}\n\n\treturn subChunks.length > 0 ? subChunks : [chunk];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction wholeFileChunk(content: string, filePath: string, fileType: TextFileType): Chunk {\n\tconst lineCount = content.split(\"\\n\").length;\n\treturn {\n\t\tfilePath,\n\t\tstartLine: 1,\n\t\tendLine: lineCount,\n\t\tkind: \"file\",\n\t\tname: null,\n\t\tcontent,\n\t\tfileType,\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tree-sitter-chunker.d.ts","sourceRoot":"","sources":["../src/tree-sitter-chunker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA8KvE;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBpD;AAqJD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,kBAAkB,GAC1B,OAAO,CAAC,KAAK,EAAE,CAAC,CAwElB","sourcesContent":["/**\n * AST-aware code chunking using tree-sitter (WASM).\n *\n * Parses source files into syntax trees and extracts meaningful code constructs\n * (functions, classes, methods, structs, etc.) as individual chunks. Gaps between\n * extracted nodes are captured as file-level chunks when substantial.\n */\n\nimport { readFileSync } from \"fs\";\nimport { createRequire } from \"module\";\nimport type { Node as TSNode } from \"web-tree-sitter\";\nimport type { Chunk, ChunkKind, TreeSitterLanguage } from \"./types.js\";\n\n// Use createRequire for resolving WASM paths in ESM context\nconst require = createRequire(import.meta.url);\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Describes which AST node types to extract for a language and how to get names. */\ninterface NodeExtractor {\n\t/** The tree-sitter node type string. */\n\ttype: string;\n\t/** The ChunkKind to assign to extracted chunks. */\n\tkind: ChunkKind;\n\t/** How to extract the symbol name from the node. */\n\tgetName: (node: TSNode) => string | null;\n}\n\n/** Intermediate representation of an extracted AST region. */\ninterface ExtractedRegion {\n\tname: string | null;\n\tkind: ChunkKind;\n\tstartLine: number; // 1-indexed\n\tendLine: number; // 1-indexed, inclusive\n\tcontent: string;\n}\n\n// ============================================================================\n// Lazy Imports\n// ============================================================================\n\n// web-tree-sitter types imported dynamically to avoid top-level await\ntype ParserClass = typeof import(\"web-tree-sitter\").Parser;\ntype LanguageClass = typeof import(\"web-tree-sitter\").Language;\n\nlet Parser: ParserClass | null = null;\nlet Language: LanguageClass | null = null;\n\nlet initPromise: Promise<void> | null = null;\nlet initialized = false;\n\n// ============================================================================\n// Language Cache\n// ============================================================================\n\nconst languageCache = new Map<TreeSitterLanguage, import(\"web-tree-sitter\").Language>();\n\n/** Grammar WASM paths keyed by language. */\nconst GRAMMAR_PATHS: Record<TreeSitterLanguage, string> = {\n\ttypescript: \"tree-sitter-typescript/tree-sitter-typescript.wasm\",\n\ttsx: \"tree-sitter-typescript/tree-sitter-tsx.wasm\",\n\tjavascript: \"tree-sitter-javascript/tree-sitter-javascript.wasm\",\n\tpython: \"tree-sitter-python/tree-sitter-python.wasm\",\n\tgo: \"tree-sitter-go/tree-sitter-go.wasm\",\n\trust: \"tree-sitter-rust/tree-sitter-rust.wasm\",\n\tjava: \"tree-sitter-java/tree-sitter-java.wasm\",\n\tc: \"tree-sitter-c/tree-sitter-c.wasm\",\n\tcpp: \"tree-sitter-cpp/tree-sitter-cpp.wasm\",\n};\n\n// ============================================================================\n// Name Extractors\n// ============================================================================\n\n/** Get name from a node's `name` field. */\nfunction nameField(node: TSNode): string | null {\n\treturn node.childForFieldName(\"name\")?.text ?? null;\n}\n\n/** Get name for an arrow function assigned to a variable. */\nfunction arrowFunctionName(node: TSNode): string | null {\n\tconst parent = node.parent;\n\tif (parent?.type === \"variable_declarator\") {\n\t\treturn parent.childForFieldName(\"name\")?.text ?? null;\n\t}\n\treturn null;\n}\n\n/** Get name for C function_definition: name is in the function_declarator child. */\nfunction cFunctionName(node: TSNode): string | null {\n\tconst declarator = node.childForFieldName(\"declarator\");\n\tif (!declarator) return null;\n\t// function_declarator has a `declarator` field for the actual name\n\tif (declarator.type === \"function_declarator\") {\n\t\treturn declarator.childForFieldName(\"declarator\")?.text ?? null;\n\t}\n\treturn declarator.text ?? null;\n}\n\n/** Get name from an export_statement's inner declaration. */\nfunction exportName(node: TSNode): string | null {\n\tconst decl = node.childForFieldName(\"declaration\");\n\tif (!decl) {\n\t\t// Named export like `export { foo }` — use the full text isn't useful,\n\t\t// just return null for anonymous exports\n\t\treturn null;\n\t}\n\treturn decl.childForFieldName(\"name\")?.text ?? null;\n}\n\n// ============================================================================\n// Per-Language Node Extractors\n// ============================================================================\n\nconst TS_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"function_declaration\", kind: \"function\", getName: nameField },\n\t{ type: \"method_definition\", kind: \"method\", getName: nameField },\n\t{ type: \"class_declaration\", kind: \"class\", getName: nameField },\n\t{ type: \"interface_declaration\", kind: \"interface\", getName: nameField },\n\t{ type: \"type_alias_declaration\", kind: \"type_alias\", getName: nameField },\n\t{ type: \"export_statement\", kind: \"export\", getName: exportName },\n\t{ type: \"arrow_function\", kind: \"function\", getName: arrowFunctionName },\n];\n\nconst JS_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"function_declaration\", kind: \"function\", getName: nameField },\n\t{ type: \"method_definition\", kind: \"method\", getName: nameField },\n\t{ type: \"class_declaration\", kind: \"class\", getName: nameField },\n\t{ type: \"export_statement\", kind: \"export\", getName: exportName },\n\t{ type: \"arrow_function\", kind: \"function\", getName: arrowFunctionName },\n];\n\nconst PYTHON_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"function_definition\", kind: \"function\", getName: nameField },\n\t{ type: \"class_definition\", kind: \"class\", getName: nameField },\n];\n\nconst GO_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"function_declaration\", kind: \"function\", getName: nameField },\n\t{ type: \"method_declaration\", kind: \"method\", getName: nameField },\n\t{ type: \"type_spec\", kind: \"struct\", getName: nameField },\n];\n\nconst RUST_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"function_item\", kind: \"function\", getName: nameField },\n\t{ type: \"impl_item\", kind: \"impl\", getName: (n) => n.childForFieldName(\"type\")?.text ?? null },\n\t{ type: \"struct_item\", kind: \"struct\", getName: nameField },\n\t{ type: \"enum_item\", kind: \"enum\", getName: nameField },\n\t{ type: \"trait_item\", kind: \"interface\", getName: nameField },\n];\n\nconst JAVA_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"class_declaration\", kind: \"class\", getName: nameField },\n\t{ type: \"method_declaration\", kind: \"method\", getName: nameField },\n\t{ type: \"interface_declaration\", kind: \"interface\", getName: nameField },\n];\n\nconst C_EXTRACTORS: NodeExtractor[] = [\n\t{ type: \"function_definition\", kind: \"function\", getName: cFunctionName },\n\t{ type: \"struct_specifier\", kind: \"struct\", getName: nameField },\n];\n\nconst CPP_EXTRACTORS: NodeExtractor[] = [\n\t...C_EXTRACTORS,\n\t{ type: \"class_specifier\", kind: \"class\", getName: nameField },\n];\n\nconst LANGUAGE_EXTRACTORS: Record<TreeSitterLanguage, NodeExtractor[]> = {\n\ttypescript: TS_EXTRACTORS,\n\ttsx: TS_EXTRACTORS,\n\tjavascript: JS_EXTRACTORS,\n\tpython: PYTHON_EXTRACTORS,\n\tgo: GO_EXTRACTORS,\n\trust: RUST_EXTRACTORS,\n\tjava: JAVA_EXTRACTORS,\n\tc: C_EXTRACTORS,\n\tcpp: CPP_EXTRACTORS,\n};\n\n// ============================================================================\n// Initialization\n// ============================================================================\n\n/**\n * Initialize the tree-sitter WASM runtime. Must be called before parsing.\n * Safe to call multiple times — subsequent calls are no-ops.\n */\nexport async function initTreeSitter(): Promise<void> {\n\tif (initialized) return;\n\tif (initPromise) return initPromise;\n\n\tinitPromise = (async () => {\n\t\ttry {\n\t\t\tconst mod = await import(\"web-tree-sitter\");\n\t\t\tParser = mod.Parser;\n\t\t\tLanguage = mod.Language;\n\n\t\t\tconst wasmPath = require.resolve(\"web-tree-sitter/web-tree-sitter.wasm\");\n\t\t\tconst wasmBuf = readFileSync(wasmPath);\n\t\t\tawait Parser.init({ locateFile: () => wasmPath, wasmBinary: wasmBuf });\n\t\t\tinitialized = true;\n\t\t} catch (err) {\n\t\t\t// Reset so subsequent calls can retry instead of returning\n\t\t\t// the same rejected promise forever\n\t\t\tinitPromise = null;\n\t\t\tthrow err;\n\t\t}\n\t})();\n\n\treturn initPromise;\n}\n\n// ============================================================================\n// Language Loading\n// ============================================================================\n\n/** Load and cache a tree-sitter language grammar. */\nasync function loadLanguage(lang: TreeSitterLanguage): Promise<import(\"web-tree-sitter\").Language> {\n\tconst cached = languageCache.get(lang);\n\tif (cached) return cached;\n\n\tif (!Language) {\n\t\tthrow new Error(\"tree-sitter not initialized — call initTreeSitter() first\");\n\t}\n\n\tconst grammarPath = require.resolve(GRAMMAR_PATHS[lang]);\n\tconst loaded = await Language.load(grammarPath);\n\tlanguageCache.set(lang, loaded);\n\treturn loaded;\n}\n\n// ============================================================================\n// AST Extraction\n// ============================================================================\n\n/**\n * Walk the tree and collect nodes matching the target types.\n * Returns regions sorted by start position, with nested nodes skipped\n * (only outermost matches are kept).\n */\nfunction extractRegions(rootNode: TSNode, extractors: NodeExtractor[], _sourceLines: string[]): ExtractedRegion[] {\n\t// Gather all target node types\n\tconst typeToExtractors = new Map<string, NodeExtractor>();\n\tfor (const ext of extractors) {\n\t\ttypeToExtractors.set(ext.type, ext);\n\t}\n\n\tconst targetTypes = extractors.map((e) => e.type);\n\tconst candidates = rootNode.descendantsOfType(targetTypes);\n\n\t// Convert to regions\n\tconst raw: ExtractedRegion[] = [];\n\tfor (const node of candidates) {\n\t\tconst ext = typeToExtractors.get(node.type);\n\t\tif (!ext) continue;\n\n\t\t// For struct_specifier in C, only extract if it has a body (field_declaration_list)\n\t\tif (node.type === \"struct_specifier\") {\n\t\t\tconst hasBody = node.children.some((c) => c.type === \"field_declaration_list\");\n\t\t\tif (!hasBody) continue;\n\t\t}\n\n\t\t// For arrow_function, only extract if parent is variable_declarator (named assignment)\n\t\tif (node.type === \"arrow_function\") {\n\t\t\tif (node.parent?.type !== \"variable_declarator\") continue;\n\t\t}\n\n\t\tconst startLine = node.startPosition.row + 1; // 0→1 indexed\n\t\tconst endLine = node.endPosition.row + 1;\n\n\t\traw.push({\n\t\t\tname: ext.getName(node),\n\t\t\tkind: ext.kind,\n\t\t\tstartLine,\n\t\t\tendLine,\n\t\t\tcontent: node.text,\n\t\t});\n\t}\n\n\t// Sort by start line, then by end line descending (larger ranges first)\n\traw.sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);\n\n\t// Remove nested regions — keep only outermost\n\tconst regions: ExtractedRegion[] = [];\n\tlet lastEndLine = -1;\n\n\tfor (const region of raw) {\n\t\tif (region.startLine > lastEndLine) {\n\t\t\tregions.push(region);\n\t\t\tlastEndLine = region.endLine;\n\t\t}\n\t\t// else: this region is nested inside the previous one — skip\n\t}\n\n\treturn regions;\n}\n\n// ============================================================================\n// Gap Collection\n// ============================================================================\n\n/** Minimum number of non-blank lines for a gap to become its own chunk. */\nconst MIN_GAP_LINES = 3;\n\n/**\n * Create file-level chunks for substantial code between extracted regions.\n */\nfunction collectGaps(\n\tregions: ExtractedRegion[],\n\tsourceLines: string[],\n\tfilePath: string,\n\tfileType: TreeSitterLanguage,\n): Chunk[] {\n\tconst gaps: Chunk[] = [];\n\tlet cursor = 1; // 1-indexed current line\n\n\tfor (const region of regions) {\n\t\tif (region.startLine > cursor) {\n\t\t\tconst gapLines = sourceLines.slice(cursor - 1, region.startLine - 1);\n\t\t\tconst nonBlank = gapLines.filter((l) => l.trim().length > 0).length;\n\t\t\tif (nonBlank > MIN_GAP_LINES) {\n\t\t\t\tgaps.push({\n\t\t\t\t\tfilePath,\n\t\t\t\t\tstartLine: cursor,\n\t\t\t\t\tendLine: region.startLine - 1,\n\t\t\t\t\tkind: \"file\",\n\t\t\t\t\tname: null,\n\t\t\t\t\tcontent: gapLines.join(\"\\n\"),\n\t\t\t\t\tfileType,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tcursor = region.endLine + 1;\n\t}\n\n\t// Trailing gap after last region\n\tif (cursor <= sourceLines.length) {\n\t\tconst gapLines = sourceLines.slice(cursor - 1);\n\t\tconst nonBlank = gapLines.filter((l) => l.trim().length > 0).length;\n\t\tif (nonBlank > MIN_GAP_LINES) {\n\t\t\tgaps.push({\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: cursor,\n\t\t\t\tendLine: sourceLines.length,\n\t\t\t\tkind: \"file\",\n\t\t\t\tname: null,\n\t\t\t\tcontent: gapLines.join(\"\\n\"),\n\t\t\t\tfileType,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn gaps;\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Parse a source file with tree-sitter and extract AST-aware chunks.\n *\n * Returns chunks for functions, classes, methods, and other language-specific\n * constructs, plus file-level chunks for substantial gaps between them.\n *\n * @param content - Raw source code text\n * @param filePath - Relative file path (stored in chunk metadata)\n * @param language - Tree-sitter language identifier\n */\nexport async function chunkWithTreeSitter(\n\tcontent: string,\n\tfilePath: string,\n\tlanguage: TreeSitterLanguage,\n): Promise<Chunk[]> {\n\tif (!initialized || !Parser) {\n\t\tawait initTreeSitter();\n\t}\n\n\t// After init, Parser is guaranteed to be set\n\tconst ParserCtor = Parser!;\n\tconst lang = await loadLanguage(language);\n\tconst parser = new ParserCtor();\n\tparser.setLanguage(lang);\n\n\tconst tree = parser.parse(content);\n\tif (!tree) {\n\t\t// Parse failed — free the parser WASM memory before returning\n\t\tparser.delete();\n\t\tconst lines = content.split(\"\\n\");\n\t\treturn [\n\t\t\t{\n\t\t\t\tfilePath,\n\t\t\t\tstartLine: 1,\n\t\t\t\tendLine: lines.length,\n\t\t\t\tkind: \"file\",\n\t\t\t\tname: null,\n\t\t\t\tcontent,\n\t\t\t\tfileType: language,\n\t\t\t},\n\t\t];\n\t}\n\n\ttry {\n\t\tconst sourceLines = content.split(\"\\n\");\n\t\tconst extractors = LANGUAGE_EXTRACTORS[language];\n\t\tconst regions = extractRegions(tree.rootNode, extractors, sourceLines);\n\n\t\t// Convert regions to Chunk objects\n\t\tconst chunks: Chunk[] = regions.map((r) => ({\n\t\t\tfilePath,\n\t\t\tstartLine: r.startLine,\n\t\t\tendLine: r.endLine,\n\t\t\tkind: r.kind,\n\t\t\tname: r.name,\n\t\t\tcontent: r.content,\n\t\t\tfileType: language,\n\t\t}));\n\n\t\t// Add gap chunks\n\t\tconst gaps = collectGaps(regions, sourceLines, filePath, language);\n\n\t\t// Merge and sort by start line\n\t\tconst all = [...chunks, ...gaps];\n\t\tall.sort((a, b) => a.startLine - b.startLine);\n\n\t\t// If no regions were extracted, return the whole file as one chunk\n\t\tif (chunks.length === 0) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tfilePath,\n\t\t\t\t\tstartLine: 1,\n\t\t\t\t\tendLine: sourceLines.length,\n\t\t\t\t\tkind: \"file\",\n\t\t\t\t\tname: null,\n\t\t\t\t\tcontent,\n\t\t\t\t\tfileType: language,\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn all;\n\t} finally {\n\t\ttree.delete();\n\t\tparser.delete();\n\t}\n}\n"]}
1
+ {"version":3,"file":"tree-sitter-chunker.d.ts","sourceRoot":"","sources":["../src/tree-sitter-chunker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA8KvE;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAuBpD;AAqJD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,kBAAkB,GAC1B,OAAO,CAAC,KAAK,EAAE,CAAC,CAwElB"}