kirograph 0.20.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +4 -3
  2. package/dist/bin/commands/data.js +52 -0
  3. package/dist/bin/commands/data.js.map +2 -2
  4. package/dist/bin/commands/docs.js +2 -1
  5. package/dist/bin/commands/docs.js.map +2 -2
  6. package/dist/bin/commands/gain.js +21 -1
  7. package/dist/bin/commands/gain.js.map +2 -2
  8. package/dist/bin/commands/status.js +23 -1
  9. package/dist/bin/commands/status.js.map +2 -2
  10. package/dist/bin/installer/config-prompt.js +30 -1
  11. package/dist/bin/installer/config-prompt.js.map +2 -2
  12. package/dist/bin/installer/index.js +108 -0
  13. package/dist/bin/installer/index.js.map +2 -2
  14. package/dist/bin/kirograph.js +1 -1
  15. package/dist/config.js +19 -2
  16. package/dist/config.js.map +2 -2
  17. package/dist/core/pipeline.js +3 -1
  18. package/dist/core/pipeline.js.map +2 -2
  19. package/dist/data/indexer.js +4 -4
  20. package/dist/data/indexer.js.map +2 -2
  21. package/dist/data/linker.js +9 -5
  22. package/dist/data/linker.js.map +2 -2
  23. package/dist/data/linker.test.js +60 -0
  24. package/dist/data/linker.test.js.map +7 -0
  25. package/dist/data/parsers/__fixtures__/pdf-inspector-stub.js +41 -0
  26. package/dist/data/parsers/__fixtures__/pdf-inspector-stub.js.map +7 -0
  27. package/dist/data/parsers/index.js +3 -1
  28. package/dist/data/parsers/index.js.map +2 -2
  29. package/dist/data/parsers/pdf.js +95 -0
  30. package/dist/data/parsers/pdf.js.map +7 -0
  31. package/dist/data/parsers/pdf.test.js +125 -0
  32. package/dist/data/parsers/pdf.test.js.map +7 -0
  33. package/dist/data/queries.js +39 -2
  34. package/dist/data/queries.js.map +2 -2
  35. package/dist/data/types.js.map +1 -1
  36. package/dist/db/data-schema.sql +2 -1
  37. package/dist/db/database.js +7 -0
  38. package/dist/db/database.js.map +2 -2
  39. package/dist/docs/indexer.js +2 -1
  40. package/dist/docs/indexer.js.map +2 -2
  41. package/dist/docs/queries.js +2 -1
  42. package/dist/docs/queries.js.map +2 -2
  43. package/dist/docs/vectors.js +46 -5
  44. package/dist/docs/vectors.js.map +2 -2
  45. package/dist/index.js +1 -0
  46. package/dist/index.js.map +2 -2
  47. package/dist/mcp/tools.js +1 -1
  48. package/dist/mcp/tools.js.map +2 -2
  49. package/dist/memory/index.js +2 -1
  50. package/dist/memory/index.js.map +2 -2
  51. package/dist/memory/vectors.js +52 -8
  52. package/dist/memory/vectors.js.map +3 -3
  53. package/dist/types.js.map +2 -2
  54. package/dist/vectors/index.js +66 -3
  55. package/dist/vectors/index.js.map +2 -2
  56. package/dist/vectors/turbovec-index.js +189 -0
  57. package/dist/vectors/turbovec-index.js.map +7 -0
  58. package/package.json +7 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/db/database.ts"],
4
- "sourcesContent": ["/**\n * KiroGraph Database Layer\n * Wraps node-sqlite3-wasm for portability (no native bindings needed).\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport type { Node, Edge, FileRecord, NodeKind, Language, GraphStats, NodeContext, NodeMetrics, SearchOptions } from '../types';\nimport type { ArchPackage, ArchLayer, ArchPackageDep, ArchLayerDep, ArchCoupling } from '../architecture/types';\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst { Database } = require('node-sqlite3-wasm');\n\nconst CURRENT_SCHEMA_VERSION = 1;\n\nexport class GraphDatabase {\n private db: any;\n private projectRoot: string;\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot;\n const dbDir = path.join(projectRoot, '.kirograph');\n fs.mkdirSync(dbDir, { recursive: true });\n const dbPath = path.join(dbDir, 'kirograph.db');\n\n // \u2500\u2500 Pre-flight lock check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // node-sqlite3-wasm calls process.abort() when the DB is locked, producing\n // a cryptic \"Aborted()\" with no context. Detect the lock file early and\n // throw a clean error instead so the global handler can surface it clearly.\n const lockPath = path.join(dbDir, 'kirograph.db.lock');\n if (fs.existsSync(lockPath)) {\n throw new Error(\n `Database is locked by another process (${lockPath} exists).\\n` +\n `Run: kirograph unlock\\n` +\n `Or delete the lock manually: ${lockPath}`\n );\n }\n\n this.db = new Database(dbPath);\n this.db.exec(`\n PRAGMA journal_mode=WAL;\n PRAGMA foreign_keys=ON;\n PRAGMA busy_timeout=120000;\n PRAGMA synchronous=NORMAL;\n PRAGMA cache_size=-64000;\n PRAGMA temp_store=MEMORY;\n PRAGMA mmap_size=268435456;\n `);\n this.applySchema();\n }\n\n private applySchema(): void {\n const schemaPath = path.join(__dirname, '../db/schema.sql');\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n this.runMigrations();\n }\n\n /**\n * Apply memory schema tables. Called when enableMemory is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyMemorySchema(): void {\n const schemaPath = path.join(__dirname, '../db/memory-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n }\n\n /**\n * Apply docs schema tables. Called when enableDocs is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyDocsSchema(): void {\n const schemaPath = path.join(__dirname, '../db/docs-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n }\n\n /**\n * Apply data schema tables. Called when enableData is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyDataSchema(): void {\n const schemaPath = path.join(__dirname, '../db/data-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n }\n\n /**\n * Apply patterns schema tables. Called when enablePatterns is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyPatternsSchema(): void {\n const schemaPath = path.join(__dirname, '../db/patterns-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n // Migrate existing DBs: add new columns introduced after initial release.\n const tryAlter = (stmt: string) => { try { this.db.run(stmt); } catch { /* already exists */ } };\n tryAlter('ALTER TABLE pattern_matches ADD COLUMN symbol_node_id TEXT');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_pm_symbol ON pattern_matches(symbol_node_id)');\n }\n\n /**\n * Apply security schema tables. Called when enableSecurity is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applySecuritySchema(): void {\n const schemaPath = path.join(__dirname, '../db/security-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n // Migrate existing DBs: add new columns introduced after initial release.\n const tryAlter = (stmt: string) => { try { this.db.run(stmt); } catch { /* already exists */ } };\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN license TEXT');\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN latest_version TEXT');\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN latest_published INTEGER');\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN staleness_score REAL');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN epss_score REAL');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN epss_percentile REAL');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN epss_fetched_at INTEGER');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_deps_license ON sec_dependencies(license)');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_deps_staleness ON sec_dependencies(staleness_score)');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_vulns_epss ON sec_vulnerabilities(epss_score)');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN risk_score REAL');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_vulns_risk ON sec_vulnerabilities(risk_score)');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN first_detected_at INTEGER');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN fix_available_since INTEGER');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN suppressed_at INTEGER');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN remediated_at INTEGER');\n }\n\n /**\n * Get the raw database handle (for MemoryDatabase).\n */\n getRawDb(): any {\n return this.db;\n }\n\n private runMigrations(): void {\n // Record initial schema version if not present\n const versionRow = this.db.get('SELECT version FROM schema_versions ORDER BY version DESC LIMIT 1');\n const currentVersion = versionRow ? versionRow.version : 0;\n\n if (currentVersion < CURRENT_SCHEMA_VERSION) {\n // Migration 1: schema.sql now includes all columns \u2014 nothing extra needed.\n // The schema CREATE IF NOT EXISTS statements handle initial setup.\n this.db.run('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)',\n [CURRENT_SCHEMA_VERSION, Date.now()]);\n }\n\n // Add attempted_strategies column to unresolved_refs if not present.\n // SQLite throws if the column already exists, so we catch that error.\n try {\n this.db.run('ALTER TABLE unresolved_refs ADD COLUMN attempted_strategies TEXT');\n } catch {\n // Column already exists \u2014 nothing to do.\n }\n\n // Add confidence columns to edges if not present.\n try {\n this.db.run(\"ALTER TABLE edges ADD COLUMN confidence TEXT NOT NULL DEFAULT 'extracted'\");\n } catch {\n // Column already exists \u2014 nothing to do.\n }\n try {\n this.db.run('ALTER TABLE edges ADD COLUMN confidence_score REAL NOT NULL DEFAULT 1.0');\n } catch {\n // Column already exists \u2014 nothing to do.\n }\n }\n\n // \u2500\u2500 Files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n upsertFile(record: FileRecord): void {\n this.db.run(\n `INSERT OR REPLACE INTO files (path, content_hash, language, file_size, symbol_count, indexed_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [record.path, record.contentHash, record.language, record.fileSize, record.symbolCount, record.indexedAt]\n );\n }\n\n getFile(filePath: string): FileRecord | null {\n const row = this.db.get('SELECT * FROM files WHERE path = ?', [filePath]);\n return row ? this.rowToFile(row) : null;\n }\n\n getAllFiles(): FileRecord[] {\n return this.db.all('SELECT * FROM files').map(this.rowToFile);\n }\n\n deleteFile(filePath: string): void {\n // Cascade deletes nodes (and their edges via FK)\n this.db.run('DELETE FROM nodes WHERE file_path = ?', [filePath]);\n this.db.run('DELETE FROM files WHERE path = ?', [filePath]);\n }\n\n private rowToFile(row: any): FileRecord {\n return {\n path: row.path,\n contentHash: row.content_hash,\n language: row.language as Language,\n fileSize: row.file_size,\n symbolCount: row.symbol_count,\n indexedAt: row.indexed_at,\n };\n }\n\n // \u2500\u2500 Nodes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n upsertNode(node: Node): void {\n this.db.run(\n `INSERT OR REPLACE INTO nodes\n (id, kind, name, qualified_name, file_path, language,\n start_line, end_line, start_column, end_column,\n docstring, signature, visibility,\n is_exported, is_async, is_static, is_abstract,\n decorators, type_parameters, updated_at)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n [\n node.id, node.kind, node.name, node.qualifiedName, node.filePath, node.language,\n node.startLine, node.endLine, node.startColumn, node.endColumn,\n node.docstring ?? null, node.signature ?? null, node.visibility ?? null,\n node.isExported ? 1 : 0, node.isAsync ? 1 : 0,\n node.isStatic ? 1 : 0, node.isAbstract ? 1 : 0,\n node.decorators ? JSON.stringify(node.decorators) : null,\n node.typeParameters ? JSON.stringify(node.typeParameters) : null,\n node.updatedAt,\n ]\n );\n // Keep FTS in sync\n this.db.run(\n `INSERT OR REPLACE INTO nodes_fts (id, name, qualified_name, docstring, signature)\n VALUES (?, ?, ?, ?, ?)`,\n [node.id, node.name, node.qualifiedName, node.docstring ?? '', node.signature ?? '']\n );\n }\n\n getNode(id: string): Node | null {\n const row = this.db.get('SELECT * FROM nodes WHERE id = ?', [id]);\n return row ? this.rowToNode(row) : null;\n }\n\n getNodesByFile(filePath: string): Node[] {\n return this.db.all('SELECT * FROM nodes WHERE file_path = ?', [filePath]).map(this.rowToNode);\n }\n\n getNodesByKind(kind: Node['kind']): Node[] {\n return this.db.all('SELECT * FROM nodes WHERE kind = ?', [kind]).map(this.rowToNode);\n }\n\n findNodesByExactName(name: string, kinds?: NodeKind[], limit = 20): Node[] {\n if (kinds && kinds.length > 0) {\n const placeholders = kinds.map(() => '?').join(',');\n return this.db.all(\n `SELECT * FROM nodes WHERE name = ? AND kind IN (${placeholders}) LIMIT ?`,\n [name, ...kinds, limit]\n ).map(this.rowToNode);\n }\n return this.db.all(\n 'SELECT * FROM nodes WHERE name = ? LIMIT ?',\n [name, limit]\n ).map(this.rowToNode);\n }\n\n searchNodes(query: string, opts: SearchOptions = {}): Node[] {\n const { kinds, languages, limit = 20 } = opts;\n // Sanitize for FTS5: strip special chars and append wildcard.\n // Both the MATCH value and LIMIT are inlined (not bound via ?) because\n // node-sqlite3-wasm passes bound parameters through to FTS5's own query\n // parser before SQLite substitutes them, causing \"syntax error near ?\".\n const safe = query\n .replace(/\\b(AND|OR|NOT)\\b/g, ' ') // FTS5 boolean operators\n .replace(/['\"*()?\\-+^~:{}\\\\\\.\\/,]/g, ' ') // FTS5 special chars (incl. / and ,)\n .replace(/\\s+/g, ' ')\n .trim();\n if (!safe) return [];\n\n const ftsQuery = safe + '*';\n const safeLimit = Math.max(1, Math.floor(Number(limit)));\n\n const conditions: string[] = [];\n const params: any[] = [];\n\n if (kinds && kinds.length > 0) {\n conditions.push(`kind IN (${kinds.map(() => '?').join(',')})`);\n params.push(...kinds);\n }\n if (languages && languages.length > 0) {\n conditions.push(`language IN (${languages.map(() => '?').join(',')})`);\n params.push(...languages);\n }\n\n const where = conditions.length > 0 ? `AND ${conditions.join(' AND ')}` : '';\n\n return this.db.all(\n `SELECT * FROM nodes\n WHERE id IN (SELECT rowid FROM nodes_fts WHERE nodes_fts MATCH '${ftsQuery}')\n ${where}\n LIMIT ${safeLimit}`,\n params\n ).map(this.rowToNode);\n }\n\n searchNodesByName(name: string, opts: SearchOptions = {}): Node[] {\n const { kinds, languages, limit = 20 } = opts;\n const pattern = `%${name}%`;\n const conditions: string[] = ['name LIKE ?'];\n const params: any[] = [pattern];\n\n if (kinds && kinds.length > 0) {\n conditions.push(`kind IN (${kinds.map(() => '?').join(',')})`);\n params.push(...kinds);\n }\n if (languages && languages.length > 0) {\n conditions.push(`language IN (${languages.map(() => '?').join(',')})`);\n params.push(...languages);\n }\n params.push(limit);\n\n return this.db.all(\n `SELECT * FROM nodes WHERE ${conditions.join(' AND ')} LIMIT ?`,\n params\n ).map(this.rowToNode);\n }\n\n deleteNodesByFile(filePath: string): void {\n const ids = this.db.all('SELECT id FROM nodes WHERE file_path = ?', [filePath]).map((r: any) => r.id);\n if (ids.length === 0) return;\n const placeholders = ids.map(() => '?').join(',');\n this.db.run(`DELETE FROM edges WHERE source IN (${placeholders}) OR target IN (${placeholders})`, [...ids, ...ids]);\n this.db.run(`DELETE FROM nodes_fts WHERE id IN (${placeholders})`, ids);\n this.db.run(`DELETE FROM nodes WHERE file_path = ?`, [filePath]);\n }\n\n private rowToNode(row: any): Node {\n return {\n id: row.id,\n kind: row.kind as NodeKind,\n name: row.name,\n qualifiedName: row.qualified_name,\n filePath: row.file_path,\n language: row.language as Language,\n startLine: row.start_line,\n endLine: row.end_line,\n startColumn: row.start_column,\n endColumn: row.end_column,\n docstring: row.docstring ?? undefined,\n signature: row.signature ?? undefined,\n visibility: row.visibility ?? undefined,\n isExported: row.is_exported === 1,\n isAsync: row.is_async === 1,\n isStatic: row.is_static === 1,\n isAbstract: row.is_abstract === 1,\n decorators: row.decorators ? JSON.parse(row.decorators) : undefined,\n typeParameters: row.type_parameters ? JSON.parse(row.type_parameters) : undefined,\n updatedAt: row.updated_at,\n };\n }\n\n // \u2500\u2500 Edges \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n insertEdge(edge: Edge): void {\n this.db.run(\n `INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, column, confidence, confidence_score)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [edge.source, edge.target, edge.kind, edge.metadata ? JSON.stringify(edge.metadata) : null, edge.line ?? null, edge.column ?? null, edge.confidence ?? 'extracted', edge.confidenceScore ?? 1.0]\n );\n }\n\n getCallers(nodeId: string, limit = 30): Node[] {\n return this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.source = n.id\n WHERE e.target = ? AND e.kind = 'calls'\n LIMIT ?`,\n [nodeId, limit]\n ).map(this.rowToNode);\n }\n\n getCallees(nodeId: string, limit = 30): Node[] {\n return this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.target = n.id\n WHERE e.source = ? AND e.kind = 'calls'\n LIMIT ?`,\n [nodeId, limit]\n ).map(this.rowToNode);\n }\n\n getImpactRadius(nodeId: string, depth = 2): Node[] {\n // BFS over 'calls' and 'imports' edges (dependents)\n const visited = new Set<string>([nodeId]);\n let frontier = [nodeId];\n for (let d = 0; d < depth; d++) {\n if (frontier.length === 0) break;\n const placeholders = frontier.map(() => '?').join(',');\n const rows = this.db.all(\n `SELECT DISTINCT source FROM edges WHERE target IN (${placeholders}) AND kind IN ('calls','imports')`,\n frontier\n );\n frontier = [];\n for (const row of rows) {\n if (!visited.has(row.source)) {\n visited.add(row.source);\n frontier.push(row.source);\n }\n }\n }\n visited.delete(nodeId);\n if (visited.size === 0) return [];\n const ids = [...visited];\n const placeholders = ids.map(() => '?').join(',');\n return this.db.all(`SELECT * FROM nodes WHERE id IN (${placeholders})`, ids).map(this.rowToNode);\n }\n\n getEdgesForNodes(nodeIds: string[]): Edge[] {\n if (nodeIds.length === 0) return [];\n const placeholders = nodeIds.map(() => '?').join(',');\n return this.db.all(\n `SELECT * FROM edges WHERE source IN (${placeholders}) OR target IN (${placeholders})`,\n [...nodeIds, ...nodeIds]\n ).map((row: any) => ({\n source: row.source,\n target: row.target,\n kind: row.kind,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n line: row.line ?? undefined,\n column: row.column ?? undefined,\n }));\n }\n\n /**\n * Find files that import (depend on) the given file path.\n * Used for affected-test traversal.\n */\n getDependentFiles(filePath: string): string[] {\n // Find nodes in the target file\n const targetNodes = this.db.all('SELECT id FROM nodes WHERE file_path = ?', [filePath]);\n if (targetNodes.length === 0) return [];\n const ids = targetNodes.map((r: any) => r.id);\n const placeholders = ids.map(() => '?').join(',');\n // Find source nodes that call/import these target nodes\n const rows = this.db.all(\n `SELECT DISTINCT n.file_path FROM nodes n\n JOIN edges e ON e.source = n.id\n WHERE e.target IN (${placeholders}) AND e.kind IN ('calls','imports')\n AND n.file_path != ?`,\n [...ids, filePath]\n );\n return rows.map((r: any) => r.file_path);\n }\n\n // \u2500\u2500 Unresolved References \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n insertUnresolvedRef(sourceId: string, refName: string, refKind: string, filePath: string, line?: number, column?: number): void {\n this.db.run(\n `INSERT INTO unresolved_refs (source_id, ref_name, ref_kind, file_path, line, column)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [sourceId, refName, refKind, filePath, line ?? null, column ?? null]\n );\n }\n\n deleteUnresolvedRefsByFile(filePath: string): void {\n this.db.run('DELETE FROM unresolved_refs WHERE file_path = ?', [filePath]);\n }\n\n /**\n * Resolve pending references:\n * - refKind='function': 3-strategy name matching \u2192 'calls' edge\n * - refKind='import': path-based resolution \u2192 'imports' edge\n *\n * Returns the number of edges successfully created.\n */\n resolveUnresolvedRefs(): number {\n const refs = this.db.all('SELECT * FROM unresolved_refs');\n let resolved = 0;\n\n for (const ref of refs) {\n const { id: refId, source_id: sourceId, ref_name: refName, ref_kind: refKind, file_path: filePath, line, column } = ref;\n\n if (refKind === 'function') {\n // Strategy 1: exact name match\n let target = this.db.get('SELECT id FROM nodes WHERE name = ? LIMIT 1', [refName]);\n let confidence: 'extracted' | 'inferred' | 'ambiguous' = 'inferred';\n let confidenceScore = 1.0;\n\n // Strategy 2: qualified name suffix\n if (!target) {\n target = this.db.get(\n `SELECT id FROM nodes WHERE qualified_name LIKE ? LIMIT 1`,\n [`%::${refName}`]\n );\n }\n\n // Strategy 3: case-insensitive\n if (!target) {\n target = this.db.get(\n 'SELECT id FROM nodes WHERE lower(name) = lower(?) LIMIT 1',\n [refName]\n );\n // Case-insensitive match is less certain\n if (target) confidenceScore = 0.8;\n }\n\n // Check if there are multiple candidates (ambiguous)\n if (target) {\n const candidateCount = this.db.get('SELECT COUNT(*) as cnt FROM nodes WHERE name = ?', [refName]);\n if (candidateCount && candidateCount.cnt > 1) {\n confidence = 'ambiguous';\n confidenceScore = 1.0 / candidateCount.cnt;\n }\n }\n\n if (target) {\n this.insertEdge({ source: sourceId, target: target.id, kind: 'calls', line: line ?? undefined, column: column ?? undefined, confidence, confidenceScore });\n this.db.run('DELETE FROM unresolved_refs WHERE id = ?', [refId]);\n resolved++;\n }\n } else if (refKind === 'import') {\n // Resolve import path to an indexed file\n const targetFileNode = this.resolveImportPath(refName, filePath);\n if (targetFileNode) {\n this.insertEdge({ source: sourceId, target: targetFileNode, kind: 'imports', line: line ?? undefined, column: column ?? undefined, confidence: 'inferred', confidenceScore: 1.0 });\n this.db.run('DELETE FROM unresolved_refs WHERE id = ?', [refId]);\n resolved++;\n }\n }\n }\n\n return resolved;\n }\n\n /** @deprecated Use resolveUnresolvedRefs() */\n resolveCallEdges(): number {\n return this.resolveUnresolvedRefs();\n }\n\n /**\n * Resolve a module import path to the ID of the first node in the target file.\n * Returns null if no indexed file matches.\n */\n private resolveImportPath(importPath: string, sourceFilePath: string): string | null {\n // Only resolve relative imports\n if (!importPath.startsWith('.')) return null;\n\n const sourceDir = sourceFilePath.replace(/[^/]+$/, '');\n // Normalize the relative path\n const segments = (sourceDir + importPath).split('/');\n const normalized: string[] = [];\n for (const seg of segments) {\n if (seg === '..') normalized.pop();\n else if (seg !== '.') normalized.push(seg);\n }\n const basePath = normalized.join('/');\n\n // Try exact match, then with common extensions\n const candidates = [\n basePath,\n basePath + '.ts',\n basePath + '.tsx',\n basePath + '.js',\n basePath + '.jsx',\n basePath + '/index.ts',\n basePath + '/index.tsx',\n basePath + '/index.js',\n ];\n\n for (const candidate of candidates) {\n const row = this.db.get('SELECT id FROM nodes WHERE file_path = ? LIMIT 1', [candidate]);\n if (row) return row.id;\n }\n\n return null;\n }\n\n // \u2500\u2500 Node Context & Metrics \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getNodeContext(nodeId: string): NodeContext | null {\n const node = this.getNode(nodeId);\n if (!node) return null;\n\n const ancestors = this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.target = n.id\n WHERE e.source = ? AND e.kind = 'contains'`,\n [nodeId]\n ).map(this.rowToNode);\n\n const children = this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.source = n.id\n WHERE e.target = n.id AND e.source = ? AND e.kind = 'contains'`,\n [nodeId]\n ).map(this.rowToNode);\n\n const callers = this.getCallers(nodeId, 20);\n const callees = this.getCallees(nodeId, 20);\n\n return { node, ancestors, children, callers, callees };\n }\n\n getNodeMetrics(nodeId: string): NodeMetrics {\n const incomingEdgeCount = (this.db.get('SELECT COUNT(*) as c FROM edges WHERE target = ?', [nodeId])?.c ?? 0);\n const outgoingEdgeCount = (this.db.get('SELECT COUNT(*) as c FROM edges WHERE source = ?', [nodeId])?.c ?? 0);\n const callCount = (this.db.get(`SELECT COUNT(*) as c FROM edges WHERE source = ? AND kind = 'calls'`, [nodeId])?.c ?? 0);\n const callerCount = (this.db.get(`SELECT COUNT(*) as c FROM edges WHERE target = ? AND kind = 'calls'`, [nodeId])?.c ?? 0);\n const childCount = (this.db.get(`SELECT COUNT(*) as c FROM edges WHERE source = ? AND kind = 'contains'`, [nodeId])?.c ?? 0);\n return { incomingEdgeCount, outgoingEdgeCount, callCount, callerCount, childCount };\n }\n\n // \u2500\u2500 Graph Analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Find symbols with no incoming edges (potential dead code).\n * Excludes exported symbols since they may be consumed externally.\n */\n findDeadCode(limit = 50): Node[] {\n return this.db.all(\n `SELECT * FROM nodes\n WHERE kind IN ('function','method','class')\n AND is_exported = 0\n AND id NOT IN (SELECT DISTINCT target FROM edges)\n LIMIT ?`,\n [limit]\n ).map(this.rowToNode);\n }\n\n /**\n * Find circular import dependencies using DFS over import edges.\n * Returns arrays of file paths forming cycles.\n */\n findCircularDependencies(): string[][] {\n // Build adjacency map: file \u2192 imported files\n const rows = this.db.all(\n `SELECT DISTINCT n1.file_path as src, n2.file_path as dst\n FROM edges e\n JOIN nodes n1 ON n1.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'imports' AND n1.file_path != n2.file_path`\n );\n\n const adj = new Map<string, Set<string>>();\n for (const row of rows) {\n if (!adj.has(row.src)) adj.set(row.src, new Set());\n adj.get(row.src)!.add(row.dst);\n }\n\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const pathSet = new Set<string>();\n const pathArr: string[] = [];\n\n function dfs(node: string): void {\n if (pathSet.has(node)) {\n const cycleStart = pathArr.indexOf(node);\n cycles.push(pathArr.slice(cycleStart).concat(node));\n return;\n }\n if (visited.has(node)) return;\n visited.add(node);\n pathSet.add(node);\n pathArr.push(node);\n for (const neighbor of adj.get(node) ?? []) {\n dfs(neighbor);\n }\n pathArr.pop();\n pathSet.delete(node);\n }\n\n for (const node of adj.keys()) {\n if (!visited.has(node)) dfs(node);\n }\n\n return cycles;\n }\n\n /**\n * Find the shortest path between two nodes via directed BFS (outgoing edges only).\n */\n findPath(fromId: string, toId: string, maxDepth = 10): Node[] {\n if (fromId === toId) {\n const node = this.getNode(fromId);\n return node ? [node] : [];\n }\n\n const prev = new Map<string, string>();\n const queue: string[] = [fromId];\n const visited = new Set<string>([fromId]);\n let depth = 0;\n\n outer: while (queue.length > 0 && depth < maxDepth) {\n const levelSize = queue.length;\n depth++;\n for (let i = 0; i < levelSize; i++) {\n const current = queue.shift()!;\n // Directed: only follow outgoing edges\n const rows = this.db.all(\n `SELECT DISTINCT target as next FROM edges WHERE source = ?`,\n [current]\n );\n for (const row of rows) {\n if (!visited.has(row.next)) {\n visited.add(row.next);\n prev.set(row.next, current);\n if (row.next === toId) break outer;\n queue.push(row.next);\n }\n }\n }\n }\n\n if (!prev.has(toId)) return [];\n\n // Reconstruct path\n const pathIds: string[] = [];\n let cur: string | undefined = toId;\n while (cur !== undefined) {\n pathIds.unshift(cur);\n cur = prev.get(cur);\n }\n\n const result: Node[] = [];\n for (const id of pathIds) {\n const node = this.getNode(id);\n if (node) result.push(node);\n }\n return result;\n }\n\n /**\n * Traverse type hierarchy via 'extends' and 'implements' edges.\n * direction 'up' = base types, 'down' = derived types, 'both' = all.\n */\n getTypeHierarchy(nodeId: string, direction: 'up' | 'down' | 'both' = 'both'): Node[] {\n const visited = new Set<string>([nodeId]);\n const frontier = [nodeId];\n const result: Node[] = [];\n\n while (frontier.length > 0) {\n const current = frontier.shift()!;\n let rows: any[] = [];\n\n if (direction === 'up' || direction === 'both') {\n // current extends/implements something \u2192 go up\n const up = this.db.all(\n `SELECT target as id FROM edges WHERE source = ? AND kind IN ('extends','implements')`,\n [current]\n );\n rows = rows.concat(up);\n }\n if (direction === 'down' || direction === 'both') {\n // something extends/implements current \u2192 go down\n const down = this.db.all(\n `SELECT source as id FROM edges WHERE target = ? AND kind IN ('extends','implements')`,\n [current]\n );\n rows = rows.concat(down);\n }\n\n for (const row of rows) {\n if (!visited.has(row.id)) {\n visited.add(row.id);\n frontier.push(row.id);\n const node = this.getNode(row.id);\n if (node) result.push(node);\n }\n }\n }\n\n return result;\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getEmbeddingCount(): number {\n try {\n const row = this.db.get('SELECT COUNT(*) as c FROM vectors');\n return row ? row.c : 0;\n } catch {\n return 0;\n }\n }\n\n storeEmbedding(nodeId: string, embedding: Float32Array, model: string): void {\n const buf = Buffer.from(embedding.buffer);\n this.db.run(\n `INSERT OR REPLACE INTO vectors (node_id, embedding, model, created_at) VALUES (?, ?, ?, ?)`,\n [nodeId, buf, model, Date.now()]\n );\n }\n\n getEmbeddedNodeIds(): string[] {\n return this.db.all('SELECT node_id FROM vectors').map((r: any) => r.node_id);\n }\n\n getAllEmbeddings(): Array<{ nodeId: string; embedding: Float32Array }> {\n const rows = this.db.all('SELECT node_id, embedding FROM vectors');\n return rows.map((r: any) => ({\n nodeId: r.node_id as string,\n embedding: new Float32Array(\n (r.embedding as Buffer).buffer,\n (r.embedding as Buffer).byteOffset,\n (r.embedding as Buffer).byteLength / 4\n ),\n }));\n }\n\n getAllNodes(): import('../types').Node[] {\n return this.db.all('SELECT * FROM nodes').map(this.rowToNode);\n }\n\n /**\n * Returns embeddable nodes in pages for memory-efficient streaming.\n * `kinds` filters by node kind (e.g. ['function','method','class']).\n * Returns an empty array when offset >= total count.\n */\n getEmbeddableNodesPaged(kinds: string[], limit: number, offset: number): import('../types').Node[] {\n if (kinds.length === 0) return [];\n const placeholders = kinds.map(() => '?').join(',');\n return this.db.all(\n `SELECT * FROM nodes WHERE kind IN (${placeholders}) LIMIT ? OFFSET ?`,\n [...kinds, limit, offset]\n ).map(this.rowToNode);\n }\n\n /** Count of nodes whose kind is in the provided list. */\n countEmbeddableNodes(kinds: string[]): number {\n if (kinds.length === 0) return 0;\n const placeholders = kinds.map(() => '?').join(',');\n const row = this.db.get(`SELECT COUNT(*) as c FROM nodes WHERE kind IN (${placeholders})`, kinds);\n return row?.c ?? 0;\n }\n\n getAllEdges(): Edge[] {\n return this.db.all('SELECT source, target, kind FROM edges').map((row: any) => ({\n source: row.source,\n target: row.target,\n kind: row.kind,\n }));\n }\n\n /**\n * Find the top-N most-connected nodes by total edge degree (in + out).\n * Excludes 'contains' edges (structural nesting, not semantic connections).\n */\n findHotspots(limit = 20): Array<Node & { degree: number; inDegree: number; outDegree: number }> {\n const rows = this.db.all(\n `SELECT n.*,\n (SELECT COUNT(*) FROM edges WHERE target = n.id AND kind != 'contains') AS in_degree,\n (SELECT COUNT(*) FROM edges WHERE source = n.id AND kind != 'contains') AS out_degree,\n (SELECT COUNT(*) FROM edges WHERE (source = n.id OR target = n.id) AND kind != 'contains') AS degree\n FROM nodes n\n ORDER BY degree DESC\n LIMIT ?`,\n [limit]\n );\n return rows.map((row: any) => ({\n ...this.rowToNode(row),\n degree: row.degree as number,\n inDegree: row.in_degree as number,\n outDegree: row.out_degree as number,\n }));\n }\n\n /**\n * Find surprising cross-file connections: direct edges between nodes in\n * structurally distant files. Scored by path distance \u00D7 edge-kind weight.\n */\n findSurprisingConnections(limit = 20): Array<{ source: Node; target: Node; kind: string; score: number }> {\n const KIND_WEIGHT: Record<string, number> = {\n calls: 1.0, references: 0.8, type_of: 0.7, returns: 0.6,\n decorates: 0.6, extends: 0.5, implements: 0.5,\n };\n\n const rows: Array<{ source: string; target: string; kind: string; source_file: string; target_file: string }> =\n this.db.all(\n `SELECT e.source, e.target, e.kind,\n ns.file_path AS source_file, nt.file_path AS target_file\n FROM edges e\n JOIN nodes ns ON ns.id = e.source\n JOIN nodes nt ON nt.id = e.target\n WHERE ns.file_path != nt.file_path\n AND e.kind NOT IN ('contains', 'import', 'imports')\n LIMIT 5000`\n );\n\n // Score: path distance \u00D7 kind weight\n const scored = rows.map(row => {\n const srcDirs = row.source_file.split('/').slice(0, -1);\n const tgtDirs = row.target_file.split('/').slice(0, -1);\n let common = 0;\n for (let i = 0; i < Math.min(srcDirs.length, tgtDirs.length); i++) {\n if (srcDirs[i] === tgtDirs[i]) common++;\n else break;\n }\n const maxDirs = Math.max(srcDirs.length, tgtDirs.length, 1);\n const pathSimilarity = common / maxDirs;\n const kindWeight = KIND_WEIGHT[row.kind] ?? 0.4;\n return { ...row, score: (1 - pathSimilarity) * kindWeight };\n });\n\n // Sort desc, deduplicate by source+target+kind\n scored.sort((a, b) => b.score - a.score);\n const seen = new Set<string>();\n const top: typeof scored = [];\n for (const row of scored) {\n const key = `${row.source}|${row.target}|${row.kind}`;\n if (!seen.has(key)) {\n seen.add(key);\n top.push(row);\n if (top.length >= limit) break;\n }\n }\n\n const result: Array<{ source: Node; target: Node; kind: string; score: number }> = [];\n for (const row of top) {\n const src = this.getNode(row.source);\n const tgt = this.getNode(row.target);\n if (src && tgt) result.push({ source: src, target: tgt, kind: row.kind, score: row.score });\n }\n return result;\n }\n\n getStats(): GraphStats {\n const files = this.db.get('SELECT COUNT(*) as c FROM files').c;\n const nodes = this.db.get('SELECT COUNT(*) as c FROM nodes').c;\n const edges = this.db.get('SELECT COUNT(*) as c FROM edges').c;\n const kindRows = this.db.all('SELECT kind, COUNT(*) as c FROM nodes GROUP BY kind');\n const nodesByKind: Record<string, number> = {};\n for (const row of kindRows) nodesByKind[row.kind] = row.c;\n const langRows = this.db.all('SELECT language, COUNT(*) as c FROM files GROUP BY language');\n const filesByLanguage: Record<string, number> = {};\n for (const row of langRows) filesByLanguage[row.language] = row.c;\n const dbPath = path.join(this.projectRoot, '.kirograph', 'kirograph.db');\n let dbSizeBytes = 0;\n try { dbSizeBytes = fs.statSync(dbPath).size; } catch { /* ignore */ }\n const embeddingCount = this.getEmbeddingCount();\n return { files, nodes, edges, nodesByKind, filesByLanguage, dbSizeBytes, embeddingCount, embeddingsEnabled: false, embeddingModel: '', useVecIndex: false, semanticEngine: 'cosine' as const, vecIndexCount: 0, engineFallback: null, embeddableNodeCount: 0, frameworks: [], architectureEnabled: false };\n }\n\n // \u2500\u2500 Architecture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n clearArchitecture(): void {\n this.db.run('DELETE FROM arch_coupling');\n this.db.run('DELETE FROM arch_layer_deps');\n this.db.run('DELETE FROM arch_package_deps');\n this.db.run('DELETE FROM arch_file_layers');\n this.db.run('DELETE FROM arch_file_packages');\n this.db.run('DELETE FROM arch_layers');\n this.db.run('DELETE FROM arch_packages');\n }\n\n upsertArchPackage(pkg: ArchPackage): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_packages\n (id, name, path, source, language, manifest_path, version, external_deps, metadata, updated_at)\n VALUES (?,?,?,?,?,?,?,?,?,?)`,\n [\n pkg.id, pkg.name, pkg.path, pkg.source,\n pkg.language ?? null, pkg.manifestPath ?? null, pkg.version ?? null,\n pkg.externalDeps ? JSON.stringify(pkg.externalDeps) : null,\n pkg.metadata ? JSON.stringify(pkg.metadata) : null,\n pkg.updatedAt,\n ]\n );\n }\n\n upsertArchLayer(layer: ArchLayer): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_layers (id, name, source, patterns, metadata, updated_at)\n VALUES (?,?,?,?,?,?)`,\n [\n layer.id, layer.name, layer.source,\n JSON.stringify(layer.patterns),\n layer.metadata ? JSON.stringify(layer.metadata) : null,\n layer.updatedAt,\n ]\n );\n }\n\n upsertArchFilePackage(filePath: string, packageId: string): void {\n this.db.run(\n 'INSERT OR IGNORE INTO arch_file_packages (file_path, package_id) VALUES (?,?)',\n [filePath, packageId]\n );\n }\n\n upsertArchFileLayer(filePath: string, layerId: string, confidence: number, matchedPattern: string): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_file_layers (file_path, layer_id, confidence, matched_pattern)\n VALUES (?,?,?,?)`,\n [filePath, layerId, confidence, matchedPattern]\n );\n }\n\n upsertArchPackageDep(sourcePkg: string, targetPkg: string, depCount: number, files?: Array<{ from: string; to: string }>): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_package_deps (source_pkg, target_pkg, dep_count, files)\n VALUES (?,?,?,?)`,\n [sourcePkg, targetPkg, depCount, files ? JSON.stringify(files) : null]\n );\n }\n\n upsertArchLayerDep(sourceLayer: string, targetLayer: string, depCount: number): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_layer_deps (source_layer, target_layer, dep_count)\n VALUES (?,?,?)`,\n [sourceLayer, targetLayer, depCount]\n );\n }\n\n upsertArchCoupling(coupling: ArchCoupling): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_coupling (package_id, afferent, efferent, instability, updated_at)\n VALUES (?,?,?,?,?)`,\n [coupling.packageId, coupling.afferent, coupling.efferent, coupling.instability, coupling.updatedAt]\n );\n }\n\n getArchPackages(): ArchPackage[] {\n return this.db.all('SELECT * FROM arch_packages').map((r: any): ArchPackage => ({\n id: r.id,\n name: r.name,\n path: r.path,\n source: r.source,\n language: r.language ?? undefined,\n manifestPath: r.manifest_path ?? undefined,\n version: r.version ?? undefined,\n externalDeps: r.external_deps ? JSON.parse(r.external_deps) : undefined,\n metadata: r.metadata ? JSON.parse(r.metadata) : undefined,\n updatedAt: r.updated_at,\n }));\n }\n\n getArchLayers(): ArchLayer[] {\n return this.db.all('SELECT * FROM arch_layers').map((r: any): ArchLayer => ({\n id: r.id,\n name: r.name,\n source: r.source,\n patterns: JSON.parse(r.patterns),\n metadata: r.metadata ? JSON.parse(r.metadata) : undefined,\n updatedAt: r.updated_at,\n }));\n }\n\n getArchPackageDeps(): ArchPackageDep[] {\n return this.db.all('SELECT * FROM arch_package_deps').map((r: any): ArchPackageDep => ({\n sourcePkg: r.source_pkg,\n targetPkg: r.target_pkg,\n depCount: r.dep_count,\n files: r.files ? JSON.parse(r.files) : undefined,\n }));\n }\n\n getArchLayerDeps(): ArchLayerDep[] {\n return this.db.all('SELECT * FROM arch_layer_deps').map((r: any): ArchLayerDep => ({\n sourceLayer: r.source_layer,\n targetLayer: r.target_layer,\n depCount: r.dep_count,\n }));\n }\n\n getArchCoupling(): ArchCoupling[] {\n return this.db.all('SELECT * FROM arch_coupling').map((r: any): ArchCoupling => ({\n packageId: r.package_id,\n afferent: r.afferent,\n efferent: r.efferent,\n instability: r.instability,\n updatedAt: r.updated_at,\n }));\n }\n\n getArchStats(): { packages: number; layers: number; packageDeps: number } {\n return {\n packages: this.db.get('SELECT COUNT(*) as c FROM arch_packages')?.c ?? 0,\n layers: this.db.get('SELECT COUNT(*) as c FROM arch_layers')?.c ?? 0,\n packageDeps: this.db.get('SELECT COUNT(*) as c FROM arch_package_deps')?.c ?? 0,\n };\n }\n\n /**\n * Return file-to-file import relationships by joining edges + nodes.\n * Used by ArchitectureAnalyzer to roll up package-level dependencies.\n */\n getFileImportPairs(): Array<{ sourceFile: string; targetFile: string }> {\n return this.db.all(\n `SELECT DISTINCT n1.file_path as source_file, n2.file_path as target_file\n FROM edges e\n JOIN nodes n1 ON n1.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'imports' AND n1.file_path != n2.file_path`\n ).map((r: any) => ({ sourceFile: r.source_file, targetFile: r.target_file }));\n }\n\n getArchFilePackages(): Array<{ filePath: string; packageId: string }> {\n return this.db.all('SELECT file_path, package_id FROM arch_file_packages')\n .map((r: any) => ({ filePath: r.file_path, packageId: r.package_id }));\n }\n\n getArchFileLayers(): Array<{ filePath: string; layerId: string; confidence: number; matchedPattern: string }> {\n return this.db.all('SELECT file_path, layer_id, confidence, matched_pattern FROM arch_file_layers')\n .map((r: any) => ({\n filePath: r.file_path,\n layerId: r.layer_id,\n confidence: r.confidence,\n matchedPattern: r.matched_pattern,\n }));\n }\n\n getArchitectureResult(): {\n packages: ArchPackage[];\n layers: ArchLayer[];\n packageDeps: ArchPackageDep[];\n layerDeps: ArchLayerDep[];\n coupling: ArchCoupling[];\n } {\n return {\n packages: this.getArchPackages(),\n layers: this.getArchLayers(),\n packageDeps: this.getArchPackageDeps(),\n layerDeps: this.getArchLayerDeps(),\n coupling: this.getArchCoupling(),\n };\n }\n\n // \u2500\u2500 Transactions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n transaction<T>(fn: () => T): T {\n this.db.run('BEGIN');\n try {\n const result = fn();\n this.db.run('COMMIT');\n return result;\n } catch (err) {\n this.db.run('ROLLBACK');\n throw err;\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * KiroGraph Database Layer\n * Wraps node-sqlite3-wasm for portability (no native bindings needed).\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport type { Node, Edge, FileRecord, NodeKind, Language, GraphStats, NodeContext, NodeMetrics, SearchOptions } from '../types';\nimport type { ArchPackage, ArchLayer, ArchPackageDep, ArchLayerDep, ArchCoupling } from '../architecture/types';\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst { Database } = require('node-sqlite3-wasm');\n\nconst CURRENT_SCHEMA_VERSION = 1;\n\nexport class GraphDatabase {\n private db: any;\n private projectRoot: string;\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot;\n const dbDir = path.join(projectRoot, '.kirograph');\n fs.mkdirSync(dbDir, { recursive: true });\n const dbPath = path.join(dbDir, 'kirograph.db');\n\n // \u2500\u2500 Pre-flight lock check \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // node-sqlite3-wasm calls process.abort() when the DB is locked, producing\n // a cryptic \"Aborted()\" with no context. Detect the lock file early and\n // throw a clean error instead so the global handler can surface it clearly.\n const lockPath = path.join(dbDir, 'kirograph.db.lock');\n if (fs.existsSync(lockPath)) {\n throw new Error(\n `Database is locked by another process (${lockPath} exists).\\n` +\n `Run: kirograph unlock\\n` +\n `Or delete the lock manually: ${lockPath}`\n );\n }\n\n this.db = new Database(dbPath);\n this.db.exec(`\n PRAGMA journal_mode=WAL;\n PRAGMA foreign_keys=ON;\n PRAGMA busy_timeout=120000;\n PRAGMA synchronous=NORMAL;\n PRAGMA cache_size=-64000;\n PRAGMA temp_store=MEMORY;\n PRAGMA mmap_size=268435456;\n `);\n this.applySchema();\n }\n\n private applySchema(): void {\n const schemaPath = path.join(__dirname, '../db/schema.sql');\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n this.runMigrations();\n }\n\n /**\n * Apply memory schema tables. Called when enableMemory is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyMemorySchema(): void {\n const schemaPath = path.join(__dirname, '../db/memory-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n }\n\n /**\n * Apply docs schema tables. Called when enableDocs is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyDocsSchema(): void {\n const schemaPath = path.join(__dirname, '../db/docs-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n }\n\n /**\n * Apply data schema tables. Called when enableData is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyDataSchema(): void {\n const schemaPath = path.join(__dirname, '../db/data-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n // Migrate existing DBs: add columns introduced after initial release.\n const tryAlter = (stmt: string) => { try { this.db.run(stmt); } catch { /* already exists */ } };\n tryAlter('ALTER TABLE data_datasets ADD COLUMN metadata_json TEXT');\n }\n\n /**\n * Apply patterns schema tables. Called when enablePatterns is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applyPatternsSchema(): void {\n const schemaPath = path.join(__dirname, '../db/patterns-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n // Migrate existing DBs: add new columns introduced after initial release.\n const tryAlter = (stmt: string) => { try { this.db.run(stmt); } catch { /* already exists */ } };\n tryAlter('ALTER TABLE pattern_matches ADD COLUMN symbol_node_id TEXT');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_pm_symbol ON pattern_matches(symbol_node_id)');\n }\n\n /**\n * Apply security schema tables. Called when enableSecurity is true.\n * Safe to call multiple times (CREATE IF NOT EXISTS).\n */\n applySecuritySchema(): void {\n const schemaPath = path.join(__dirname, '../db/security-schema.sql');\n if (fs.existsSync(schemaPath)) {\n const sql = fs.readFileSync(schemaPath, 'utf8');\n this.db.exec(sql);\n }\n // Migrate existing DBs: add new columns introduced after initial release.\n const tryAlter = (stmt: string) => { try { this.db.run(stmt); } catch { /* already exists */ } };\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN license TEXT');\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN latest_version TEXT');\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN latest_published INTEGER');\n tryAlter('ALTER TABLE sec_dependencies ADD COLUMN staleness_score REAL');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN epss_score REAL');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN epss_percentile REAL');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN epss_fetched_at INTEGER');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_deps_license ON sec_dependencies(license)');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_deps_staleness ON sec_dependencies(staleness_score)');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_vulns_epss ON sec_vulnerabilities(epss_score)');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN risk_score REAL');\n tryAlter('CREATE INDEX IF NOT EXISTS idx_sec_vulns_risk ON sec_vulnerabilities(risk_score)');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN first_detected_at INTEGER');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN fix_available_since INTEGER');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN suppressed_at INTEGER');\n tryAlter('ALTER TABLE sec_vulnerabilities ADD COLUMN remediated_at INTEGER');\n }\n\n /**\n * Get the raw database handle (for MemoryDatabase).\n */\n getRawDb(): any {\n return this.db;\n }\n\n private runMigrations(): void {\n // Record initial schema version if not present\n const versionRow = this.db.get('SELECT version FROM schema_versions ORDER BY version DESC LIMIT 1');\n const currentVersion = versionRow ? versionRow.version : 0;\n\n if (currentVersion < CURRENT_SCHEMA_VERSION) {\n // Migration 1: schema.sql now includes all columns \u2014 nothing extra needed.\n // The schema CREATE IF NOT EXISTS statements handle initial setup.\n this.db.run('INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)',\n [CURRENT_SCHEMA_VERSION, Date.now()]);\n }\n\n // Add attempted_strategies column to unresolved_refs if not present.\n // SQLite throws if the column already exists, so we catch that error.\n try {\n this.db.run('ALTER TABLE unresolved_refs ADD COLUMN attempted_strategies TEXT');\n } catch {\n // Column already exists \u2014 nothing to do.\n }\n\n // Add confidence columns to edges if not present.\n try {\n this.db.run(\"ALTER TABLE edges ADD COLUMN confidence TEXT NOT NULL DEFAULT 'extracted'\");\n } catch {\n // Column already exists \u2014 nothing to do.\n }\n try {\n this.db.run('ALTER TABLE edges ADD COLUMN confidence_score REAL NOT NULL DEFAULT 1.0');\n } catch {\n // Column already exists \u2014 nothing to do.\n }\n }\n\n // \u2500\u2500 Files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n upsertFile(record: FileRecord): void {\n this.db.run(\n `INSERT OR REPLACE INTO files (path, content_hash, language, file_size, symbol_count, indexed_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [record.path, record.contentHash, record.language, record.fileSize, record.symbolCount, record.indexedAt]\n );\n }\n\n getFile(filePath: string): FileRecord | null {\n const row = this.db.get('SELECT * FROM files WHERE path = ?', [filePath]);\n return row ? this.rowToFile(row) : null;\n }\n\n getAllFiles(): FileRecord[] {\n return this.db.all('SELECT * FROM files').map(this.rowToFile);\n }\n\n deleteFile(filePath: string): void {\n // Cascade deletes nodes (and their edges via FK)\n this.db.run('DELETE FROM nodes WHERE file_path = ?', [filePath]);\n this.db.run('DELETE FROM files WHERE path = ?', [filePath]);\n }\n\n private rowToFile(row: any): FileRecord {\n return {\n path: row.path,\n contentHash: row.content_hash,\n language: row.language as Language,\n fileSize: row.file_size,\n symbolCount: row.symbol_count,\n indexedAt: row.indexed_at,\n };\n }\n\n // \u2500\u2500 Nodes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n upsertNode(node: Node): void {\n this.db.run(\n `INSERT OR REPLACE INTO nodes\n (id, kind, name, qualified_name, file_path, language,\n start_line, end_line, start_column, end_column,\n docstring, signature, visibility,\n is_exported, is_async, is_static, is_abstract,\n decorators, type_parameters, updated_at)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n [\n node.id, node.kind, node.name, node.qualifiedName, node.filePath, node.language,\n node.startLine, node.endLine, node.startColumn, node.endColumn,\n node.docstring ?? null, node.signature ?? null, node.visibility ?? null,\n node.isExported ? 1 : 0, node.isAsync ? 1 : 0,\n node.isStatic ? 1 : 0, node.isAbstract ? 1 : 0,\n node.decorators ? JSON.stringify(node.decorators) : null,\n node.typeParameters ? JSON.stringify(node.typeParameters) : null,\n node.updatedAt,\n ]\n );\n // Keep FTS in sync\n this.db.run(\n `INSERT OR REPLACE INTO nodes_fts (id, name, qualified_name, docstring, signature)\n VALUES (?, ?, ?, ?, ?)`,\n [node.id, node.name, node.qualifiedName, node.docstring ?? '', node.signature ?? '']\n );\n }\n\n getNode(id: string): Node | null {\n const row = this.db.get('SELECT * FROM nodes WHERE id = ?', [id]);\n return row ? this.rowToNode(row) : null;\n }\n\n getNodesByFile(filePath: string): Node[] {\n return this.db.all('SELECT * FROM nodes WHERE file_path = ?', [filePath]).map(this.rowToNode);\n }\n\n getNodesByKind(kind: Node['kind']): Node[] {\n return this.db.all('SELECT * FROM nodes WHERE kind = ?', [kind]).map(this.rowToNode);\n }\n\n findNodesByExactName(name: string, kinds?: NodeKind[], limit = 20): Node[] {\n if (kinds && kinds.length > 0) {\n const placeholders = kinds.map(() => '?').join(',');\n return this.db.all(\n `SELECT * FROM nodes WHERE name = ? AND kind IN (${placeholders}) LIMIT ?`,\n [name, ...kinds, limit]\n ).map(this.rowToNode);\n }\n return this.db.all(\n 'SELECT * FROM nodes WHERE name = ? LIMIT ?',\n [name, limit]\n ).map(this.rowToNode);\n }\n\n searchNodes(query: string, opts: SearchOptions = {}): Node[] {\n const { kinds, languages, limit = 20 } = opts;\n // Sanitize for FTS5: strip special chars and append wildcard.\n // Both the MATCH value and LIMIT are inlined (not bound via ?) because\n // node-sqlite3-wasm passes bound parameters through to FTS5's own query\n // parser before SQLite substitutes them, causing \"syntax error near ?\".\n const safe = query\n .replace(/\\b(AND|OR|NOT)\\b/g, ' ') // FTS5 boolean operators\n .replace(/['\"*()?\\-+^~:{}\\\\\\.\\/,]/g, ' ') // FTS5 special chars (incl. / and ,)\n .replace(/\\s+/g, ' ')\n .trim();\n if (!safe) return [];\n\n const ftsQuery = safe + '*';\n const safeLimit = Math.max(1, Math.floor(Number(limit)));\n\n const conditions: string[] = [];\n const params: any[] = [];\n\n if (kinds && kinds.length > 0) {\n conditions.push(`kind IN (${kinds.map(() => '?').join(',')})`);\n params.push(...kinds);\n }\n if (languages && languages.length > 0) {\n conditions.push(`language IN (${languages.map(() => '?').join(',')})`);\n params.push(...languages);\n }\n\n const where = conditions.length > 0 ? `AND ${conditions.join(' AND ')}` : '';\n\n return this.db.all(\n `SELECT * FROM nodes\n WHERE id IN (SELECT rowid FROM nodes_fts WHERE nodes_fts MATCH '${ftsQuery}')\n ${where}\n LIMIT ${safeLimit}`,\n params\n ).map(this.rowToNode);\n }\n\n searchNodesByName(name: string, opts: SearchOptions = {}): Node[] {\n const { kinds, languages, limit = 20 } = opts;\n const pattern = `%${name}%`;\n const conditions: string[] = ['name LIKE ?'];\n const params: any[] = [pattern];\n\n if (kinds && kinds.length > 0) {\n conditions.push(`kind IN (${kinds.map(() => '?').join(',')})`);\n params.push(...kinds);\n }\n if (languages && languages.length > 0) {\n conditions.push(`language IN (${languages.map(() => '?').join(',')})`);\n params.push(...languages);\n }\n params.push(limit);\n\n return this.db.all(\n `SELECT * FROM nodes WHERE ${conditions.join(' AND ')} LIMIT ?`,\n params\n ).map(this.rowToNode);\n }\n\n deleteNodesByFile(filePath: string): void {\n const ids = this.db.all('SELECT id FROM nodes WHERE file_path = ?', [filePath]).map((r: any) => r.id);\n if (ids.length === 0) return;\n const placeholders = ids.map(() => '?').join(',');\n this.db.run(`DELETE FROM edges WHERE source IN (${placeholders}) OR target IN (${placeholders})`, [...ids, ...ids]);\n this.db.run(`DELETE FROM nodes_fts WHERE id IN (${placeholders})`, ids);\n this.db.run(`DELETE FROM nodes WHERE file_path = ?`, [filePath]);\n }\n\n private rowToNode(row: any): Node {\n return {\n id: row.id,\n kind: row.kind as NodeKind,\n name: row.name,\n qualifiedName: row.qualified_name,\n filePath: row.file_path,\n language: row.language as Language,\n startLine: row.start_line,\n endLine: row.end_line,\n startColumn: row.start_column,\n endColumn: row.end_column,\n docstring: row.docstring ?? undefined,\n signature: row.signature ?? undefined,\n visibility: row.visibility ?? undefined,\n isExported: row.is_exported === 1,\n isAsync: row.is_async === 1,\n isStatic: row.is_static === 1,\n isAbstract: row.is_abstract === 1,\n decorators: row.decorators ? JSON.parse(row.decorators) : undefined,\n typeParameters: row.type_parameters ? JSON.parse(row.type_parameters) : undefined,\n updatedAt: row.updated_at,\n };\n }\n\n // \u2500\u2500 Edges \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n insertEdge(edge: Edge): void {\n this.db.run(\n `INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, column, confidence, confidence_score)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [edge.source, edge.target, edge.kind, edge.metadata ? JSON.stringify(edge.metadata) : null, edge.line ?? null, edge.column ?? null, edge.confidence ?? 'extracted', edge.confidenceScore ?? 1.0]\n );\n }\n\n getCallers(nodeId: string, limit = 30): Node[] {\n return this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.source = n.id\n WHERE e.target = ? AND e.kind = 'calls'\n LIMIT ?`,\n [nodeId, limit]\n ).map(this.rowToNode);\n }\n\n getCallees(nodeId: string, limit = 30): Node[] {\n return this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.target = n.id\n WHERE e.source = ? AND e.kind = 'calls'\n LIMIT ?`,\n [nodeId, limit]\n ).map(this.rowToNode);\n }\n\n getImpactRadius(nodeId: string, depth = 2): Node[] {\n // BFS over 'calls' and 'imports' edges (dependents)\n const visited = new Set<string>([nodeId]);\n let frontier = [nodeId];\n for (let d = 0; d < depth; d++) {\n if (frontier.length === 0) break;\n const placeholders = frontier.map(() => '?').join(',');\n const rows = this.db.all(\n `SELECT DISTINCT source FROM edges WHERE target IN (${placeholders}) AND kind IN ('calls','imports')`,\n frontier\n );\n frontier = [];\n for (const row of rows) {\n if (!visited.has(row.source)) {\n visited.add(row.source);\n frontier.push(row.source);\n }\n }\n }\n visited.delete(nodeId);\n if (visited.size === 0) return [];\n const ids = [...visited];\n const placeholders = ids.map(() => '?').join(',');\n return this.db.all(`SELECT * FROM nodes WHERE id IN (${placeholders})`, ids).map(this.rowToNode);\n }\n\n getEdgesForNodes(nodeIds: string[]): Edge[] {\n if (nodeIds.length === 0) return [];\n const placeholders = nodeIds.map(() => '?').join(',');\n return this.db.all(\n `SELECT * FROM edges WHERE source IN (${placeholders}) OR target IN (${placeholders})`,\n [...nodeIds, ...nodeIds]\n ).map((row: any) => ({\n source: row.source,\n target: row.target,\n kind: row.kind,\n metadata: row.metadata ? JSON.parse(row.metadata) : undefined,\n line: row.line ?? undefined,\n column: row.column ?? undefined,\n }));\n }\n\n /**\n * Find files that import (depend on) the given file path.\n * Used for affected-test traversal.\n */\n getDependentFiles(filePath: string): string[] {\n // Find nodes in the target file\n const targetNodes = this.db.all('SELECT id FROM nodes WHERE file_path = ?', [filePath]);\n if (targetNodes.length === 0) return [];\n const ids = targetNodes.map((r: any) => r.id);\n const placeholders = ids.map(() => '?').join(',');\n // Find source nodes that call/import these target nodes\n const rows = this.db.all(\n `SELECT DISTINCT n.file_path FROM nodes n\n JOIN edges e ON e.source = n.id\n WHERE e.target IN (${placeholders}) AND e.kind IN ('calls','imports')\n AND n.file_path != ?`,\n [...ids, filePath]\n );\n return rows.map((r: any) => r.file_path);\n }\n\n // \u2500\u2500 Unresolved References \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n insertUnresolvedRef(sourceId: string, refName: string, refKind: string, filePath: string, line?: number, column?: number): void {\n this.db.run(\n `INSERT INTO unresolved_refs (source_id, ref_name, ref_kind, file_path, line, column)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [sourceId, refName, refKind, filePath, line ?? null, column ?? null]\n );\n }\n\n deleteUnresolvedRefsByFile(filePath: string): void {\n this.db.run('DELETE FROM unresolved_refs WHERE file_path = ?', [filePath]);\n }\n\n /**\n * Resolve pending references:\n * - refKind='function': 3-strategy name matching \u2192 'calls' edge\n * - refKind='import': path-based resolution \u2192 'imports' edge\n *\n * Returns the number of edges successfully created.\n */\n resolveUnresolvedRefs(): number {\n const refs = this.db.all('SELECT * FROM unresolved_refs');\n let resolved = 0;\n\n for (const ref of refs) {\n const { id: refId, source_id: sourceId, ref_name: refName, ref_kind: refKind, file_path: filePath, line, column } = ref;\n\n if (refKind === 'function') {\n // Strategy 1: exact name match\n let target = this.db.get('SELECT id FROM nodes WHERE name = ? LIMIT 1', [refName]);\n let confidence: 'extracted' | 'inferred' | 'ambiguous' = 'inferred';\n let confidenceScore = 1.0;\n\n // Strategy 2: qualified name suffix\n if (!target) {\n target = this.db.get(\n `SELECT id FROM nodes WHERE qualified_name LIKE ? LIMIT 1`,\n [`%::${refName}`]\n );\n }\n\n // Strategy 3: case-insensitive\n if (!target) {\n target = this.db.get(\n 'SELECT id FROM nodes WHERE lower(name) = lower(?) LIMIT 1',\n [refName]\n );\n // Case-insensitive match is less certain\n if (target) confidenceScore = 0.8;\n }\n\n // Check if there are multiple candidates (ambiguous)\n if (target) {\n const candidateCount = this.db.get('SELECT COUNT(*) as cnt FROM nodes WHERE name = ?', [refName]);\n if (candidateCount && candidateCount.cnt > 1) {\n confidence = 'ambiguous';\n confidenceScore = 1.0 / candidateCount.cnt;\n }\n }\n\n if (target) {\n this.insertEdge({ source: sourceId, target: target.id, kind: 'calls', line: line ?? undefined, column: column ?? undefined, confidence, confidenceScore });\n this.db.run('DELETE FROM unresolved_refs WHERE id = ?', [refId]);\n resolved++;\n }\n } else if (refKind === 'import') {\n // Resolve import path to an indexed file\n const targetFileNode = this.resolveImportPath(refName, filePath);\n if (targetFileNode) {\n this.insertEdge({ source: sourceId, target: targetFileNode, kind: 'imports', line: line ?? undefined, column: column ?? undefined, confidence: 'inferred', confidenceScore: 1.0 });\n this.db.run('DELETE FROM unresolved_refs WHERE id = ?', [refId]);\n resolved++;\n }\n }\n }\n\n return resolved;\n }\n\n /** @deprecated Use resolveUnresolvedRefs() */\n resolveCallEdges(): number {\n return this.resolveUnresolvedRefs();\n }\n\n /**\n * Resolve a module import path to the ID of the first node in the target file.\n * Returns null if no indexed file matches.\n */\n private resolveImportPath(importPath: string, sourceFilePath: string): string | null {\n // Only resolve relative imports\n if (!importPath.startsWith('.')) return null;\n\n const sourceDir = sourceFilePath.replace(/[^/]+$/, '');\n // Normalize the relative path\n const segments = (sourceDir + importPath).split('/');\n const normalized: string[] = [];\n for (const seg of segments) {\n if (seg === '..') normalized.pop();\n else if (seg !== '.') normalized.push(seg);\n }\n const basePath = normalized.join('/');\n\n // Try exact match, then with common extensions\n const candidates = [\n basePath,\n basePath + '.ts',\n basePath + '.tsx',\n basePath + '.js',\n basePath + '.jsx',\n basePath + '/index.ts',\n basePath + '/index.tsx',\n basePath + '/index.js',\n ];\n\n for (const candidate of candidates) {\n const row = this.db.get('SELECT id FROM nodes WHERE file_path = ? LIMIT 1', [candidate]);\n if (row) return row.id;\n }\n\n return null;\n }\n\n // \u2500\u2500 Node Context & Metrics \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getNodeContext(nodeId: string): NodeContext | null {\n const node = this.getNode(nodeId);\n if (!node) return null;\n\n const ancestors = this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.target = n.id\n WHERE e.source = ? AND e.kind = 'contains'`,\n [nodeId]\n ).map(this.rowToNode);\n\n const children = this.db.all(\n `SELECT n.* FROM nodes n\n JOIN edges e ON e.source = n.id\n WHERE e.target = n.id AND e.source = ? AND e.kind = 'contains'`,\n [nodeId]\n ).map(this.rowToNode);\n\n const callers = this.getCallers(nodeId, 20);\n const callees = this.getCallees(nodeId, 20);\n\n return { node, ancestors, children, callers, callees };\n }\n\n getNodeMetrics(nodeId: string): NodeMetrics {\n const incomingEdgeCount = (this.db.get('SELECT COUNT(*) as c FROM edges WHERE target = ?', [nodeId])?.c ?? 0);\n const outgoingEdgeCount = (this.db.get('SELECT COUNT(*) as c FROM edges WHERE source = ?', [nodeId])?.c ?? 0);\n const callCount = (this.db.get(`SELECT COUNT(*) as c FROM edges WHERE source = ? AND kind = 'calls'`, [nodeId])?.c ?? 0);\n const callerCount = (this.db.get(`SELECT COUNT(*) as c FROM edges WHERE target = ? AND kind = 'calls'`, [nodeId])?.c ?? 0);\n const childCount = (this.db.get(`SELECT COUNT(*) as c FROM edges WHERE source = ? AND kind = 'contains'`, [nodeId])?.c ?? 0);\n return { incomingEdgeCount, outgoingEdgeCount, callCount, callerCount, childCount };\n }\n\n // \u2500\u2500 Graph Analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Find symbols with no incoming edges (potential dead code).\n * Excludes exported symbols since they may be consumed externally.\n */\n findDeadCode(limit = 50): Node[] {\n return this.db.all(\n `SELECT * FROM nodes\n WHERE kind IN ('function','method','class')\n AND is_exported = 0\n AND id NOT IN (SELECT DISTINCT target FROM edges)\n LIMIT ?`,\n [limit]\n ).map(this.rowToNode);\n }\n\n /**\n * Find circular import dependencies using DFS over import edges.\n * Returns arrays of file paths forming cycles.\n */\n findCircularDependencies(): string[][] {\n // Build adjacency map: file \u2192 imported files\n const rows = this.db.all(\n `SELECT DISTINCT n1.file_path as src, n2.file_path as dst\n FROM edges e\n JOIN nodes n1 ON n1.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'imports' AND n1.file_path != n2.file_path`\n );\n\n const adj = new Map<string, Set<string>>();\n for (const row of rows) {\n if (!adj.has(row.src)) adj.set(row.src, new Set());\n adj.get(row.src)!.add(row.dst);\n }\n\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const pathSet = new Set<string>();\n const pathArr: string[] = [];\n\n function dfs(node: string): void {\n if (pathSet.has(node)) {\n const cycleStart = pathArr.indexOf(node);\n cycles.push(pathArr.slice(cycleStart).concat(node));\n return;\n }\n if (visited.has(node)) return;\n visited.add(node);\n pathSet.add(node);\n pathArr.push(node);\n for (const neighbor of adj.get(node) ?? []) {\n dfs(neighbor);\n }\n pathArr.pop();\n pathSet.delete(node);\n }\n\n for (const node of adj.keys()) {\n if (!visited.has(node)) dfs(node);\n }\n\n return cycles;\n }\n\n /**\n * Find the shortest path between two nodes via directed BFS (outgoing edges only).\n */\n findPath(fromId: string, toId: string, maxDepth = 10): Node[] {\n if (fromId === toId) {\n const node = this.getNode(fromId);\n return node ? [node] : [];\n }\n\n const prev = new Map<string, string>();\n const queue: string[] = [fromId];\n const visited = new Set<string>([fromId]);\n let depth = 0;\n\n outer: while (queue.length > 0 && depth < maxDepth) {\n const levelSize = queue.length;\n depth++;\n for (let i = 0; i < levelSize; i++) {\n const current = queue.shift()!;\n // Directed: only follow outgoing edges\n const rows = this.db.all(\n `SELECT DISTINCT target as next FROM edges WHERE source = ?`,\n [current]\n );\n for (const row of rows) {\n if (!visited.has(row.next)) {\n visited.add(row.next);\n prev.set(row.next, current);\n if (row.next === toId) break outer;\n queue.push(row.next);\n }\n }\n }\n }\n\n if (!prev.has(toId)) return [];\n\n // Reconstruct path\n const pathIds: string[] = [];\n let cur: string | undefined = toId;\n while (cur !== undefined) {\n pathIds.unshift(cur);\n cur = prev.get(cur);\n }\n\n const result: Node[] = [];\n for (const id of pathIds) {\n const node = this.getNode(id);\n if (node) result.push(node);\n }\n return result;\n }\n\n /**\n * Traverse type hierarchy via 'extends' and 'implements' edges.\n * direction 'up' = base types, 'down' = derived types, 'both' = all.\n */\n getTypeHierarchy(nodeId: string, direction: 'up' | 'down' | 'both' = 'both'): Node[] {\n const visited = new Set<string>([nodeId]);\n const frontier = [nodeId];\n const result: Node[] = [];\n\n while (frontier.length > 0) {\n const current = frontier.shift()!;\n let rows: any[] = [];\n\n if (direction === 'up' || direction === 'both') {\n // current extends/implements something \u2192 go up\n const up = this.db.all(\n `SELECT target as id FROM edges WHERE source = ? AND kind IN ('extends','implements')`,\n [current]\n );\n rows = rows.concat(up);\n }\n if (direction === 'down' || direction === 'both') {\n // something extends/implements current \u2192 go down\n const down = this.db.all(\n `SELECT source as id FROM edges WHERE target = ? AND kind IN ('extends','implements')`,\n [current]\n );\n rows = rows.concat(down);\n }\n\n for (const row of rows) {\n if (!visited.has(row.id)) {\n visited.add(row.id);\n frontier.push(row.id);\n const node = this.getNode(row.id);\n if (node) result.push(node);\n }\n }\n }\n\n return result;\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getEmbeddingCount(): number {\n try {\n const row = this.db.get('SELECT COUNT(*) as c FROM vectors');\n return row ? row.c : 0;\n } catch {\n return 0;\n }\n }\n\n storeEmbedding(nodeId: string, embedding: Float32Array, model: string): void {\n const buf = Buffer.from(embedding.buffer);\n this.db.run(\n `INSERT OR REPLACE INTO vectors (node_id, embedding, model, created_at) VALUES (?, ?, ?, ?)`,\n [nodeId, buf, model, Date.now()]\n );\n }\n\n getEmbeddedNodeIds(): string[] {\n return this.db.all('SELECT node_id FROM vectors').map((r: any) => r.node_id);\n }\n\n getAllEmbeddings(): Array<{ nodeId: string; embedding: Float32Array }> {\n const rows = this.db.all('SELECT node_id, embedding FROM vectors');\n return rows.map((r: any) => ({\n nodeId: r.node_id as string,\n embedding: new Float32Array(\n (r.embedding as Buffer).buffer,\n (r.embedding as Buffer).byteOffset,\n (r.embedding as Buffer).byteLength / 4\n ),\n }));\n }\n\n getAllNodes(): import('../types').Node[] {\n return this.db.all('SELECT * FROM nodes').map(this.rowToNode);\n }\n\n /**\n * Returns embeddable nodes in pages for memory-efficient streaming.\n * `kinds` filters by node kind (e.g. ['function','method','class']).\n * Returns an empty array when offset >= total count.\n */\n getEmbeddableNodesPaged(kinds: string[], limit: number, offset: number): import('../types').Node[] {\n if (kinds.length === 0) return [];\n const placeholders = kinds.map(() => '?').join(',');\n return this.db.all(\n `SELECT * FROM nodes WHERE kind IN (${placeholders}) LIMIT ? OFFSET ?`,\n [...kinds, limit, offset]\n ).map(this.rowToNode);\n }\n\n /** Count of nodes whose kind is in the provided list. */\n countEmbeddableNodes(kinds: string[]): number {\n if (kinds.length === 0) return 0;\n const placeholders = kinds.map(() => '?').join(',');\n const row = this.db.get(`SELECT COUNT(*) as c FROM nodes WHERE kind IN (${placeholders})`, kinds);\n return row?.c ?? 0;\n }\n\n getAllEdges(): Edge[] {\n return this.db.all('SELECT source, target, kind FROM edges').map((row: any) => ({\n source: row.source,\n target: row.target,\n kind: row.kind,\n }));\n }\n\n /**\n * Find the top-N most-connected nodes by total edge degree (in + out).\n * Excludes 'contains' edges (structural nesting, not semantic connections).\n */\n findHotspots(limit = 20): Array<Node & { degree: number; inDegree: number; outDegree: number }> {\n const rows = this.db.all(\n `SELECT n.*,\n (SELECT COUNT(*) FROM edges WHERE target = n.id AND kind != 'contains') AS in_degree,\n (SELECT COUNT(*) FROM edges WHERE source = n.id AND kind != 'contains') AS out_degree,\n (SELECT COUNT(*) FROM edges WHERE (source = n.id OR target = n.id) AND kind != 'contains') AS degree\n FROM nodes n\n ORDER BY degree DESC\n LIMIT ?`,\n [limit]\n );\n return rows.map((row: any) => ({\n ...this.rowToNode(row),\n degree: row.degree as number,\n inDegree: row.in_degree as number,\n outDegree: row.out_degree as number,\n }));\n }\n\n /**\n * Find surprising cross-file connections: direct edges between nodes in\n * structurally distant files. Scored by path distance \u00D7 edge-kind weight.\n */\n findSurprisingConnections(limit = 20): Array<{ source: Node; target: Node; kind: string; score: number }> {\n const KIND_WEIGHT: Record<string, number> = {\n calls: 1.0, references: 0.8, type_of: 0.7, returns: 0.6,\n decorates: 0.6, extends: 0.5, implements: 0.5,\n };\n\n const rows: Array<{ source: string; target: string; kind: string; source_file: string; target_file: string }> =\n this.db.all(\n `SELECT e.source, e.target, e.kind,\n ns.file_path AS source_file, nt.file_path AS target_file\n FROM edges e\n JOIN nodes ns ON ns.id = e.source\n JOIN nodes nt ON nt.id = e.target\n WHERE ns.file_path != nt.file_path\n AND e.kind NOT IN ('contains', 'import', 'imports')\n LIMIT 5000`\n );\n\n // Score: path distance \u00D7 kind weight\n const scored = rows.map(row => {\n const srcDirs = row.source_file.split('/').slice(0, -1);\n const tgtDirs = row.target_file.split('/').slice(0, -1);\n let common = 0;\n for (let i = 0; i < Math.min(srcDirs.length, tgtDirs.length); i++) {\n if (srcDirs[i] === tgtDirs[i]) common++;\n else break;\n }\n const maxDirs = Math.max(srcDirs.length, tgtDirs.length, 1);\n const pathSimilarity = common / maxDirs;\n const kindWeight = KIND_WEIGHT[row.kind] ?? 0.4;\n return { ...row, score: (1 - pathSimilarity) * kindWeight };\n });\n\n // Sort desc, deduplicate by source+target+kind\n scored.sort((a, b) => b.score - a.score);\n const seen = new Set<string>();\n const top: typeof scored = [];\n for (const row of scored) {\n const key = `${row.source}|${row.target}|${row.kind}`;\n if (!seen.has(key)) {\n seen.add(key);\n top.push(row);\n if (top.length >= limit) break;\n }\n }\n\n const result: Array<{ source: Node; target: Node; kind: string; score: number }> = [];\n for (const row of top) {\n const src = this.getNode(row.source);\n const tgt = this.getNode(row.target);\n if (src && tgt) result.push({ source: src, target: tgt, kind: row.kind, score: row.score });\n }\n return result;\n }\n\n getStats(): GraphStats {\n const files = this.db.get('SELECT COUNT(*) as c FROM files').c;\n const nodes = this.db.get('SELECT COUNT(*) as c FROM nodes').c;\n const edges = this.db.get('SELECT COUNT(*) as c FROM edges').c;\n const kindRows = this.db.all('SELECT kind, COUNT(*) as c FROM nodes GROUP BY kind');\n const nodesByKind: Record<string, number> = {};\n for (const row of kindRows) nodesByKind[row.kind] = row.c;\n const langRows = this.db.all('SELECT language, COUNT(*) as c FROM files GROUP BY language');\n const filesByLanguage: Record<string, number> = {};\n for (const row of langRows) filesByLanguage[row.language] = row.c;\n const dbPath = path.join(this.projectRoot, '.kirograph', 'kirograph.db');\n let dbSizeBytes = 0;\n try { dbSizeBytes = fs.statSync(dbPath).size; } catch { /* ignore */ }\n const embeddingCount = this.getEmbeddingCount();\n return { files, nodes, edges, nodesByKind, filesByLanguage, dbSizeBytes, embeddingCount, embeddingsEnabled: false, embeddingModel: '', useVecIndex: false, semanticEngine: 'cosine' as const, vecIndexCount: 0, engineFallback: null, embeddableNodeCount: 0, frameworks: [], architectureEnabled: false };\n }\n\n // \u2500\u2500 Architecture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n clearArchitecture(): void {\n this.db.run('DELETE FROM arch_coupling');\n this.db.run('DELETE FROM arch_layer_deps');\n this.db.run('DELETE FROM arch_package_deps');\n this.db.run('DELETE FROM arch_file_layers');\n this.db.run('DELETE FROM arch_file_packages');\n this.db.run('DELETE FROM arch_layers');\n this.db.run('DELETE FROM arch_packages');\n }\n\n upsertArchPackage(pkg: ArchPackage): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_packages\n (id, name, path, source, language, manifest_path, version, external_deps, metadata, updated_at)\n VALUES (?,?,?,?,?,?,?,?,?,?)`,\n [\n pkg.id, pkg.name, pkg.path, pkg.source,\n pkg.language ?? null, pkg.manifestPath ?? null, pkg.version ?? null,\n pkg.externalDeps ? JSON.stringify(pkg.externalDeps) : null,\n pkg.metadata ? JSON.stringify(pkg.metadata) : null,\n pkg.updatedAt,\n ]\n );\n }\n\n upsertArchLayer(layer: ArchLayer): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_layers (id, name, source, patterns, metadata, updated_at)\n VALUES (?,?,?,?,?,?)`,\n [\n layer.id, layer.name, layer.source,\n JSON.stringify(layer.patterns),\n layer.metadata ? JSON.stringify(layer.metadata) : null,\n layer.updatedAt,\n ]\n );\n }\n\n upsertArchFilePackage(filePath: string, packageId: string): void {\n this.db.run(\n 'INSERT OR IGNORE INTO arch_file_packages (file_path, package_id) VALUES (?,?)',\n [filePath, packageId]\n );\n }\n\n upsertArchFileLayer(filePath: string, layerId: string, confidence: number, matchedPattern: string): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_file_layers (file_path, layer_id, confidence, matched_pattern)\n VALUES (?,?,?,?)`,\n [filePath, layerId, confidence, matchedPattern]\n );\n }\n\n upsertArchPackageDep(sourcePkg: string, targetPkg: string, depCount: number, files?: Array<{ from: string; to: string }>): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_package_deps (source_pkg, target_pkg, dep_count, files)\n VALUES (?,?,?,?)`,\n [sourcePkg, targetPkg, depCount, files ? JSON.stringify(files) : null]\n );\n }\n\n upsertArchLayerDep(sourceLayer: string, targetLayer: string, depCount: number): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_layer_deps (source_layer, target_layer, dep_count)\n VALUES (?,?,?)`,\n [sourceLayer, targetLayer, depCount]\n );\n }\n\n upsertArchCoupling(coupling: ArchCoupling): void {\n this.db.run(\n `INSERT OR REPLACE INTO arch_coupling (package_id, afferent, efferent, instability, updated_at)\n VALUES (?,?,?,?,?)`,\n [coupling.packageId, coupling.afferent, coupling.efferent, coupling.instability, coupling.updatedAt]\n );\n }\n\n getArchPackages(): ArchPackage[] {\n return this.db.all('SELECT * FROM arch_packages').map((r: any): ArchPackage => ({\n id: r.id,\n name: r.name,\n path: r.path,\n source: r.source,\n language: r.language ?? undefined,\n manifestPath: r.manifest_path ?? undefined,\n version: r.version ?? undefined,\n externalDeps: r.external_deps ? JSON.parse(r.external_deps) : undefined,\n metadata: r.metadata ? JSON.parse(r.metadata) : undefined,\n updatedAt: r.updated_at,\n }));\n }\n\n getArchLayers(): ArchLayer[] {\n return this.db.all('SELECT * FROM arch_layers').map((r: any): ArchLayer => ({\n id: r.id,\n name: r.name,\n source: r.source,\n patterns: JSON.parse(r.patterns),\n metadata: r.metadata ? JSON.parse(r.metadata) : undefined,\n updatedAt: r.updated_at,\n }));\n }\n\n getArchPackageDeps(): ArchPackageDep[] {\n return this.db.all('SELECT * FROM arch_package_deps').map((r: any): ArchPackageDep => ({\n sourcePkg: r.source_pkg,\n targetPkg: r.target_pkg,\n depCount: r.dep_count,\n files: r.files ? JSON.parse(r.files) : undefined,\n }));\n }\n\n getArchLayerDeps(): ArchLayerDep[] {\n return this.db.all('SELECT * FROM arch_layer_deps').map((r: any): ArchLayerDep => ({\n sourceLayer: r.source_layer,\n targetLayer: r.target_layer,\n depCount: r.dep_count,\n }));\n }\n\n getArchCoupling(): ArchCoupling[] {\n return this.db.all('SELECT * FROM arch_coupling').map((r: any): ArchCoupling => ({\n packageId: r.package_id,\n afferent: r.afferent,\n efferent: r.efferent,\n instability: r.instability,\n updatedAt: r.updated_at,\n }));\n }\n\n getArchStats(): { packages: number; layers: number; packageDeps: number } {\n return {\n packages: this.db.get('SELECT COUNT(*) as c FROM arch_packages')?.c ?? 0,\n layers: this.db.get('SELECT COUNT(*) as c FROM arch_layers')?.c ?? 0,\n packageDeps: this.db.get('SELECT COUNT(*) as c FROM arch_package_deps')?.c ?? 0,\n };\n }\n\n /**\n * Return file-to-file import relationships by joining edges + nodes.\n * Used by ArchitectureAnalyzer to roll up package-level dependencies.\n */\n getFileImportPairs(): Array<{ sourceFile: string; targetFile: string }> {\n return this.db.all(\n `SELECT DISTINCT n1.file_path as source_file, n2.file_path as target_file\n FROM edges e\n JOIN nodes n1 ON n1.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'imports' AND n1.file_path != n2.file_path`\n ).map((r: any) => ({ sourceFile: r.source_file, targetFile: r.target_file }));\n }\n\n getArchFilePackages(): Array<{ filePath: string; packageId: string }> {\n return this.db.all('SELECT file_path, package_id FROM arch_file_packages')\n .map((r: any) => ({ filePath: r.file_path, packageId: r.package_id }));\n }\n\n getArchFileLayers(): Array<{ filePath: string; layerId: string; confidence: number; matchedPattern: string }> {\n return this.db.all('SELECT file_path, layer_id, confidence, matched_pattern FROM arch_file_layers')\n .map((r: any) => ({\n filePath: r.file_path,\n layerId: r.layer_id,\n confidence: r.confidence,\n matchedPattern: r.matched_pattern,\n }));\n }\n\n getArchitectureResult(): {\n packages: ArchPackage[];\n layers: ArchLayer[];\n packageDeps: ArchPackageDep[];\n layerDeps: ArchLayerDep[];\n coupling: ArchCoupling[];\n } {\n return {\n packages: this.getArchPackages(),\n layers: this.getArchLayers(),\n packageDeps: this.getArchPackageDeps(),\n layerDeps: this.getArchLayerDeps(),\n coupling: this.getArchCoupling(),\n };\n }\n\n // \u2500\u2500 Transactions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n transaction<T>(fn: () => T): T {\n this.db.run('BEGIN');\n try {\n const result = fn();\n this.db.run('COMMIT');\n return result;\n } catch (err) {\n this.db.run('ROLLBACK');\n throw err;\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,WAAsB;AACtB,SAAoB;AAKpB,MAAM,EAAE,SAAS,IAAI,QAAQ,mBAAmB;AAEhD,MAAM,yBAAyB;AAExB,MAAM,cAAc;AAAA,EAIzB,YAAY,aAAqB;AAC/B,SAAK,cAAc;AACnB,UAAM,QAAQ,KAAK,KAAK,aAAa,YAAY;AACjD,OAAG,UAAU,OAAO,EAAE,WAAW,KAAK,CAAC;AACvC,UAAM,SAAS,KAAK,KAAK,OAAO,cAAc;AAM9C,UAAM,WAAW,KAAK,KAAK,OAAO,mBAAmB;AACrD,QAAI,GAAG,WAAW,QAAQ,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,0CAA0C,QAAQ;AAAA;AAAA,+BAElB,QAAQ;AAAA,MAC1C;AAAA,IACF;AAEA,SAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAQZ;AACD,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,cAAoB;AAC1B,UAAM,aAAa,KAAK,KAAK,WAAW,kBAAkB;AAC1D,UAAM,MAAM,GAAG,aAAa,YAAY,MAAM;AAC9C,SAAK,GAAG,KAAK,GAAG;AAChB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA0B;AACxB,UAAM,aAAa,KAAK,KAAK,WAAW,yBAAyB;AACjE,QAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,YAAM,MAAM,GAAG,aAAa,YAAY,MAAM;AAC9C,WAAK,GAAG,KAAK,GAAG;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAwB;AACtB,UAAM,aAAa,KAAK,KAAK,WAAW,uBAAuB;AAC/D,QAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,YAAM,MAAM,GAAG,aAAa,YAAY,MAAM;AAC9C,WAAK,GAAG,KAAK,GAAG;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAwB;AACtB,UAAM,aAAa,KAAK,KAAK,WAAW,uBAAuB;AAC/D,QAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,YAAM,MAAM,GAAG,aAAa,YAAY,MAAM;AAC9C,WAAK,GAAG,KAAK,GAAG;AAAA,IAClB;AAEA,UAAM,WAAW,CAAC,SAAiB;AAAE,UAAI;AAAE,aAAK,GAAG,IAAI,IAAI;AAAA,MAAG,QAAQ;AAAA,MAAuB;AAAA,IAAE;AAC/F,aAAS,yDAAyD;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAA4B;AAC1B,UAAM,aAAa,KAAK,KAAK,WAAW,2BAA2B;AACnE,QAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,YAAM,MAAM,GAAG,aAAa,YAAY,MAAM;AAC9C,WAAK,GAAG,KAAK,GAAG;AAAA,IAClB;AAEA,UAAM,WAAW,CAAC,SAAiB;AAAE,UAAI;AAAE,aAAK,GAAG,IAAI,IAAI;AAAA,MAAG,QAAQ;AAAA,MAAuB;AAAA,IAAE;AAC/F,aAAS,4DAA4D;AACrE,aAAS,6EAA6E;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAA4B;AAC1B,UAAM,aAAa,KAAK,KAAK,WAAW,2BAA2B;AACnE,QAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,YAAM,MAAM,GAAG,aAAa,YAAY,MAAM;AAC9C,WAAK,GAAG,KAAK,GAAG;AAAA,IAClB;AAEA,UAAM,WAAW,CAAC,SAAiB;AAAE,UAAI;AAAE,aAAK,GAAG,IAAI,IAAI;AAAA,MAAG,QAAQ;AAAA,MAAuB;AAAA,IAAE;AAC/F,aAAS,sDAAsD;AAC/D,aAAS,6DAA6D;AACtE,aAAS,kEAAkE;AAC3E,aAAS,8DAA8D;AACvE,aAAS,4DAA4D;AACrE,aAAS,iEAAiE;AAC1E,aAAS,oEAAoE;AAC7E,aAAS,8EAA8E;AACvF,aAAS,wFAAwF;AACjG,aAAS,kFAAkF;AAC3F,aAAS,4DAA4D;AACrE,aAAS,kFAAkF;AAC3F,aAAS,sEAAsE;AAC/E,aAAS,wEAAwE;AACjF,aAAS,kEAAkE;AAC3E,aAAS,kEAAkE;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA,EAKA,WAAgB;AACd,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,gBAAsB;AAE5B,UAAM,aAAa,KAAK,GAAG,IAAI,mEAAmE;AAClG,UAAM,iBAAiB,aAAa,WAAW,UAAU;AAEzD,QAAI,iBAAiB,wBAAwB;AAG3C,WAAK,GAAG;AAAA,QAAI;AAAA,QACV,CAAC,wBAAwB,KAAK,IAAI,CAAC;AAAA,MAAC;AAAA,IACxC;AAIA,QAAI;AACF,WAAK,GAAG,IAAI,kEAAkE;AAAA,IAChF,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,WAAK,GAAG,IAAI,2EAA2E;AAAA,IACzF,QAAQ;AAAA,IAER;AACA,QAAI;AACF,WAAK,GAAG,IAAI,yEAAyE;AAAA,IACvF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAIA,WAAW,QAA0B;AACnC,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,OAAO,MAAM,OAAO,aAAa,OAAO,UAAU,OAAO,UAAU,OAAO,aAAa,OAAO,SAAS;AAAA,IAC1G;AAAA,EACF;AAAA,EAEA,QAAQ,UAAqC;AAC3C,UAAM,MAAM,KAAK,GAAG,IAAI,sCAAsC,CAAC,QAAQ,CAAC;AACxE,WAAO,MAAM,KAAK,UAAU,GAAG,IAAI;AAAA,EACrC;AAAA,EAEA,cAA4B;AAC1B,WAAO,KAAK,GAAG,IAAI,qBAAqB,EAAE,IAAI,KAAK,SAAS;AAAA,EAC9D;AAAA,EAEA,WAAW,UAAwB;AAEjC,SAAK,GAAG,IAAI,yCAAyC,CAAC,QAAQ,CAAC;AAC/D,SAAK,GAAG,IAAI,oCAAoC,CAAC,QAAQ,CAAC;AAAA,EAC5D;AAAA,EAEQ,UAAU,KAAsB;AACtC,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,UAAU,IAAI;AAAA,MACd,UAAU,IAAI;AAAA,MACd,aAAa,IAAI;AAAA,MACjB,WAAW,IAAI;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAIA,WAAW,MAAkB;AAC3B,SAAK,GAAG;AAAA,MACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA;AAAA,QACE,KAAK;AAAA,QAAI,KAAK;AAAA,QAAM,KAAK;AAAA,QAAM,KAAK;AAAA,QAAe,KAAK;AAAA,QAAU,KAAK;AAAA,QACvE,KAAK;AAAA,QAAW,KAAK;AAAA,QAAS,KAAK;AAAA,QAAa,KAAK;AAAA,QACrD,KAAK,aAAa;AAAA,QAAM,KAAK,aAAa;AAAA,QAAM,KAAK,cAAc;AAAA,QACnE,KAAK,aAAa,IAAI;AAAA,QAAG,KAAK,UAAU,IAAI;AAAA,QAC5C,KAAK,WAAW,IAAI;AAAA,QAAG,KAAK,aAAa,IAAI;AAAA,QAC7C,KAAK,aAAa,KAAK,UAAU,KAAK,UAAU,IAAI;AAAA,QACpD,KAAK,iBAAiB,KAAK,UAAU,KAAK,cAAc,IAAI;AAAA,QAC5D,KAAK;AAAA,MACP;AAAA,IACF;AAEA,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,KAAK,IAAI,KAAK,MAAM,KAAK,eAAe,KAAK,aAAa,IAAI,KAAK,aAAa,EAAE;AAAA,IACrF;AAAA,EACF;AAAA,EAEA,QAAQ,IAAyB;AAC/B,UAAM,MAAM,KAAK,GAAG,IAAI,oCAAoC,CAAC,EAAE,CAAC;AAChE,WAAO,MAAM,KAAK,UAAU,GAAG,IAAI;AAAA,EACrC;AAAA,EAEA,eAAe,UAA0B;AACvC,WAAO,KAAK,GAAG,IAAI,2CAA2C,CAAC,QAAQ,CAAC,EAAE,IAAI,KAAK,SAAS;AAAA,EAC9F;AAAA,EAEA,eAAe,MAA4B;AACzC,WAAO,KAAK,GAAG,IAAI,sCAAsC,CAAC,IAAI,CAAC,EAAE,IAAI,KAAK,SAAS;AAAA,EACrF;AAAA,EAEA,qBAAqB,MAAc,OAAoB,QAAQ,IAAY;AACzE,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,YAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,aAAO,KAAK,GAAG;AAAA,QACb,mDAAmD,YAAY;AAAA,QAC/D,CAAC,MAAM,GAAG,OAAO,KAAK;AAAA,MACxB,EAAE,IAAI,KAAK,SAAS;AAAA,IACtB;AACA,WAAO,KAAK,GAAG;AAAA,MACb;AAAA,MACA,CAAC,MAAM,KAAK;AAAA,IACd,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,YAAY,OAAe,OAAsB,CAAC,GAAW;AAC3D,UAAM,EAAE,OAAO,WAAW,QAAQ,GAAG,IAAI;AAKzC,UAAM,OAAO,MACV,QAAQ,qBAAqB,GAAG,EAChC,QAAQ,4BAA4B,GAAG,EACvC,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACR,QAAI,CAAC,KAAM,QAAO,CAAC;AAEnB,UAAM,WAAW,OAAO;AACxB,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,KAAK,CAAC,CAAC;AAEvD,UAAM,aAAuB,CAAC;AAC9B,UAAM,SAAgB,CAAC;AAEvB,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,iBAAW,KAAK,YAAY,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC,GAAG;AAC7D,aAAO,KAAK,GAAG,KAAK;AAAA,IACtB;AACA,QAAI,aAAa,UAAU,SAAS,GAAG;AACrC,iBAAW,KAAK,gBAAgB,UAAU,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC,GAAG;AACrE,aAAO,KAAK,GAAG,SAAS;AAAA,IAC1B;AAEA,UAAM,QAAQ,WAAW,SAAS,IAAI,OAAO,WAAW,KAAK,OAAO,CAAC,KAAK;AAE1E,WAAO,KAAK,GAAG;AAAA,MACb;AAAA,yEACmE,QAAQ;AAAA,SACxE,KAAK;AAAA,eACC,SAAS;AAAA,MAClB;AAAA,IACF,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,kBAAkB,MAAc,OAAsB,CAAC,GAAW;AAChE,UAAM,EAAE,OAAO,WAAW,QAAQ,GAAG,IAAI;AACzC,UAAM,UAAU,IAAI,IAAI;AACxB,UAAM,aAAuB,CAAC,aAAa;AAC3C,UAAM,SAAgB,CAAC,OAAO;AAE9B,QAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,iBAAW,KAAK,YAAY,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC,GAAG;AAC7D,aAAO,KAAK,GAAG,KAAK;AAAA,IACtB;AACA,QAAI,aAAa,UAAU,SAAS,GAAG;AACrC,iBAAW,KAAK,gBAAgB,UAAU,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC,GAAG;AACrE,aAAO,KAAK,GAAG,SAAS;AAAA,IAC1B;AACA,WAAO,KAAK,KAAK;AAEjB,WAAO,KAAK,GAAG;AAAA,MACb,6BAA6B,WAAW,KAAK,OAAO,CAAC;AAAA,MACrD;AAAA,IACF,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,kBAAkB,UAAwB;AACxC,UAAM,MAAM,KAAK,GAAG,IAAI,4CAA4C,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,MAAW,EAAE,EAAE;AACpG,QAAI,IAAI,WAAW,EAAG;AACtB,UAAM,eAAe,IAAI,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAChD,SAAK,GAAG,IAAI,sCAAsC,YAAY,mBAAmB,YAAY,KAAK,CAAC,GAAG,KAAK,GAAG,GAAG,CAAC;AAClH,SAAK,GAAG,IAAI,sCAAsC,YAAY,KAAK,GAAG;AACtE,SAAK,GAAG,IAAI,yCAAyC,CAAC,QAAQ,CAAC;AAAA,EACjE;AAAA,EAEQ,UAAU,KAAgB;AAChC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV,eAAe,IAAI;AAAA,MACnB,UAAU,IAAI;AAAA,MACd,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,aAAa,IAAI;AAAA,MACjB,WAAW,IAAI;AAAA,MACf,WAAW,IAAI,aAAa;AAAA,MAC5B,WAAW,IAAI,aAAa;AAAA,MAC5B,YAAY,IAAI,cAAc;AAAA,MAC9B,YAAY,IAAI,gBAAgB;AAAA,MAChC,SAAS,IAAI,aAAa;AAAA,MAC1B,UAAU,IAAI,cAAc;AAAA,MAC5B,YAAY,IAAI,gBAAgB;AAAA,MAChC,YAAY,IAAI,aAAa,KAAK,MAAM,IAAI,UAAU,IAAI;AAAA,MAC1D,gBAAgB,IAAI,kBAAkB,KAAK,MAAM,IAAI,eAAe,IAAI;AAAA,MACxE,WAAW,IAAI;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAIA,WAAW,MAAkB;AAC3B,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,KAAK,QAAQ,KAAK,QAAQ,KAAK,MAAM,KAAK,WAAW,KAAK,UAAU,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,MAAM,KAAK,UAAU,MAAM,KAAK,cAAc,aAAa,KAAK,mBAAmB,CAAG;AAAA,IACjM;AAAA,EACF;AAAA,EAEA,WAAW,QAAgB,QAAQ,IAAY;AAC7C,WAAO,KAAK,GAAG;AAAA,MACb;AAAA;AAAA;AAAA;AAAA,MAIA,CAAC,QAAQ,KAAK;AAAA,IAChB,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,WAAW,QAAgB,QAAQ,IAAY;AAC7C,WAAO,KAAK,GAAG;AAAA,MACb;AAAA;AAAA;AAAA;AAAA,MAIA,CAAC,QAAQ,KAAK;AAAA,IAChB,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,gBAAgB,QAAgB,QAAQ,GAAW;AAEjD,UAAM,UAAU,oBAAI,IAAY,CAAC,MAAM,CAAC;AACxC,QAAI,WAAW,CAAC,MAAM;AACtB,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,SAAS,WAAW,EAAG;AAC3B,YAAMA,gBAAe,SAAS,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AACrD,YAAM,OAAO,KAAK,GAAG;AAAA,QACnB,sDAAsDA,aAAY;AAAA,QAClE;AAAA,MACF;AACA,iBAAW,CAAC;AACZ,iBAAW,OAAO,MAAM;AACtB,YAAI,CAAC,QAAQ,IAAI,IAAI,MAAM,GAAG;AAC5B,kBAAQ,IAAI,IAAI,MAAM;AACtB,mBAAS,KAAK,IAAI,MAAM;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AACA,YAAQ,OAAO,MAAM;AACrB,QAAI,QAAQ,SAAS,EAAG,QAAO,CAAC;AAChC,UAAM,MAAM,CAAC,GAAG,OAAO;AACvB,UAAM,eAAe,IAAI,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAChD,WAAO,KAAK,GAAG,IAAI,oCAAoC,YAAY,KAAK,GAAG,EAAE,IAAI,KAAK,SAAS;AAAA,EACjG;AAAA,EAEA,iBAAiB,SAA2B;AAC1C,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,UAAM,eAAe,QAAQ,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AACpD,WAAO,KAAK,GAAG;AAAA,MACb,wCAAwC,YAAY,mBAAmB,YAAY;AAAA,MACnF,CAAC,GAAG,SAAS,GAAG,OAAO;AAAA,IACzB,EAAE,IAAI,CAAC,SAAc;AAAA,MACnB,QAAQ,IAAI;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,MAAM,IAAI;AAAA,MACV,UAAU,IAAI,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI;AAAA,MACpD,MAAM,IAAI,QAAQ;AAAA,MAClB,QAAQ,IAAI,UAAU;AAAA,IACxB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,UAA4B;AAE5C,UAAM,cAAc,KAAK,GAAG,IAAI,4CAA4C,CAAC,QAAQ,CAAC;AACtF,QAAI,YAAY,WAAW,EAAG,QAAO,CAAC;AACtC,UAAM,MAAM,YAAY,IAAI,CAAC,MAAW,EAAE,EAAE;AAC5C,UAAM,eAAe,IAAI,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAEhD,UAAM,OAAO,KAAK,GAAG;AAAA,MACnB;AAAA;AAAA,4BAEsB,YAAY;AAAA;AAAA,MAElC,CAAC,GAAG,KAAK,QAAQ;AAAA,IACnB;AACA,WAAO,KAAK,IAAI,CAAC,MAAW,EAAE,SAAS;AAAA,EACzC;AAAA;AAAA,EAIA,oBAAoB,UAAkB,SAAiB,SAAiB,UAAkB,MAAe,QAAuB;AAC9H,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,UAAU,SAAS,SAAS,UAAU,QAAQ,MAAM,UAAU,IAAI;AAAA,IACrE;AAAA,EACF;AAAA,EAEA,2BAA2B,UAAwB;AACjD,SAAK,GAAG,IAAI,mDAAmD,CAAC,QAAQ,CAAC;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,wBAAgC;AAC9B,UAAM,OAAO,KAAK,GAAG,IAAI,+BAA+B;AACxD,QAAI,WAAW;AAEf,eAAW,OAAO,MAAM;AACtB,YAAM,EAAE,IAAI,OAAO,WAAW,UAAU,UAAU,SAAS,UAAU,SAAS,WAAW,UAAU,MAAM,OAAO,IAAI;AAEpH,UAAI,YAAY,YAAY;AAE1B,YAAI,SAAS,KAAK,GAAG,IAAI,+CAA+C,CAAC,OAAO,CAAC;AACjF,YAAI,aAAqD;AACzD,YAAI,kBAAkB;AAGtB,YAAI,CAAC,QAAQ;AACX,mBAAS,KAAK,GAAG;AAAA,YACf;AAAA,YACA,CAAC,MAAM,OAAO,EAAE;AAAA,UAClB;AAAA,QACF;AAGA,YAAI,CAAC,QAAQ;AACX,mBAAS,KAAK,GAAG;AAAA,YACf;AAAA,YACA,CAAC,OAAO;AAAA,UACV;AAEA,cAAI,OAAQ,mBAAkB;AAAA,QAChC;AAGA,YAAI,QAAQ;AACV,gBAAM,iBAAiB,KAAK,GAAG,IAAI,oDAAoD,CAAC,OAAO,CAAC;AAChG,cAAI,kBAAkB,eAAe,MAAM,GAAG;AAC5C,yBAAa;AACb,8BAAkB,IAAM,eAAe;AAAA,UACzC;AAAA,QACF;AAEA,YAAI,QAAQ;AACV,eAAK,WAAW,EAAE,QAAQ,UAAU,QAAQ,OAAO,IAAI,MAAM,SAAS,MAAM,QAAQ,QAAW,QAAQ,UAAU,QAAW,YAAY,gBAAgB,CAAC;AACzJ,eAAK,GAAG,IAAI,4CAA4C,CAAC,KAAK,CAAC;AAC/D;AAAA,QACF;AAAA,MACF,WAAW,YAAY,UAAU;AAE/B,cAAM,iBAAiB,KAAK,kBAAkB,SAAS,QAAQ;AAC/D,YAAI,gBAAgB;AAClB,eAAK,WAAW,EAAE,QAAQ,UAAU,QAAQ,gBAAgB,MAAM,WAAW,MAAM,QAAQ,QAAW,QAAQ,UAAU,QAAW,YAAY,YAAY,iBAAiB,EAAI,CAAC;AACjL,eAAK,GAAG,IAAI,4CAA4C,CAAC,KAAK,CAAC;AAC/D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,mBAA2B;AACzB,WAAO,KAAK,sBAAsB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,YAAoB,gBAAuC;AAEnF,QAAI,CAAC,WAAW,WAAW,GAAG,EAAG,QAAO;AAExC,UAAM,YAAY,eAAe,QAAQ,UAAU,EAAE;AAErD,UAAM,YAAY,YAAY,YAAY,MAAM,GAAG;AACnD,UAAM,aAAuB,CAAC;AAC9B,eAAW,OAAO,UAAU;AAC1B,UAAI,QAAQ,KAAM,YAAW,IAAI;AAAA,eACxB,QAAQ,IAAK,YAAW,KAAK,GAAG;AAAA,IAC3C;AACA,UAAM,WAAW,WAAW,KAAK,GAAG;AAGpC,UAAM,aAAa;AAAA,MACjB;AAAA,MACA,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AAEA,eAAW,aAAa,YAAY;AAClC,YAAM,MAAM,KAAK,GAAG,IAAI,oDAAoD,CAAC,SAAS,CAAC;AACvF,UAAI,IAAK,QAAO,IAAI;AAAA,IACtB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,eAAe,QAAoC;AACjD,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,YAAY,KAAK,GAAG;AAAA,MACxB;AAAA;AAAA;AAAA,MAGA,CAAC,MAAM;AAAA,IACT,EAAE,IAAI,KAAK,SAAS;AAEpB,UAAM,WAAW,KAAK,GAAG;AAAA,MACvB;AAAA;AAAA;AAAA,MAGA,CAAC,MAAM;AAAA,IACT,EAAE,IAAI,KAAK,SAAS;AAEpB,UAAM,UAAU,KAAK,WAAW,QAAQ,EAAE;AAC1C,UAAM,UAAU,KAAK,WAAW,QAAQ,EAAE;AAE1C,WAAO,EAAE,MAAM,WAAW,UAAU,SAAS,QAAQ;AAAA,EACvD;AAAA,EAEA,eAAe,QAA6B;AAC1C,UAAM,oBAAqB,KAAK,GAAG,IAAI,oDAAoD,CAAC,MAAM,CAAC,GAAG,KAAK;AAC3G,UAAM,oBAAqB,KAAK,GAAG,IAAI,oDAAoD,CAAC,MAAM,CAAC,GAAG,KAAK;AAC3G,UAAM,YAAa,KAAK,GAAG,IAAI,uEAAuE,CAAC,MAAM,CAAC,GAAG,KAAK;AACtH,UAAM,cAAe,KAAK,GAAG,IAAI,uEAAuE,CAAC,MAAM,CAAC,GAAG,KAAK;AACxH,UAAM,aAAc,KAAK,GAAG,IAAI,0EAA0E,CAAC,MAAM,CAAC,GAAG,KAAK;AAC1H,WAAO,EAAE,mBAAmB,mBAAmB,WAAW,aAAa,WAAW;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,QAAQ,IAAY;AAC/B,WAAO,KAAK,GAAG;AAAA,MACb;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,CAAC,KAAK;AAAA,IACR,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,2BAAuC;AAErC,UAAM,OAAO,KAAK,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF;AAEA,UAAM,MAAM,oBAAI,IAAyB;AACzC,eAAW,OAAO,MAAM;AACtB,UAAI,CAAC,IAAI,IAAI,IAAI,GAAG,EAAG,KAAI,IAAI,IAAI,KAAK,oBAAI,IAAI,CAAC;AACjD,UAAI,IAAI,IAAI,GAAG,EAAG,IAAI,IAAI,GAAG;AAAA,IAC/B;AAEA,UAAM,SAAqB,CAAC;AAC5B,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,UAAoB,CAAC;AAE3B,aAAS,IAAI,MAAoB;AAC/B,UAAI,QAAQ,IAAI,IAAI,GAAG;AACrB,cAAM,aAAa,QAAQ,QAAQ,IAAI;AACvC,eAAO,KAAK,QAAQ,MAAM,UAAU,EAAE,OAAO,IAAI,CAAC;AAClD;AAAA,MACF;AACA,UAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,cAAQ,IAAI,IAAI;AAChB,cAAQ,IAAI,IAAI;AAChB,cAAQ,KAAK,IAAI;AACjB,iBAAW,YAAY,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG;AAC1C,YAAI,QAAQ;AAAA,MACd;AACA,cAAQ,IAAI;AACZ,cAAQ,OAAO,IAAI;AAAA,IACrB;AAEA,eAAW,QAAQ,IAAI,KAAK,GAAG;AAC7B,UAAI,CAAC,QAAQ,IAAI,IAAI,EAAG,KAAI,IAAI;AAAA,IAClC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,QAAgB,MAAc,WAAW,IAAY;AAC5D,QAAI,WAAW,MAAM;AACnB,YAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,aAAO,OAAO,CAAC,IAAI,IAAI,CAAC;AAAA,IAC1B;AAEA,UAAM,OAAO,oBAAI,IAAoB;AACrC,UAAM,QAAkB,CAAC,MAAM;AAC/B,UAAM,UAAU,oBAAI,IAAY,CAAC,MAAM,CAAC;AACxC,QAAI,QAAQ;AAEZ,UAAO,QAAO,MAAM,SAAS,KAAK,QAAQ,UAAU;AAClD,YAAM,YAAY,MAAM;AACxB;AACA,eAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,cAAM,UAAU,MAAM,MAAM;AAE5B,cAAM,OAAO,KAAK,GAAG;AAAA,UACnB;AAAA,UACA,CAAC,OAAO;AAAA,QACV;AACA,mBAAW,OAAO,MAAM;AACtB,cAAI,CAAC,QAAQ,IAAI,IAAI,IAAI,GAAG;AAC1B,oBAAQ,IAAI,IAAI,IAAI;AACpB,iBAAK,IAAI,IAAI,MAAM,OAAO;AAC1B,gBAAI,IAAI,SAAS,KAAM,OAAM;AAC7B,kBAAM,KAAK,IAAI,IAAI;AAAA,UACrB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,IAAI,IAAI,EAAG,QAAO,CAAC;AAG7B,UAAM,UAAoB,CAAC;AAC3B,QAAI,MAA0B;AAC9B,WAAO,QAAQ,QAAW;AACxB,cAAQ,QAAQ,GAAG;AACnB,YAAM,KAAK,IAAI,GAAG;AAAA,IACpB;AAEA,UAAM,SAAiB,CAAC;AACxB,eAAW,MAAM,SAAS;AACxB,YAAM,OAAO,KAAK,QAAQ,EAAE;AAC5B,UAAI,KAAM,QAAO,KAAK,IAAI;AAAA,IAC5B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB,QAAgB,YAAoC,QAAgB;AACnF,UAAM,UAAU,oBAAI,IAAY,CAAC,MAAM,CAAC;AACxC,UAAM,WAAW,CAAC,MAAM;AACxB,UAAM,SAAiB,CAAC;AAExB,WAAO,SAAS,SAAS,GAAG;AAC1B,YAAM,UAAU,SAAS,MAAM;AAC/B,UAAI,OAAc,CAAC;AAEnB,UAAI,cAAc,QAAQ,cAAc,QAAQ;AAE9C,cAAM,KAAK,KAAK,GAAG;AAAA,UACjB;AAAA,UACA,CAAC,OAAO;AAAA,QACV;AACA,eAAO,KAAK,OAAO,EAAE;AAAA,MACvB;AACA,UAAI,cAAc,UAAU,cAAc,QAAQ;AAEhD,cAAM,OAAO,KAAK,GAAG;AAAA,UACnB;AAAA,UACA,CAAC,OAAO;AAAA,QACV;AACA,eAAO,KAAK,OAAO,IAAI;AAAA,MACzB;AAEA,iBAAW,OAAO,MAAM;AACtB,YAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,GAAG;AACxB,kBAAQ,IAAI,IAAI,EAAE;AAClB,mBAAS,KAAK,IAAI,EAAE;AACpB,gBAAM,OAAO,KAAK,QAAQ,IAAI,EAAE;AAChC,cAAI,KAAM,QAAO,KAAK,IAAI;AAAA,QAC5B;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,oBAA4B;AAC1B,QAAI;AACF,YAAM,MAAM,KAAK,GAAG,IAAI,mCAAmC;AAC3D,aAAO,MAAM,IAAI,IAAI;AAAA,IACvB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,eAAe,QAAgB,WAAyB,OAAqB;AAC3E,UAAM,MAAM,OAAO,KAAK,UAAU,MAAM;AACxC,SAAK,GAAG;AAAA,MACN;AAAA,MACA,CAAC,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,qBAA+B;AAC7B,WAAO,KAAK,GAAG,IAAI,6BAA6B,EAAE,IAAI,CAAC,MAAW,EAAE,OAAO;AAAA,EAC7E;AAAA,EAEA,mBAAuE;AACrE,UAAM,OAAO,KAAK,GAAG,IAAI,wCAAwC;AACjE,WAAO,KAAK,IAAI,CAAC,OAAY;AAAA,MAC3B,QAAQ,EAAE;AAAA,MACV,WAAW,IAAI;AAAA,QACZ,EAAE,UAAqB;AAAA,QACvB,EAAE,UAAqB;AAAA,QACvB,EAAE,UAAqB,aAAa;AAAA,MACvC;AAAA,IACF,EAAE;AAAA,EACJ;AAAA,EAEA,cAAyC;AACvC,WAAO,KAAK,GAAG,IAAI,qBAAqB,EAAE,IAAI,KAAK,SAAS;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,wBAAwB,OAAiB,OAAe,QAA2C;AACjG,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,UAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,WAAO,KAAK,GAAG;AAAA,MACb,sCAAsC,YAAY;AAAA,MAClD,CAAC,GAAG,OAAO,OAAO,MAAM;AAAA,IAC1B,EAAE,IAAI,KAAK,SAAS;AAAA,EACtB;AAAA;AAAA,EAGA,qBAAqB,OAAyB;AAC5C,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,UAAM,MAAM,KAAK,GAAG,IAAI,kDAAkD,YAAY,KAAK,KAAK;AAChG,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,cAAsB;AACpB,WAAO,KAAK,GAAG,IAAI,wCAAwC,EAAE,IAAI,CAAC,SAAc;AAAA,MAC9E,QAAQ,IAAI;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,MAAM,IAAI;AAAA,IACZ,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,QAAQ,IAA2E;AAC9F,UAAM,OAAO,KAAK,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,CAAC,KAAK;AAAA,IACR;AACA,WAAO,KAAK,IAAI,CAAC,SAAc;AAAA,MAC7B,GAAG,KAAK,UAAU,GAAG;AAAA,MACrB,QAAQ,IAAI;AAAA,MACZ,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,IACjB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,0BAA0B,QAAQ,IAAwE;AACxG,UAAM,cAAsC;AAAA,MAC1C,OAAO;AAAA,MAAK,YAAY;AAAA,MAAK,SAAS;AAAA,MAAK,SAAS;AAAA,MACpD,WAAW;AAAA,MAAK,SAAS;AAAA,MAAK,YAAY;AAAA,IAC5C;AAEA,UAAM,OACJ,KAAK,GAAG;AAAA,MACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQF;AAGF,UAAM,SAAS,KAAK,IAAI,SAAO;AAC7B,YAAM,UAAU,IAAI,YAAY,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE;AACtD,YAAM,UAAU,IAAI,YAAY,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE;AACtD,UAAI,SAAS;AACb,eAAS,IAAI,GAAG,IAAI,KAAK,IAAI,QAAQ,QAAQ,QAAQ,MAAM,GAAG,KAAK;AACjE,YAAI,QAAQ,CAAC,MAAM,QAAQ,CAAC,EAAG;AAAA,YAC1B;AAAA,MACP;AACA,YAAM,UAAU,KAAK,IAAI,QAAQ,QAAQ,QAAQ,QAAQ,CAAC;AAC1D,YAAM,iBAAiB,SAAS;AAChC,YAAM,aAAa,YAAY,IAAI,IAAI,KAAK;AAC5C,aAAO,EAAE,GAAG,KAAK,QAAQ,IAAI,kBAAkB,WAAW;AAAA,IAC5D,CAAC;AAGD,WAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvC,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,MAAqB,CAAC;AAC5B,eAAW,OAAO,QAAQ;AACxB,YAAM,MAAM,GAAG,IAAI,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI;AACnD,UAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,aAAK,IAAI,GAAG;AACZ,YAAI,KAAK,GAAG;AACZ,YAAI,IAAI,UAAU,MAAO;AAAA,MAC3B;AAAA,IACF;AAEA,UAAM,SAA6E,CAAC;AACpF,eAAW,OAAO,KAAK;AACrB,YAAM,MAAM,KAAK,QAAQ,IAAI,MAAM;AACnC,YAAM,MAAM,KAAK,QAAQ,IAAI,MAAM;AACnC,UAAI,OAAO,IAAK,QAAO,KAAK,EAAE,QAAQ,KAAK,QAAQ,KAAK,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,CAAC;AAAA,IAC5F;AACA,WAAO;AAAA,EACT;AAAA,EAEA,WAAuB;AACrB,UAAM,QAAQ,KAAK,GAAG,IAAI,iCAAiC,EAAE;AAC7D,UAAM,QAAQ,KAAK,GAAG,IAAI,iCAAiC,EAAE;AAC7D,UAAM,QAAQ,KAAK,GAAG,IAAI,iCAAiC,EAAE;AAC7D,UAAM,WAAW,KAAK,GAAG,IAAI,qDAAqD;AAClF,UAAM,cAAsC,CAAC;AAC7C,eAAW,OAAO,SAAU,aAAY,IAAI,IAAI,IAAI,IAAI;AACxD,UAAM,WAAW,KAAK,GAAG,IAAI,6DAA6D;AAC1F,UAAM,kBAA0C,CAAC;AACjD,eAAW,OAAO,SAAU,iBAAgB,IAAI,QAAQ,IAAI,IAAI;AAChE,UAAM,SAAS,KAAK,KAAK,KAAK,aAAa,cAAc,cAAc;AACvE,QAAI,cAAc;AAClB,QAAI;AAAE,oBAAc,GAAG,SAAS,MAAM,EAAE;AAAA,IAAM,QAAQ;AAAA,IAAe;AACrE,UAAM,iBAAiB,KAAK,kBAAkB;AAC9C,WAAO,EAAE,OAAO,OAAO,OAAO,aAAa,iBAAiB,aAAa,gBAAgB,mBAAmB,OAAO,gBAAgB,IAAI,aAAa,OAAO,gBAAgB,UAAmB,eAAe,GAAG,gBAAgB,MAAM,qBAAqB,GAAG,YAAY,CAAC,GAAG,qBAAqB,MAAM;AAAA,EAC3S;AAAA;AAAA,EAIA,oBAA0B;AACxB,SAAK,GAAG,IAAI,2BAA2B;AACvC,SAAK,GAAG,IAAI,6BAA6B;AACzC,SAAK,GAAG,IAAI,+BAA+B;AAC3C,SAAK,GAAG,IAAI,8BAA8B;AAC1C,SAAK,GAAG,IAAI,gCAAgC;AAC5C,SAAK,GAAG,IAAI,yBAAyB;AACrC,SAAK,GAAG,IAAI,2BAA2B;AAAA,EACzC;AAAA,EAEA,kBAAkB,KAAwB;AACxC,SAAK,GAAG;AAAA,MACN;AAAA;AAAA;AAAA,MAGA;AAAA,QACE,IAAI;AAAA,QAAI,IAAI;AAAA,QAAM,IAAI;AAAA,QAAM,IAAI;AAAA,QAChC,IAAI,YAAY;AAAA,QAAM,IAAI,gBAAgB;AAAA,QAAM,IAAI,WAAW;AAAA,QAC/D,IAAI,eAAe,KAAK,UAAU,IAAI,YAAY,IAAI;AAAA,QACtD,IAAI,WAAW,KAAK,UAAU,IAAI,QAAQ,IAAI;AAAA,QAC9C,IAAI;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAAA,EAEA,gBAAgB,OAAwB;AACtC,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA;AAAA,QACE,MAAM;AAAA,QAAI,MAAM;AAAA,QAAM,MAAM;AAAA,QAC5B,KAAK,UAAU,MAAM,QAAQ;AAAA,QAC7B,MAAM,WAAW,KAAK,UAAU,MAAM,QAAQ,IAAI;AAAA,QAClD,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,sBAAsB,UAAkB,WAAyB;AAC/D,SAAK,GAAG;AAAA,MACN;AAAA,MACA,CAAC,UAAU,SAAS;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,oBAAoB,UAAkB,SAAiB,YAAoB,gBAA8B;AACvG,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,UAAU,SAAS,YAAY,cAAc;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,qBAAqB,WAAmB,WAAmB,UAAkB,OAAmD;AAC9H,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,WAAW,WAAW,UAAU,QAAQ,KAAK,UAAU,KAAK,IAAI,IAAI;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,mBAAmB,aAAqB,aAAqB,UAAwB;AACnF,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,aAAa,aAAa,QAAQ;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,mBAAmB,UAA8B;AAC/C,SAAK,GAAG;AAAA,MACN;AAAA;AAAA,MAEA,CAAC,SAAS,WAAW,SAAS,UAAU,SAAS,UAAU,SAAS,aAAa,SAAS,SAAS;AAAA,IACrG;AAAA,EACF;AAAA,EAEA,kBAAiC;AAC/B,WAAO,KAAK,GAAG,IAAI,6BAA6B,EAAE,IAAI,CAAC,OAAyB;AAAA,MAC9E,IAAI,EAAE;AAAA,MACN,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE,YAAY;AAAA,MACxB,cAAc,EAAE,iBAAiB;AAAA,MACjC,SAAS,EAAE,WAAW;AAAA,MACtB,cAAc,EAAE,gBAAgB,KAAK,MAAM,EAAE,aAAa,IAAI;AAAA,MAC9D,UAAU,EAAE,WAAW,KAAK,MAAM,EAAE,QAAQ,IAAI;AAAA,MAChD,WAAW,EAAE;AAAA,IACf,EAAE;AAAA,EACJ;AAAA,EAEA,gBAA6B;AAC3B,WAAO,KAAK,GAAG,IAAI,2BAA2B,EAAE,IAAI,CAAC,OAAuB;AAAA,MAC1E,IAAI,EAAE;AAAA,MACN,MAAM,EAAE;AAAA,MACR,QAAQ,EAAE;AAAA,MACV,UAAU,KAAK,MAAM,EAAE,QAAQ;AAAA,MAC/B,UAAU,EAAE,WAAW,KAAK,MAAM,EAAE,QAAQ,IAAI;AAAA,MAChD,WAAW,EAAE;AAAA,IACf,EAAE;AAAA,EACJ;AAAA,EAEA,qBAAuC;AACrC,WAAO,KAAK,GAAG,IAAI,iCAAiC,EAAE,IAAI,CAAC,OAA4B;AAAA,MACrF,WAAW,EAAE;AAAA,MACb,WAAW,EAAE;AAAA,MACb,UAAU,EAAE;AAAA,MACZ,OAAO,EAAE,QAAQ,KAAK,MAAM,EAAE,KAAK,IAAI;AAAA,IACzC,EAAE;AAAA,EACJ;AAAA,EAEA,mBAAmC;AACjC,WAAO,KAAK,GAAG,IAAI,+BAA+B,EAAE,IAAI,CAAC,OAA0B;AAAA,MACjF,aAAa,EAAE;AAAA,MACf,aAAa,EAAE;AAAA,MACf,UAAU,EAAE;AAAA,IACd,EAAE;AAAA,EACJ;AAAA,EAEA,kBAAkC;AAChC,WAAO,KAAK,GAAG,IAAI,6BAA6B,EAAE,IAAI,CAAC,OAA0B;AAAA,MAC/E,WAAW,EAAE;AAAA,MACb,UAAU,EAAE;AAAA,MACZ,UAAU,EAAE;AAAA,MACZ,aAAa,EAAE;AAAA,MACf,WAAW,EAAE;AAAA,IACf,EAAE;AAAA,EACJ;AAAA,EAEA,eAA0E;AACxE,WAAO;AAAA,MACL,UAAU,KAAK,GAAG,IAAI,yCAAyC,GAAG,KAAK;AAAA,MACvE,QAAQ,KAAK,GAAG,IAAI,uCAAuC,GAAG,KAAK;AAAA,MACnE,aAAa,KAAK,GAAG,IAAI,6CAA6C,GAAG,KAAK;AAAA,IAChF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAAwE;AACtE,WAAO,KAAK,GAAG;AAAA,MACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,EAAE,IAAI,CAAC,OAAY,EAAE,YAAY,EAAE,aAAa,YAAY,EAAE,YAAY,EAAE;AAAA,EAC9E;AAAA,EAEA,sBAAsE;AACpE,WAAO,KAAK,GAAG,IAAI,sDAAsD,EACtE,IAAI,CAAC,OAAY,EAAE,UAAU,EAAE,WAAW,WAAW,EAAE,WAAW,EAAE;AAAA,EACzE;AAAA,EAEA,oBAA8G;AAC5G,WAAO,KAAK,GAAG,IAAI,+EAA+E,EAC/F,IAAI,CAAC,OAAY;AAAA,MAChB,UAAU,EAAE;AAAA,MACZ,SAAS,EAAE;AAAA,MACX,YAAY,EAAE;AAAA,MACd,gBAAgB,EAAE;AAAA,IACpB,EAAE;AAAA,EACN;AAAA,EAEA,wBAME;AACA,WAAO;AAAA,MACL,UAAU,KAAK,gBAAgB;AAAA,MAC/B,QAAQ,KAAK,cAAc;AAAA,MAC3B,aAAa,KAAK,mBAAmB;AAAA,MACrC,WAAW,KAAK,iBAAiB;AAAA,MACjC,UAAU,KAAK,gBAAgB;AAAA,IACjC;AAAA,EACF;AAAA;AAAA,EAIA,YAAe,IAAgB;AAC7B,SAAK,GAAG,IAAI,OAAO;AACnB,QAAI;AACF,YAAM,SAAS,GAAG;AAClB,WAAK,GAAG,IAAI,QAAQ;AACpB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,GAAG,IAAI,UAAU;AACtB,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;",
6
6
  "names": ["placeholders"]
7
7
  }
@@ -81,7 +81,8 @@ class DocsIndexer {
81
81
  if (this.config.enableEmbeddings) {
82
82
  try {
83
83
  opts?.onProgress?.("docs: embedding sections");
84
- const vectorMgr = new import_vectors.DocsVectorManager(this.config, this.db);
84
+ const kirographDir = path.join(this.projectRoot, ".kirograph");
85
+ const vectorMgr = new import_vectors.DocsVectorManager(this.config, this.db, kirographDir);
85
86
  const allSections = this.db.all("SELECT * FROM doc_sections ORDER BY file_path, position");
86
87
  const sections = allSections.map((row) => ({
87
88
  id: row.id,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/docs/indexer.ts"],
4
- "sourcesContent": ["/**\n * Documentation Indexer\n *\n * Orchestrates: scan doc files \u2192 parse sections \u2192 persist to DB.\n * Supports incremental re-indexing via content hashes.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as crypto from 'crypto';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport type { DocSection, ParsedSection, DocIndexResult } from './types';\nimport { generateSectionId, slugify } from './section-id';\nimport { parseDocFile } from './formats/index';\nimport { linkSection } from './linker';\nimport { DocsVectorManager } from './vectors';\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class DocsIndexer {\n private readonly db: any; // raw sqlite db handle\n private readonly config: KiroGraphConfig;\n private readonly projectRoot: string;\n\n constructor(db: any, config: KiroGraphConfig, projectRoot: string) {\n this.db = db;\n this.config = config;\n this.projectRoot = projectRoot;\n }\n\n /**\n * Index all documentation files in the project.\n * Skips files whose content hash hasn't changed (incremental).\n */\n async indexAll(opts?: { force?: boolean; onProgress?: (msg: string) => void }): Promise<DocIndexResult> {\n const start = Date.now();\n const result: DocIndexResult = {\n filesIndexed: 0, sectionsCreated: 0, sectionsUpdated: 0,\n sectionsRemoved: 0, codeRefsCreated: 0, errors: [], duration: 0,\n };\n\n const files = this.scanDocFiles();\n opts?.onProgress?.(`docs: found ${files.length} documentation files`);\n\n for (const relPath of files) {\n try {\n const changed = await this.indexFile(relPath, opts?.force ?? false);\n if (changed) {\n result.filesIndexed++;\n result.sectionsCreated += changed.created;\n result.sectionsUpdated += changed.updated;\n result.sectionsRemoved += changed.removed;\n result.codeRefsCreated += changed.codeRefs;\n }\n } catch (err) {\n result.errors.push(`${relPath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Remove sections for files that no longer exist\n const removedFromDeleted = this.removeDeletedFiles(files);\n result.sectionsRemoved += removedFromDeleted;\n\n // Embed all sections (if embeddings enabled)\n if (this.config.enableEmbeddings) {\n try {\n opts?.onProgress?.('docs: embedding sections');\n const vectorMgr = new DocsVectorManager(this.config, this.db);\n const allSections = this.db.all('SELECT * FROM doc_sections ORDER BY file_path, position') as any[];\n const sections: DocSection[] = allSections.map((row: any) => ({\n id: row.id,\n filePath: row.file_path,\n title: row.title,\n level: row.level,\n parentId: row.parent_id ?? null,\n summary: row.summary ?? null,\n byteStart: row.byte_start,\n byteEnd: row.byte_end,\n contentHash: row.content_hash,\n tags: row.tags ? JSON.parse(row.tags) : [],\n position: row.position,\n updatedAt: row.updated_at,\n }));\n await vectorMgr.embedBatch(sections);\n } catch { /* embedding is non-critical */ }\n }\n\n result.duration = Date.now() - start;\n return result;\n }\n\n /**\n * Index a single file. Returns null if unchanged (hash match).\n */\n async indexFile(relPath: string, force = false): Promise<{ created: number; updated: number; removed: number; codeRefs: number } | null> {\n const absPath = path.join(this.projectRoot, relPath);\n if (!fs.existsSync(absPath)) return null;\n\n const stat = fs.statSync(absPath);\n if (stat.size > this.config.docsMaxFileSize) return null;\n\n const content = fs.readFileSync(absPath, 'utf8');\n const fileHash = crypto.createHash('sha256').update(content).digest('hex');\n\n // Check if file has changed (skip if hash matches and not forced)\n if (!force) {\n const existing = this.db.get(\n 'SELECT content_hash FROM doc_sections WHERE file_path = ? LIMIT 1',\n [relPath]\n );\n // If any section exists with same file and we can verify the file hasn't changed\n if (existing) {\n const allSections = this.db.all(\n 'SELECT content_hash FROM doc_sections WHERE file_path = ?',\n [relPath]\n );\n // Use a file-level hash stored as a tag on the first section\n const firstSection = this.db.get(\n \"SELECT tags FROM doc_sections WHERE file_path = ? AND parent_id IS NULL LIMIT 1\",\n [relPath]\n );\n if (firstSection?.tags) {\n try {\n const tags = JSON.parse(firstSection.tags);\n if (tags._fileHash === fileHash) return null; // unchanged\n } catch { /* proceed with re-index */ }\n }\n }\n }\n\n // Parse the file\n const parseResult = parseDocFile(content, relPath);\n if (!parseResult) return null;\n\n // Flatten sections and generate IDs\n const flatSections = this.flattenSections(parseResult.sections, relPath, fileHash);\n\n // Get existing section IDs for this file\n const existingIds = new Set<string>(\n (this.db.all('SELECT id FROM doc_sections WHERE file_path = ?', [relPath]) as Array<{ id: string }>)\n .map(r => r.id)\n );\n\n let created = 0;\n let updated = 0;\n let codeRefsCount = 0;\n\n // Upsert sections in a transaction\n this.db.run('BEGIN');\n try {\n for (const section of flatSections) {\n if (existingIds.has(section.id)) {\n // Update existing\n this.db.run(`\n UPDATE doc_sections SET\n title = ?, level = ?, parent_id = ?, summary = ?,\n byte_start = ?, byte_end = ?, content_hash = ?,\n tags = ?, position = ?, updated_at = ?\n WHERE id = ?\n `, [\n section.title, section.level, section.parentId, section.summary,\n section.byteStart, section.byteEnd, section.contentHash,\n JSON.stringify(section.tags), section.position, section.updatedAt,\n section.id,\n ]);\n existingIds.delete(section.id);\n updated++;\n } else {\n // Insert new\n this.db.run(`\n INSERT INTO doc_sections (id, file_path, title, level, parent_id, summary, byte_start, byte_end, content_hash, tags, position, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `, [\n section.id, section.filePath, section.title, section.level,\n section.parentId, section.summary, section.byteStart, section.byteEnd,\n section.contentHash, JSON.stringify(section.tags), section.position, section.updatedAt,\n ]);\n created++;\n }\n\n // Update FTS\n this.db.run('INSERT OR REPLACE INTO doc_sections_fts (id, title, summary) VALUES (?, ?, ?)', [\n section.id, section.title, section.summary ?? '',\n ]);\n }\n\n // Remove sections that no longer exist in the file\n for (const staleId of existingIds) {\n this.db.run('DELETE FROM doc_sections WHERE id = ?', [staleId]);\n this.db.run('DELETE FROM doc_sections_fts WHERE id = ?', [staleId]);\n this.db.run('DELETE FROM doc_code_refs WHERE section_id = ?', [staleId]);\n }\n\n // Run code linker if enabled\n if (this.config.docsLinkCode) {\n for (const section of flatSections) {\n // Read section content for linking\n const sectionContent = Buffer.from(content, 'utf8')\n .slice(section.byteStart, section.byteEnd).toString('utf8');\n const refs = linkSection(section.id, sectionContent, this.db);\n // Clear old refs for this section and insert new ones\n this.db.run('DELETE FROM doc_code_refs WHERE section_id = ?', [section.id]);\n for (const ref of refs) {\n this.db.run(\n 'INSERT OR REPLACE INTO doc_code_refs (section_id, qualified_name, ref_type, confidence) VALUES (?, ?, ?, ?)',\n [ref.sectionId, ref.qualifiedName, ref.refType, ref.confidence],\n );\n codeRefsCount++;\n }\n }\n }\n\n this.db.run('COMMIT');\n } catch (err) {\n this.db.run('ROLLBACK');\n throw err;\n }\n\n return { created, updated, removed: existingIds.size, codeRefs: codeRefsCount };\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Scan the project for documentation files matching include/exclude patterns.\n */\n private scanDocFiles(): string[] {\n const includeMatchers = this.config.docsInclude.map(p => picomatch(p));\n const excludeMatchers = this.config.docsExclude.map(p => picomatch(p));\n const results: string[] = [];\n\n const walk = (dir: string) => {\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n const rel = path.relative(this.projectRoot, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n // Skip excluded directories early\n if (excludeMatchers.some(m => m(rel + '/'))) continue;\n walk(full);\n } else if (entry.isFile()) {\n // Check exclude first\n if (excludeMatchers.some(m => m(rel))) continue;\n // Check include\n if (includeMatchers.some(m => m(rel))) {\n results.push(rel);\n }\n }\n }\n };\n\n walk(this.projectRoot);\n return results;\n }\n\n /**\n * Flatten a parsed section tree into a flat array of DocSection objects.\n */\n private flattenSections(\n sections: ParsedSection[],\n filePath: string,\n fileHash: string,\n ): DocSection[] {\n const flat: DocSection[] = [];\n const now = Date.now();\n\n const walk = (nodes: ParsedSection[], parentId: string | null, ancestorSlugs: string[]) => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const id = generateSectionId(filePath, ancestorSlugs, node.title, node.level);\n const contentHash = crypto.createHash('sha256').update(node.content).digest('hex');\n const summary = this.extractFirstSentence(node.content, node.title);\n\n // Store file hash in tags of root-level sections for incremental detection\n const tags: string[] = [];\n if (parentId === null && i === 0) {\n (tags as any)._fileHash = fileHash;\n }\n\n flat.push({\n id,\n filePath,\n title: node.title,\n level: node.level,\n parentId,\n summary,\n byteStart: node.byteStart,\n byteEnd: node.byteEnd,\n contentHash,\n tags: parentId === null && i === 0 ? [JSON.stringify({ _fileHash: fileHash })] : tags,\n position: i,\n updatedAt: now,\n });\n\n if (node.children.length > 0) {\n const currentSlug = slugify(node.title);\n walk(node.children, id, [...ancestorSlugs, currentSlug]);\n }\n }\n };\n\n walk(sections, null, []);\n return flat;\n }\n\n /**\n * Extract the first meaningful sentence from section content.\n * Skips the heading line itself.\n */\n private extractFirstSentence(content: string, title: string): string | null {\n // Remove the heading line\n const lines = content.split('\\n');\n const bodyLines = lines.filter(l => {\n const trimmed = l.trim();\n // Skip heading lines, empty lines, and code fence markers\n if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith('```') || trimmed.startsWith('~~~')) return false;\n // Skip setext underlines\n if (/^[=-]+\\s*$/.test(trimmed)) return false;\n return true;\n });\n\n if (bodyLines.length === 0) return null;\n\n // Take first non-empty line, truncate at sentence boundary\n const firstLine = bodyLines[0].trim();\n const sentenceEnd = firstLine.search(/[.!?]\\s|[.!?]$/);\n if (sentenceEnd > 0) {\n return firstLine.slice(0, sentenceEnd + 1).trim();\n }\n\n // No sentence boundary \u2014 take the whole first line (truncated)\n return firstLine.length > 200 ? firstLine.slice(0, 200) + '\u2026' : firstLine;\n }\n\n /**\n * Remove sections for files that no longer exist on disk.\n */\n private removeDeletedFiles(currentFiles: string[]): number {\n const currentSet = new Set(currentFiles);\n const indexedFiles = (this.db.all('SELECT DISTINCT file_path FROM doc_sections') as Array<{ file_path: string }>)\n .map(r => r.file_path);\n\n let removed = 0;\n for (const filePath of indexedFiles) {\n if (!currentSet.has(filePath)) {\n const count = this.db.get('SELECT COUNT(*) as cnt FROM doc_sections WHERE file_path = ?', [filePath])?.cnt ?? 0;\n this.db.run('DELETE FROM doc_sections WHERE file_path = ?', [filePath]);\n this.db.run('DELETE FROM doc_sections_fts WHERE id IN (SELECT id FROM doc_sections WHERE file_path = ?)', [filePath]);\n this.db.run('DELETE FROM doc_code_refs WHERE section_id IN (SELECT id FROM doc_sections WHERE file_path = ?)', [filePath]);\n removed += count;\n }\n }\n\n return removed;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Documentation Indexer\n *\n * Orchestrates: scan doc files \u2192 parse sections \u2192 persist to DB.\n * Supports incremental re-indexing via content hashes.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as crypto from 'crypto';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport type { DocSection, ParsedSection, DocIndexResult } from './types';\nimport { generateSectionId, slugify } from './section-id';\nimport { parseDocFile } from './formats/index';\nimport { linkSection } from './linker';\nimport { DocsVectorManager } from './vectors';\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class DocsIndexer {\n private readonly db: any; // raw sqlite db handle\n private readonly config: KiroGraphConfig;\n private readonly projectRoot: string;\n\n constructor(db: any, config: KiroGraphConfig, projectRoot: string) {\n this.db = db;\n this.config = config;\n this.projectRoot = projectRoot;\n }\n\n /**\n * Index all documentation files in the project.\n * Skips files whose content hash hasn't changed (incremental).\n */\n async indexAll(opts?: { force?: boolean; onProgress?: (msg: string) => void }): Promise<DocIndexResult> {\n const start = Date.now();\n const result: DocIndexResult = {\n filesIndexed: 0, sectionsCreated: 0, sectionsUpdated: 0,\n sectionsRemoved: 0, codeRefsCreated: 0, errors: [], duration: 0,\n };\n\n const files = this.scanDocFiles();\n opts?.onProgress?.(`docs: found ${files.length} documentation files`);\n\n for (const relPath of files) {\n try {\n const changed = await this.indexFile(relPath, opts?.force ?? false);\n if (changed) {\n result.filesIndexed++;\n result.sectionsCreated += changed.created;\n result.sectionsUpdated += changed.updated;\n result.sectionsRemoved += changed.removed;\n result.codeRefsCreated += changed.codeRefs;\n }\n } catch (err) {\n result.errors.push(`${relPath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Remove sections for files that no longer exist\n const removedFromDeleted = this.removeDeletedFiles(files);\n result.sectionsRemoved += removedFromDeleted;\n\n // Embed all sections (if embeddings enabled)\n if (this.config.enableEmbeddings) {\n try {\n opts?.onProgress?.('docs: embedding sections');\n const kirographDir = path.join(this.projectRoot, '.kirograph');\n const vectorMgr = new DocsVectorManager(this.config, this.db, kirographDir);\n const allSections = this.db.all('SELECT * FROM doc_sections ORDER BY file_path, position') as any[];\n const sections: DocSection[] = allSections.map((row: any) => ({\n id: row.id,\n filePath: row.file_path,\n title: row.title,\n level: row.level,\n parentId: row.parent_id ?? null,\n summary: row.summary ?? null,\n byteStart: row.byte_start,\n byteEnd: row.byte_end,\n contentHash: row.content_hash,\n tags: row.tags ? JSON.parse(row.tags) : [],\n position: row.position,\n updatedAt: row.updated_at,\n }));\n await vectorMgr.embedBatch(sections);\n } catch { /* embedding is non-critical */ }\n }\n\n result.duration = Date.now() - start;\n return result;\n }\n\n /**\n * Index a single file. Returns null if unchanged (hash match).\n */\n async indexFile(relPath: string, force = false): Promise<{ created: number; updated: number; removed: number; codeRefs: number } | null> {\n const absPath = path.join(this.projectRoot, relPath);\n if (!fs.existsSync(absPath)) return null;\n\n const stat = fs.statSync(absPath);\n if (stat.size > this.config.docsMaxFileSize) return null;\n\n const content = fs.readFileSync(absPath, 'utf8');\n const fileHash = crypto.createHash('sha256').update(content).digest('hex');\n\n // Check if file has changed (skip if hash matches and not forced)\n if (!force) {\n const existing = this.db.get(\n 'SELECT content_hash FROM doc_sections WHERE file_path = ? LIMIT 1',\n [relPath]\n );\n // If any section exists with same file and we can verify the file hasn't changed\n if (existing) {\n const allSections = this.db.all(\n 'SELECT content_hash FROM doc_sections WHERE file_path = ?',\n [relPath]\n );\n // Use a file-level hash stored as a tag on the first section\n const firstSection = this.db.get(\n \"SELECT tags FROM doc_sections WHERE file_path = ? AND parent_id IS NULL LIMIT 1\",\n [relPath]\n );\n if (firstSection?.tags) {\n try {\n const tags = JSON.parse(firstSection.tags);\n if (tags._fileHash === fileHash) return null; // unchanged\n } catch { /* proceed with re-index */ }\n }\n }\n }\n\n // Parse the file\n const parseResult = parseDocFile(content, relPath);\n if (!parseResult) return null;\n\n // Flatten sections and generate IDs\n const flatSections = this.flattenSections(parseResult.sections, relPath, fileHash);\n\n // Get existing section IDs for this file\n const existingIds = new Set<string>(\n (this.db.all('SELECT id FROM doc_sections WHERE file_path = ?', [relPath]) as Array<{ id: string }>)\n .map(r => r.id)\n );\n\n let created = 0;\n let updated = 0;\n let codeRefsCount = 0;\n\n // Upsert sections in a transaction\n this.db.run('BEGIN');\n try {\n for (const section of flatSections) {\n if (existingIds.has(section.id)) {\n // Update existing\n this.db.run(`\n UPDATE doc_sections SET\n title = ?, level = ?, parent_id = ?, summary = ?,\n byte_start = ?, byte_end = ?, content_hash = ?,\n tags = ?, position = ?, updated_at = ?\n WHERE id = ?\n `, [\n section.title, section.level, section.parentId, section.summary,\n section.byteStart, section.byteEnd, section.contentHash,\n JSON.stringify(section.tags), section.position, section.updatedAt,\n section.id,\n ]);\n existingIds.delete(section.id);\n updated++;\n } else {\n // Insert new\n this.db.run(`\n INSERT INTO doc_sections (id, file_path, title, level, parent_id, summary, byte_start, byte_end, content_hash, tags, position, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `, [\n section.id, section.filePath, section.title, section.level,\n section.parentId, section.summary, section.byteStart, section.byteEnd,\n section.contentHash, JSON.stringify(section.tags), section.position, section.updatedAt,\n ]);\n created++;\n }\n\n // Update FTS\n this.db.run('INSERT OR REPLACE INTO doc_sections_fts (id, title, summary) VALUES (?, ?, ?)', [\n section.id, section.title, section.summary ?? '',\n ]);\n }\n\n // Remove sections that no longer exist in the file\n for (const staleId of existingIds) {\n this.db.run('DELETE FROM doc_sections WHERE id = ?', [staleId]);\n this.db.run('DELETE FROM doc_sections_fts WHERE id = ?', [staleId]);\n this.db.run('DELETE FROM doc_code_refs WHERE section_id = ?', [staleId]);\n }\n\n // Run code linker if enabled\n if (this.config.docsLinkCode) {\n for (const section of flatSections) {\n // Read section content for linking\n const sectionContent = Buffer.from(content, 'utf8')\n .slice(section.byteStart, section.byteEnd).toString('utf8');\n const refs = linkSection(section.id, sectionContent, this.db);\n // Clear old refs for this section and insert new ones\n this.db.run('DELETE FROM doc_code_refs WHERE section_id = ?', [section.id]);\n for (const ref of refs) {\n this.db.run(\n 'INSERT OR REPLACE INTO doc_code_refs (section_id, qualified_name, ref_type, confidence) VALUES (?, ?, ?, ?)',\n [ref.sectionId, ref.qualifiedName, ref.refType, ref.confidence],\n );\n codeRefsCount++;\n }\n }\n }\n\n this.db.run('COMMIT');\n } catch (err) {\n this.db.run('ROLLBACK');\n throw err;\n }\n\n return { created, updated, removed: existingIds.size, codeRefs: codeRefsCount };\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Scan the project for documentation files matching include/exclude patterns.\n */\n private scanDocFiles(): string[] {\n const includeMatchers = this.config.docsInclude.map(p => picomatch(p));\n const excludeMatchers = this.config.docsExclude.map(p => picomatch(p));\n const results: string[] = [];\n\n const walk = (dir: string) => {\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n const rel = path.relative(this.projectRoot, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n // Skip excluded directories early\n if (excludeMatchers.some(m => m(rel + '/'))) continue;\n walk(full);\n } else if (entry.isFile()) {\n // Check exclude first\n if (excludeMatchers.some(m => m(rel))) continue;\n // Check include\n if (includeMatchers.some(m => m(rel))) {\n results.push(rel);\n }\n }\n }\n };\n\n walk(this.projectRoot);\n return results;\n }\n\n /**\n * Flatten a parsed section tree into a flat array of DocSection objects.\n */\n private flattenSections(\n sections: ParsedSection[],\n filePath: string,\n fileHash: string,\n ): DocSection[] {\n const flat: DocSection[] = [];\n const now = Date.now();\n\n const walk = (nodes: ParsedSection[], parentId: string | null, ancestorSlugs: string[]) => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const id = generateSectionId(filePath, ancestorSlugs, node.title, node.level);\n const contentHash = crypto.createHash('sha256').update(node.content).digest('hex');\n const summary = this.extractFirstSentence(node.content, node.title);\n\n // Store file hash in tags of root-level sections for incremental detection\n const tags: string[] = [];\n if (parentId === null && i === 0) {\n (tags as any)._fileHash = fileHash;\n }\n\n flat.push({\n id,\n filePath,\n title: node.title,\n level: node.level,\n parentId,\n summary,\n byteStart: node.byteStart,\n byteEnd: node.byteEnd,\n contentHash,\n tags: parentId === null && i === 0 ? [JSON.stringify({ _fileHash: fileHash })] : tags,\n position: i,\n updatedAt: now,\n });\n\n if (node.children.length > 0) {\n const currentSlug = slugify(node.title);\n walk(node.children, id, [...ancestorSlugs, currentSlug]);\n }\n }\n };\n\n walk(sections, null, []);\n return flat;\n }\n\n /**\n * Extract the first meaningful sentence from section content.\n * Skips the heading line itself.\n */\n private extractFirstSentence(content: string, title: string): string | null {\n // Remove the heading line\n const lines = content.split('\\n');\n const bodyLines = lines.filter(l => {\n const trimmed = l.trim();\n // Skip heading lines, empty lines, and code fence markers\n if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith('```') || trimmed.startsWith('~~~')) return false;\n // Skip setext underlines\n if (/^[=-]+\\s*$/.test(trimmed)) return false;\n return true;\n });\n\n if (bodyLines.length === 0) return null;\n\n // Take first non-empty line, truncate at sentence boundary\n const firstLine = bodyLines[0].trim();\n const sentenceEnd = firstLine.search(/[.!?]\\s|[.!?]$/);\n if (sentenceEnd > 0) {\n return firstLine.slice(0, sentenceEnd + 1).trim();\n }\n\n // No sentence boundary \u2014 take the whole first line (truncated)\n return firstLine.length > 200 ? firstLine.slice(0, 200) + '\u2026' : firstLine;\n }\n\n /**\n * Remove sections for files that no longer exist on disk.\n */\n private removeDeletedFiles(currentFiles: string[]): number {\n const currentSet = new Set(currentFiles);\n const indexedFiles = (this.db.all('SELECT DISTINCT file_path FROM doc_sections') as Array<{ file_path: string }>)\n .map(r => r.file_path);\n\n let removed = 0;\n for (const filePath of indexedFiles) {\n if (!currentSet.has(filePath)) {\n const count = this.db.get('SELECT COUNT(*) as cnt FROM doc_sections WHERE file_path = ?', [filePath])?.cnt ?? 0;\n this.db.run('DELETE FROM doc_sections WHERE file_path = ?', [filePath]);\n this.db.run('DELETE FROM doc_sections_fts WHERE id IN (SELECT id FROM doc_sections WHERE file_path = ?)', [filePath]);\n this.db.run('DELETE FROM doc_code_refs WHERE section_id IN (SELECT id FROM doc_sections WHERE file_path = ?)', [filePath]);\n removed += count;\n }\n }\n\n return removed;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["picomatch"]
7
7
  }
@@ -105,7 +105,8 @@ class DocsQueries {
105
105
  const ftsResults = this.searchFTS(query, opts?.file, limit);
106
106
  if (this.config?.enableEmbeddings) {
107
107
  try {
108
- const vectorMgr = new import_vectors.DocsVectorManager(this.config, this.db);
108
+ const kirographDir = path.join(this.projectRoot, ".kirograph");
109
+ const vectorMgr = new import_vectors.DocsVectorManager(this.config, this.db, kirographDir);
109
110
  const vectorResults = await vectorMgr.search(query, limit);
110
111
  if (vectorResults.length > 0) {
111
112
  const alpha = 0.5;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/docs/queries.ts"],
4
- "sourcesContent": ["/**\n * Documentation Queries\n *\n * Read-side helpers for MCP tools and CLI commands.\n * All methods operate on the doc_sections and doc_code_refs tables.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { DocSection, DocTocEntry, DocSearchResult } from './types';\nimport type { KiroGraphConfig } from '../config';\nimport { parseSectionId } from './section-id';\nimport { DocsVectorManager } from './vectors';\n\nexport class DocsQueries {\n private readonly db: any;\n private readonly projectRoot: string;\n private readonly config: KiroGraphConfig | null;\n\n constructor(db: any, projectRoot: string, config?: KiroGraphConfig) {\n this.db = db;\n this.projectRoot = projectRoot;\n this.config = config ?? null;\n }\n\n // \u2500\u2500 TOC \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get table of contents for a file or the whole project.\n */\n getToc(opts?: { file?: string; tree?: boolean }): DocTocEntry[] {\n const where = opts?.file ? 'WHERE file_path = ?' : '';\n const params = opts?.file ? [opts.file] : [];\n\n const rows = this.db.all(`\n SELECT id, title, level, file_path, summary, parent_id\n FROM doc_sections\n ${where}\n ORDER BY file_path, position\n `, params) as Array<{\n id: string; title: string; level: number;\n file_path: string; summary: string | null; parent_id: string | null;\n }>;\n\n if (!opts?.tree) {\n // Flat list\n return rows.map(r => ({\n id: r.id,\n title: r.title,\n level: r.level,\n filePath: r.file_path,\n summary: r.summary,\n }));\n }\n\n // Build tree\n return this.buildTocTree(rows);\n }\n\n /**\n * Get heading hierarchy for a single document.\n */\n getOutline(file: string): DocTocEntry[] {\n return this.getToc({ file, tree: true });\n }\n\n // \u2500\u2500 Section Retrieval \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get a section by ID, optionally with context (ancestor chain + child summaries).\n */\n getSection(id: string, opts?: { context?: boolean }): {\n section: DocSection;\n content: string;\n ancestors?: Array<{ id: string; title: string; level: number }>;\n children?: Array<{ id: string; title: string; summary: string | null }>;\n } | null {\n const row = this.db.get('SELECT * FROM doc_sections WHERE id = ?', [id]);\n if (!row) return null;\n\n const section = this.rowToSection(row);\n const content = this.readSectionContent(section);\n\n if (!opts?.context) {\n return { section, content };\n }\n\n // Build ancestor chain\n const ancestors: Array<{ id: string; title: string; level: number }> = [];\n let currentParent = section.parentId;\n while (currentParent) {\n const parent = this.db.get('SELECT id, title, level, parent_id FROM doc_sections WHERE id = ?', [currentParent]);\n if (!parent) break;\n ancestors.unshift({ id: parent.id, title: parent.title, level: parent.level });\n currentParent = parent.parent_id;\n }\n\n // Get child summaries\n const childRows = this.db.all(\n 'SELECT id, title, summary FROM doc_sections WHERE parent_id = ? ORDER BY position',\n [id]\n ) as Array<{ id: string; title: string; summary: string | null }>;\n\n return { section, content, ancestors, children: childRows };\n }\n\n // \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Search sections by query using FTS5 + optional vector search (hybrid).\n */\n async searchSections(query: string, opts?: { file?: string; limit?: number }): Promise<DocSearchResult[]> {\n const limit = opts?.limit ?? 10;\n\n // FTS search\n const ftsResults = this.searchFTS(query, opts?.file, limit);\n\n // Vector search (if embeddings enabled)\n if (this.config?.enableEmbeddings) {\n try {\n const vectorMgr = new DocsVectorManager(this.config, this.db);\n const vectorResults = await vectorMgr.search(query, limit);\n\n if (vectorResults.length > 0) {\n // Merge FTS + vector results with alpha blend\n const alpha = 0.5; // 0 = pure FTS, 1 = pure vector\n const merged = new Map<string, { section: DocSection; score: number; matchType: 'fts' | 'semantic' }>();\n\n // Add FTS results\n for (const r of ftsResults) {\n merged.set(r.section.id, { section: r.section, score: r.score * (1 - alpha), matchType: 'fts' });\n }\n\n // Add/merge vector results\n for (const vr of vectorResults) {\n const existing = merged.get(vr.sectionId);\n if (existing) {\n existing.score += vr.score * alpha;\n existing.matchType = 'semantic'; // promoted to semantic if both match\n } else {\n // Fetch section from DB\n const row = this.db.get('SELECT * FROM doc_sections WHERE id = ?', [vr.sectionId]);\n if (row) {\n // Filter by file if specified\n if (opts?.file && row.file_path !== opts.file) continue;\n merged.set(vr.sectionId, {\n section: this.rowToSection(row),\n score: vr.score * alpha,\n matchType: 'semantic',\n });\n }\n }\n }\n\n // Sort by merged score, return top N\n const sorted = [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit);\n return sorted;\n }\n } catch { /* vector search is non-critical, fall through to FTS-only */ }\n }\n\n return ftsResults;\n }\n\n /**\n * FTS-only search (used internally and as fallback).\n */\n private searchFTS(query: string, file: string | undefined, limit: number): DocSearchResult[] {\n\n // Sanitize query for FTS5\n const sanitized = query\n .replace(/[\",]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n if (!sanitized) return [];\n\n let sql: string;\n let params: any[];\n\n if (file) {\n sql = `\n SELECT s.*, rank\n FROM doc_sections_fts fts\n JOIN doc_sections s ON s.id = fts.id\n WHERE doc_sections_fts MATCH ? AND s.file_path = ?\n ORDER BY rank\n LIMIT ?\n `;\n params = [sanitized, file, limit];\n } else {\n sql = `\n SELECT s.*, rank\n FROM doc_sections_fts fts\n JOIN doc_sections s ON s.id = fts.id\n WHERE doc_sections_fts MATCH ?\n ORDER BY rank\n LIMIT ?\n `;\n params = [sanitized, limit];\n }\n\n try {\n const rows = this.db.all(sql, params) as any[];\n return rows.map(row => ({\n section: this.rowToSection(row),\n score: Math.abs(row.rank ?? 0),\n matchType: 'fts' as const,\n }));\n } catch {\n // FTS query syntax error \u2014 fall back to LIKE search\n return this.searchFallback(sanitized, file, limit);\n }\n }\n\n // \u2500\u2500 Code Refs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get code symbols referenced by a section, or doc sections that reference a symbol.\n */\n getRefs(opts: { sectionId?: string; qualifiedName?: string }): Array<{\n sectionId: string;\n qualifiedName: string;\n refType: string;\n confidence: number;\n sectionTitle?: string;\n }> {\n if (opts.sectionId) {\n return this.db.all(`\n SELECT r.*, s.title as section_title\n FROM doc_code_refs r\n JOIN doc_sections s ON s.id = r.section_id\n WHERE r.section_id = ?\n ORDER BY r.confidence DESC\n `, [opts.sectionId]).map((r: any) => ({\n sectionId: r.section_id,\n qualifiedName: r.qualified_name,\n refType: r.ref_type,\n confidence: r.confidence,\n sectionTitle: r.section_title,\n }));\n }\n\n if (opts.qualifiedName) {\n return this.db.all(`\n SELECT r.*, s.title as section_title\n FROM doc_code_refs r\n JOIN doc_sections s ON s.id = r.section_id\n WHERE r.qualified_name = ?\n ORDER BY r.confidence DESC\n `, [opts.qualifiedName]).map((r: any) => ({\n sectionId: r.section_id,\n qualifiedName: r.qualified_name,\n refType: r.ref_type,\n confidence: r.confidence,\n sectionTitle: r.section_title,\n }));\n }\n\n return [];\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): { files: number; sections: number; codeRefs: number } {\n const files = this.db.get('SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections')?.cnt ?? 0;\n const sections = this.db.get('SELECT COUNT(*) as cnt FROM doc_sections')?.cnt ?? 0;\n const codeRefs = this.db.get('SELECT COUNT(*) as cnt FROM doc_code_refs')?.cnt ?? 0;\n return { files, sections, codeRefs };\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private rowToSection(row: any): DocSection {\n return {\n id: row.id,\n filePath: row.file_path,\n title: row.title,\n level: row.level,\n parentId: row.parent_id ?? null,\n summary: row.summary ?? null,\n byteStart: row.byte_start,\n byteEnd: row.byte_end,\n contentHash: row.content_hash,\n tags: row.tags ? JSON.parse(row.tags) : [],\n position: row.position,\n updatedAt: row.updated_at,\n };\n }\n\n private readSectionContent(section: DocSection): string {\n const absPath = path.join(this.projectRoot, section.filePath);\n try {\n const buffer = fs.readFileSync(absPath);\n return buffer.slice(section.byteStart, section.byteEnd).toString('utf8');\n } catch {\n return '(file not found on disk)';\n }\n }\n\n private buildTocTree(rows: Array<{\n id: string; title: string; level: number;\n file_path: string; summary: string | null; parent_id: string | null;\n }>): DocTocEntry[] {\n const map = new Map<string, DocTocEntry>();\n const roots: DocTocEntry[] = [];\n\n for (const row of rows) {\n const entry: DocTocEntry = {\n id: row.id,\n title: row.title,\n level: row.level,\n filePath: row.file_path,\n summary: row.summary,\n children: [],\n };\n map.set(row.id, entry);\n\n if (!row.parent_id || !map.has(row.parent_id)) {\n roots.push(entry);\n } else {\n map.get(row.parent_id)!.children!.push(entry);\n }\n }\n\n return roots;\n }\n\n private searchFallback(query: string, file: string | undefined, limit: number): DocSearchResult[] {\n const pattern = `%${query}%`;\n let sql: string;\n let params: any[];\n\n if (file) {\n sql = 'SELECT * FROM doc_sections WHERE file_path = ? AND (title LIKE ? OR summary LIKE ?) LIMIT ?';\n params = [file, pattern, pattern, limit];\n } else {\n sql = 'SELECT * FROM doc_sections WHERE title LIKE ? OR summary LIKE ? LIMIT ?';\n params = [pattern, pattern, limit];\n }\n\n const rows = this.db.all(sql, params) as any[];\n return rows.map(row => ({\n section: this.rowToSection(row),\n score: 1.0,\n matchType: 'fts' as const,\n }));\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Documentation Queries\n *\n * Read-side helpers for MCP tools and CLI commands.\n * All methods operate on the doc_sections and doc_code_refs tables.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { DocSection, DocTocEntry, DocSearchResult } from './types';\nimport type { KiroGraphConfig } from '../config';\nimport { parseSectionId } from './section-id';\nimport { DocsVectorManager } from './vectors';\n\nexport class DocsQueries {\n private readonly db: any;\n private readonly projectRoot: string;\n private readonly config: KiroGraphConfig | null;\n\n constructor(db: any, projectRoot: string, config?: KiroGraphConfig) {\n this.db = db;\n this.projectRoot = projectRoot;\n this.config = config ?? null;\n }\n\n // \u2500\u2500 TOC \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get table of contents for a file or the whole project.\n */\n getToc(opts?: { file?: string; tree?: boolean }): DocTocEntry[] {\n const where = opts?.file ? 'WHERE file_path = ?' : '';\n const params = opts?.file ? [opts.file] : [];\n\n const rows = this.db.all(`\n SELECT id, title, level, file_path, summary, parent_id\n FROM doc_sections\n ${where}\n ORDER BY file_path, position\n `, params) as Array<{\n id: string; title: string; level: number;\n file_path: string; summary: string | null; parent_id: string | null;\n }>;\n\n if (!opts?.tree) {\n // Flat list\n return rows.map(r => ({\n id: r.id,\n title: r.title,\n level: r.level,\n filePath: r.file_path,\n summary: r.summary,\n }));\n }\n\n // Build tree\n return this.buildTocTree(rows);\n }\n\n /**\n * Get heading hierarchy for a single document.\n */\n getOutline(file: string): DocTocEntry[] {\n return this.getToc({ file, tree: true });\n }\n\n // \u2500\u2500 Section Retrieval \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get a section by ID, optionally with context (ancestor chain + child summaries).\n */\n getSection(id: string, opts?: { context?: boolean }): {\n section: DocSection;\n content: string;\n ancestors?: Array<{ id: string; title: string; level: number }>;\n children?: Array<{ id: string; title: string; summary: string | null }>;\n } | null {\n const row = this.db.get('SELECT * FROM doc_sections WHERE id = ?', [id]);\n if (!row) return null;\n\n const section = this.rowToSection(row);\n const content = this.readSectionContent(section);\n\n if (!opts?.context) {\n return { section, content };\n }\n\n // Build ancestor chain\n const ancestors: Array<{ id: string; title: string; level: number }> = [];\n let currentParent = section.parentId;\n while (currentParent) {\n const parent = this.db.get('SELECT id, title, level, parent_id FROM doc_sections WHERE id = ?', [currentParent]);\n if (!parent) break;\n ancestors.unshift({ id: parent.id, title: parent.title, level: parent.level });\n currentParent = parent.parent_id;\n }\n\n // Get child summaries\n const childRows = this.db.all(\n 'SELECT id, title, summary FROM doc_sections WHERE parent_id = ? ORDER BY position',\n [id]\n ) as Array<{ id: string; title: string; summary: string | null }>;\n\n return { section, content, ancestors, children: childRows };\n }\n\n // \u2500\u2500 Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Search sections by query using FTS5 + optional vector search (hybrid).\n */\n async searchSections(query: string, opts?: { file?: string; limit?: number }): Promise<DocSearchResult[]> {\n const limit = opts?.limit ?? 10;\n\n // FTS search\n const ftsResults = this.searchFTS(query, opts?.file, limit);\n\n // Vector search (if embeddings enabled)\n if (this.config?.enableEmbeddings) {\n try {\n const kirographDir = path.join(this.projectRoot, '.kirograph');\n const vectorMgr = new DocsVectorManager(this.config, this.db, kirographDir);\n const vectorResults = await vectorMgr.search(query, limit);\n\n if (vectorResults.length > 0) {\n // Merge FTS + vector results with alpha blend\n const alpha = 0.5; // 0 = pure FTS, 1 = pure vector\n const merged = new Map<string, { section: DocSection; score: number; matchType: 'fts' | 'semantic' }>();\n\n // Add FTS results\n for (const r of ftsResults) {\n merged.set(r.section.id, { section: r.section, score: r.score * (1 - alpha), matchType: 'fts' });\n }\n\n // Add/merge vector results\n for (const vr of vectorResults) {\n const existing = merged.get(vr.sectionId);\n if (existing) {\n existing.score += vr.score * alpha;\n existing.matchType = 'semantic'; // promoted to semantic if both match\n } else {\n // Fetch section from DB\n const row = this.db.get('SELECT * FROM doc_sections WHERE id = ?', [vr.sectionId]);\n if (row) {\n // Filter by file if specified\n if (opts?.file && row.file_path !== opts.file) continue;\n merged.set(vr.sectionId, {\n section: this.rowToSection(row),\n score: vr.score * alpha,\n matchType: 'semantic',\n });\n }\n }\n }\n\n // Sort by merged score, return top N\n const sorted = [...merged.values()].sort((a, b) => b.score - a.score).slice(0, limit);\n return sorted;\n }\n } catch { /* vector search is non-critical, fall through to FTS-only */ }\n }\n\n return ftsResults;\n }\n\n /**\n * FTS-only search (used internally and as fallback).\n */\n private searchFTS(query: string, file: string | undefined, limit: number): DocSearchResult[] {\n\n // Sanitize query for FTS5\n const sanitized = query\n .replace(/[\",]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n if (!sanitized) return [];\n\n let sql: string;\n let params: any[];\n\n if (file) {\n sql = `\n SELECT s.*, rank\n FROM doc_sections_fts fts\n JOIN doc_sections s ON s.id = fts.id\n WHERE doc_sections_fts MATCH ? AND s.file_path = ?\n ORDER BY rank\n LIMIT ?\n `;\n params = [sanitized, file, limit];\n } else {\n sql = `\n SELECT s.*, rank\n FROM doc_sections_fts fts\n JOIN doc_sections s ON s.id = fts.id\n WHERE doc_sections_fts MATCH ?\n ORDER BY rank\n LIMIT ?\n `;\n params = [sanitized, limit];\n }\n\n try {\n const rows = this.db.all(sql, params) as any[];\n return rows.map(row => ({\n section: this.rowToSection(row),\n score: Math.abs(row.rank ?? 0),\n matchType: 'fts' as const,\n }));\n } catch {\n // FTS query syntax error \u2014 fall back to LIKE search\n return this.searchFallback(sanitized, file, limit);\n }\n }\n\n // \u2500\u2500 Code Refs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Get code symbols referenced by a section, or doc sections that reference a symbol.\n */\n getRefs(opts: { sectionId?: string; qualifiedName?: string }): Array<{\n sectionId: string;\n qualifiedName: string;\n refType: string;\n confidence: number;\n sectionTitle?: string;\n }> {\n if (opts.sectionId) {\n return this.db.all(`\n SELECT r.*, s.title as section_title\n FROM doc_code_refs r\n JOIN doc_sections s ON s.id = r.section_id\n WHERE r.section_id = ?\n ORDER BY r.confidence DESC\n `, [opts.sectionId]).map((r: any) => ({\n sectionId: r.section_id,\n qualifiedName: r.qualified_name,\n refType: r.ref_type,\n confidence: r.confidence,\n sectionTitle: r.section_title,\n }));\n }\n\n if (opts.qualifiedName) {\n return this.db.all(`\n SELECT r.*, s.title as section_title\n FROM doc_code_refs r\n JOIN doc_sections s ON s.id = r.section_id\n WHERE r.qualified_name = ?\n ORDER BY r.confidence DESC\n `, [opts.qualifiedName]).map((r: any) => ({\n sectionId: r.section_id,\n qualifiedName: r.qualified_name,\n refType: r.ref_type,\n confidence: r.confidence,\n sectionTitle: r.section_title,\n }));\n }\n\n return [];\n }\n\n // \u2500\u2500 Stats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n getStats(): { files: number; sections: number; codeRefs: number } {\n const files = this.db.get('SELECT COUNT(DISTINCT file_path) as cnt FROM doc_sections')?.cnt ?? 0;\n const sections = this.db.get('SELECT COUNT(*) as cnt FROM doc_sections')?.cnt ?? 0;\n const codeRefs = this.db.get('SELECT COUNT(*) as cnt FROM doc_code_refs')?.cnt ?? 0;\n return { files, sections, codeRefs };\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private rowToSection(row: any): DocSection {\n return {\n id: row.id,\n filePath: row.file_path,\n title: row.title,\n level: row.level,\n parentId: row.parent_id ?? null,\n summary: row.summary ?? null,\n byteStart: row.byte_start,\n byteEnd: row.byte_end,\n contentHash: row.content_hash,\n tags: row.tags ? JSON.parse(row.tags) : [],\n position: row.position,\n updatedAt: row.updated_at,\n };\n }\n\n private readSectionContent(section: DocSection): string {\n const absPath = path.join(this.projectRoot, section.filePath);\n try {\n const buffer = fs.readFileSync(absPath);\n return buffer.slice(section.byteStart, section.byteEnd).toString('utf8');\n } catch {\n return '(file not found on disk)';\n }\n }\n\n private buildTocTree(rows: Array<{\n id: string; title: string; level: number;\n file_path: string; summary: string | null; parent_id: string | null;\n }>): DocTocEntry[] {\n const map = new Map<string, DocTocEntry>();\n const roots: DocTocEntry[] = [];\n\n for (const row of rows) {\n const entry: DocTocEntry = {\n id: row.id,\n title: row.title,\n level: row.level,\n filePath: row.file_path,\n summary: row.summary,\n children: [],\n };\n map.set(row.id, entry);\n\n if (!row.parent_id || !map.has(row.parent_id)) {\n roots.push(entry);\n } else {\n map.get(row.parent_id)!.children!.push(entry);\n }\n }\n\n return roots;\n }\n\n private searchFallback(query: string, file: string | undefined, limit: number): DocSearchResult[] {\n const pattern = `%${query}%`;\n let sql: string;\n let params: any[];\n\n if (file) {\n sql = 'SELECT * FROM doc_sections WHERE file_path = ? AND (title LIKE ? OR summary LIKE ?) LIMIT ?';\n params = [file, pattern, pattern, limit];\n } else {\n sql = 'SELECT * FROM doc_sections WHERE title LIKE ? OR summary LIKE ? LIMIT ?';\n params = [pattern, pattern, limit];\n }\n\n const rows = this.db.all(sql, params) as any[];\n return rows.map(row => ({\n section: this.rowToSection(row),\n score: 1.0,\n matchType: 'fts' as const,\n }));\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,SAAoB;AACpB,WAAsB;AAItB,qBAAkC;AAE3B,MAAM,YAAY;AAAA,EAKvB,YAAY,IAAS,aAAqB,QAA0B;AAClE,SAAK,KAAK;AACV,SAAK,cAAc;AACnB,SAAK,SAAS,UAAU;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,MAAyD;AAC9D,UAAM,QAAQ,MAAM,OAAO,wBAAwB;AACnD,UAAM,SAAS,MAAM,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC;AAE3C,UAAM,OAAO,KAAK,GAAG,IAAI;AAAA;AAAA;AAAA,QAGrB,KAAK;AAAA;AAAA,OAEN,MAAM;AAKT,QAAI,CAAC,MAAM,MAAM;AAEf,aAAO,KAAK,IAAI,QAAM;AAAA,QACpB,IAAI,EAAE;AAAA,QACN,OAAO,EAAE;AAAA,QACT,OAAO,EAAE;AAAA,QACT,UAAU,EAAE;AAAA,QACZ,SAAS,EAAE;AAAA,MACb,EAAE;AAAA,IACJ;AAGA,WAAO,KAAK,aAAa,IAAI;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAA6B;AACtC,WAAO,KAAK,OAAO,EAAE,MAAM,MAAM,KAAK,CAAC;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW,IAAY,MAKd;AACP,UAAM,MAAM,KAAK,GAAG,IAAI,2CAA2C,CAAC,EAAE,CAAC;AACvE,QAAI,CAAC,IAAK,QAAO;AAEjB,UAAM,UAAU,KAAK,aAAa,GAAG;AACrC,UAAM,UAAU,KAAK,mBAAmB,OAAO;AAE/C,QAAI,CAAC,MAAM,SAAS;AAClB,aAAO,EAAE,SAAS,QAAQ;AAAA,IAC5B;AAGA,UAAM,YAAiE,CAAC;AACxE,QAAI,gBAAgB,QAAQ;AAC5B,WAAO,eAAe;AACpB,YAAM,SAAS,KAAK,GAAG,IAAI,qEAAqE,CAAC,aAAa,CAAC;AAC/G,UAAI,CAAC,OAAQ;AACb,gBAAU,QAAQ,EAAE,IAAI,OAAO,IAAI,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM,CAAC;AAC7E,sBAAgB,OAAO;AAAA,IACzB;AAGA,UAAM,YAAY,KAAK,GAAG;AAAA,MACxB;AAAA,MACA,CAAC,EAAE;AAAA,IACL;AAEA,WAAO,EAAE,SAAS,SAAS,WAAW,UAAU,UAAU;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,OAAe,MAAsE;AACxG,UAAM,QAAQ,MAAM,SAAS;AAG7B,UAAM,aAAa,KAAK,UAAU,OAAO,MAAM,MAAM,KAAK;AAG1D,QAAI,KAAK,QAAQ,kBAAkB;AACjC,UAAI;AACF,cAAM,eAAe,KAAK,KAAK,KAAK,aAAa,YAAY;AAC7D,cAAM,YAAY,IAAI,iCAAkB,KAAK,QAAQ,KAAK,IAAI,YAAY;AAC1E,cAAM,gBAAgB,MAAM,UAAU,OAAO,OAAO,KAAK;AAEzD,YAAI,cAAc,SAAS,GAAG;AAE5B,gBAAM,QAAQ;AACd,gBAAM,SAAS,oBAAI,IAAmF;AAGtG,qBAAW,KAAK,YAAY;AAC1B,mBAAO,IAAI,EAAE,QAAQ,IAAI,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,SAAS,IAAI,QAAQ,WAAW,MAAM,CAAC;AAAA,UACjG;AAGA,qBAAW,MAAM,eAAe;AAC9B,kBAAM,WAAW,OAAO,IAAI,GAAG,SAAS;AACxC,gBAAI,UAAU;AACZ,uBAAS,SAAS,GAAG,QAAQ;AAC7B,uBAAS,YAAY;AAAA,YACvB,OAAO;AAEL,oBAAM,MAAM,KAAK,GAAG,IAAI,2CAA2C,CAAC,GAAG,SAAS,CAAC;AACjF,kBAAI,KAAK;AAEP,oBAAI,MAAM,QAAQ,IAAI,cAAc,KAAK,KAAM;AAC/C,uBAAO,IAAI,GAAG,WAAW;AAAA,kBACvB,SAAS,KAAK,aAAa,GAAG;AAAA,kBAC9B,OAAO,GAAG,QAAQ;AAAA,kBAClB,WAAW;AAAA,gBACb,CAAC;AAAA,cACH;AAAA,YACF;AAAA,UACF;AAGA,gBAAM,SAAS,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK;AACpF,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAAgE;AAAA,IAC1E;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,OAAe,MAA0B,OAAkC;AAG3F,UAAM,YAAY,MACf,QAAQ,SAAS,GAAG,EACpB,QAAQ,QAAQ,GAAG,EACnB,KAAK;AAER,QAAI,CAAC,UAAW,QAAO,CAAC;AAExB,QAAI;AACJ,QAAI;AAEJ,QAAI,MAAM;AACR,YAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQN,eAAS,CAAC,WAAW,MAAM,KAAK;AAAA,IAClC,OAAO;AACL,YAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQN,eAAS,CAAC,WAAW,KAAK;AAAA,IAC5B;AAEA,QAAI;AACF,YAAM,OAAO,KAAK,GAAG,IAAI,KAAK,MAAM;AACpC,aAAO,KAAK,IAAI,UAAQ;AAAA,QACtB,SAAS,KAAK,aAAa,GAAG;AAAA,QAC9B,OAAO,KAAK,IAAI,IAAI,QAAQ,CAAC;AAAA,QAC7B,WAAW;AAAA,MACb,EAAE;AAAA,IACJ,QAAQ;AAEN,aAAO,KAAK,eAAe,WAAW,MAAM,KAAK;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,MAML;AACD,QAAI,KAAK,WAAW;AAClB,aAAO,KAAK,GAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAMhB,CAAC,KAAK,SAAS,CAAC,EAAE,IAAI,CAAC,OAAY;AAAA,QACpC,WAAW,EAAE;AAAA,QACb,eAAe,EAAE;AAAA,QACjB,SAAS,EAAE;AAAA,QACX,YAAY,EAAE;AAAA,QACd,cAAc,EAAE;AAAA,MAClB,EAAE;AAAA,IACJ;AAEA,QAAI,KAAK,eAAe;AACtB,aAAO,KAAK,GAAG,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAMhB,CAAC,KAAK,aAAa,CAAC,EAAE,IAAI,CAAC,OAAY;AAAA,QACxC,WAAW,EAAE;AAAA,QACb,eAAe,EAAE;AAAA,QACjB,SAAS,EAAE;AAAA,QACX,YAAY,EAAE;AAAA,QACd,cAAc,EAAE;AAAA,MAClB,EAAE;AAAA,IACJ;AAEA,WAAO,CAAC;AAAA,EACV;AAAA;AAAA,EAIA,WAAkE;AAChE,UAAM,QAAQ,KAAK,GAAG,IAAI,2DAA2D,GAAG,OAAO;AAC/F,UAAM,WAAW,KAAK,GAAG,IAAI,0CAA0C,GAAG,OAAO;AACjF,UAAM,WAAW,KAAK,GAAG,IAAI,2CAA2C,GAAG,OAAO;AAClF,WAAO,EAAE,OAAO,UAAU,SAAS;AAAA,EACrC;AAAA;AAAA,EAIQ,aAAa,KAAsB;AACzC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,UAAU,IAAI;AAAA,MACd,OAAO,IAAI;AAAA,MACX,OAAO,IAAI;AAAA,MACX,UAAU,IAAI,aAAa;AAAA,MAC3B,SAAS,IAAI,WAAW;AAAA,MACxB,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,aAAa,IAAI;AAAA,MACjB,MAAM,IAAI,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC;AAAA,MACzC,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,IACjB;AAAA,EACF;AAAA,EAEQ,mBAAmB,SAA6B;AACtD,UAAM,UAAU,KAAK,KAAK,KAAK,aAAa,QAAQ,QAAQ;AAC5D,QAAI;AACF,YAAM,SAAS,GAAG,aAAa,OAAO;AACtC,aAAO,OAAO,MAAM,QAAQ,WAAW,QAAQ,OAAO,EAAE,SAAS,MAAM;AAAA,IACzE,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,aAAa,MAGF;AACjB,UAAM,MAAM,oBAAI,IAAyB;AACzC,UAAM,QAAuB,CAAC;AAE9B,eAAW,OAAO,MAAM;AACtB,YAAM,QAAqB;AAAA,QACzB,IAAI,IAAI;AAAA,QACR,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,SAAS,IAAI;AAAA,QACb,UAAU,CAAC;AAAA,MACb;AACA,UAAI,IAAI,IAAI,IAAI,KAAK;AAErB,UAAI,CAAC,IAAI,aAAa,CAAC,IAAI,IAAI,IAAI,SAAS,GAAG;AAC7C,cAAM,KAAK,KAAK;AAAA,MAClB,OAAO;AACL,YAAI,IAAI,IAAI,SAAS,EAAG,SAAU,KAAK,KAAK;AAAA,MAC9C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,OAAe,MAA0B,OAAkC;AAChG,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI;AACJ,QAAI;AAEJ,QAAI,MAAM;AACR,YAAM;AACN,eAAS,CAAC,MAAM,SAAS,SAAS,KAAK;AAAA,IACzC,OAAO;AACL,YAAM;AACN,eAAS,CAAC,SAAS,SAAS,KAAK;AAAA,IACnC;AAEA,UAAM,OAAO,KAAK,GAAG,IAAI,KAAK,MAAM;AACpC,WAAO,KAAK,IAAI,UAAQ;AAAA,MACtB,SAAS,KAAK,aAAa,GAAG;AAAA,MAC9B,OAAO;AAAA,MACP,WAAW;AAAA,IACb,EAAE;AAAA,EACJ;AACF;",
6
6
  "names": []
7
7
  }