kirograph 0.28.1 → 1.0.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.
- package/dist/architecture/index.js +4 -1
- package/dist/architecture/index.js.map +2 -2
- package/dist/architecture/manifest/index.js +13 -2
- package/dist/architecture/manifest/index.js.map +2 -2
- package/dist/bin/commands/install.js +2 -1
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/status.js +2 -1
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/uninit.js +2 -1
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vulns.js +10 -5
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/installer/common.js +7 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/detect.js +10 -0
- package/dist/bin/installer/detect.js.map +2 -2
- package/dist/bin/installer/hooks.js +13 -8
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +1 -1
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +1 -1
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +1 -1
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/commandcode.js +84 -0
- package/dist/bin/installer/targets/commandcode.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +1 -1
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +2 -2
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +1 -1
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +1 -1
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/index.js +10 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +1 -1
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +1 -1
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/bin/kirograph.js.map +1 -1
- package/dist/config.js +21 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +18 -6
- package/dist/core/pipeline.js.map +2 -2
- package/dist/extraction/extractor.js +58 -1
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +38 -3
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
- package/dist/frameworks/index.js +68 -7
- package/dist/frameworks/index.js.map +2 -2
- package/dist/security/manifest/adapter.js +17 -2
- package/dist/security/manifest/adapter.js.map +2 -2
- package/dist/security/manifest/parser.js +2 -2
- package/dist/security/manifest/parser.js.map +2 -2
- package/dist/security/pipeline.js +4 -1
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +61 -45
- package/dist/security/reachability.js.map +3 -3
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/epss-client.js +1 -1
- package/dist/security/vuln/epss-client.js.map +1 -1
- package/dist/security/vuln/osv-adapter.js +99 -31
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/sync/index.js +9 -2
- package/dist/sync/index.js.map +2 -2
- package/dist/vectors/libsql-index.js +287 -0
- package/dist/vectors/libsql-index.js.map +7 -0
- package/dist/vectors/turso-index.js +307 -0
- package/dist/vectors/turso-index.js.map +7 -0
- package/package.json +3 -2
package/dist/sync/index.js
CHANGED
|
@@ -61,6 +61,13 @@ function buildIncludeMatcher(config) {
|
|
|
61
61
|
});
|
|
62
62
|
return (s) => matchers.some((m) => m(s));
|
|
63
63
|
}
|
|
64
|
+
function exceedsMaxFileSize(absPath, config) {
|
|
65
|
+
try {
|
|
66
|
+
return fs.statSync(absPath).size > config.maxFileSize;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
64
71
|
function scanDirectoryWalk(root, config, signal, visited = /* @__PURE__ */ new Set()) {
|
|
65
72
|
const results = [];
|
|
66
73
|
const walk = (dir) => {
|
|
@@ -88,7 +95,7 @@ function scanDirectoryWalk(root, config, signal, visited = /* @__PURE__ */ new S
|
|
|
88
95
|
if (entry.isDirectory()) {
|
|
89
96
|
walk(full);
|
|
90
97
|
} else if (entry.isFile()) {
|
|
91
|
-
if (shouldIncludeFile(rel, config)) {
|
|
98
|
+
if (shouldIncludeFile(rel, config) && !exceedsMaxFileSize(full, config)) {
|
|
92
99
|
const lang = (0, import_languages.detectLanguage)(full);
|
|
93
100
|
if (lang !== "unknown") results.push(full);
|
|
94
101
|
}
|
|
@@ -150,7 +157,7 @@ async function scanDirectory(root, config, signal) {
|
|
|
150
157
|
if (signal?.aborted) return allFiles;
|
|
151
158
|
const files = output.split("\n").filter(Boolean).map((rel) => path.join(gitRoot, rel)).filter((abs) => {
|
|
152
159
|
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
153
|
-
return shouldIncludeFile(rel, config) && (0, import_languages.detectLanguage)(abs) !== "unknown";
|
|
160
|
+
return shouldIncludeFile(rel, config) && (0, import_languages.detectLanguage)(abs) !== "unknown" && !exceedsMaxFileSize(abs, config);
|
|
154
161
|
});
|
|
155
162
|
allFiles.push(...files);
|
|
156
163
|
} catch {
|
package/dist/sync/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/sync/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Sync Module \u2014 file discovery and change detection\n *\n * Extracted from KiroGraph class private methods in src/index.ts.\n * Provides scanDirectory, hashContent, shouldIncludeFile, and getChangedFiles.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as crypto from 'crypto';\nimport { execFileSync } from 'child_process';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport { detectLanguage } from '../extraction/languages';\n\n// \u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Returns picomatch matchers for all exclude patterns plus .kirographignore root file lines.\n */\nfunction buildExcludeMatchers(config: KiroGraphConfig): ((s: string) => boolean)[] {\n const patterns = [...config.exclude];\n\n return patterns.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n}\n\n/**\n * Returns a combined picomatch matcher or null if no include patterns.\n */\nfunction buildIncludeMatcher(config: KiroGraphConfig): ((s: string) => boolean) | null {\n if (!config.include || config.include.length === 0) return null;\n const matchers = config.include.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n return (s: string) => matchers.some(m => m(s));\n}\n\n/**\n * Recursive filesystem walk. Checks .kirographignore, guards against symlink cycles,\n * respects AbortSignal, and filters files through shouldIncludeFile.\n */\nfunction scanDirectoryWalk(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal,\n visited: Set<string> = new Set()\n): string[] {\n const results: string[] = [];\n\n const walk = (dir: string) => {\n if (signal?.aborted) return;\n\n // Guard against symlink cycles\n let realDir: string;\n try { realDir = fs.realpathSync(dir); } catch { return; }\n if (visited.has(realDir)) return;\n visited.add(realDir);\n\n // Check for .kirographignore in this directory\n const ignoreFile = path.join(dir, '.kirographignore');\n if (fs.existsSync(ignoreFile)) return;\n\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n if (signal?.aborted) return;\n\n const full = path.join(dir, entry.name);\n const rel = path.relative(root, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile()) {\n if (shouldIncludeFile(rel, config)) {\n const lang = detectLanguage(full);\n if (lang !== 'unknown') results.push(full);\n }\n }\n }\n };\n\n walk(root);\n return results;\n}\n\n// \u2500\u2500 Git root helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Finds all git roots at or under `root` (including root itself).\n * Returns paths sorted shallowest-first so parent roots are processed before children.\n */\nfunction findGitRoots(root: string): string[] {\n const roots: string[] = [];\n\n const walk = (dir: string) => {\n if (fs.existsSync(path.join(dir, '.git'))) {\n roots.push(dir);\n return; // don't recurse into nested git repos\n }\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n for (const entry of entries) {\n if (entry.isDirectory()) walk(path.join(dir, entry.name));\n }\n };\n\n walk(root);\n return roots.length > 0 ? roots : [root];\n}\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\n/**\n * Returns true when relPath passes the include/exclude filter from config.\n * Exclude patterns are checked first; include patterns (if any) are checked second.\n */\nexport function shouldIncludeFile(relPath: string, config: KiroGraphConfig): boolean {\n const excludeMatchers = buildExcludeMatchers(config);\n if (excludeMatchers.some(m => {\n try { return m(relPath) || m(relPath + '/'); } catch { return false; }\n })) return false;\n\n const includeMatcher = buildIncludeMatcher(config);\n if (includeMatcher && !includeMatcher(relPath)) return false;\n\n return true;\n}\n\n/**\n * SHA-256 hex digest of the given content.\n */\nexport function hashContent(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Returns absolute paths of all indexable files under root.\n * Handles monorepos with multiple nested git roots by running git ls-files\n * from each sub-repo root independently, then merging results.\n * Falls back to filesystem walk if no git roots found.\n * Respects AbortSignal.\n */\nexport async function scanDirectory(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal\n): Promise<string[]> {\n if (signal?.aborted) return [];\n\n const gitRoots = findGitRoots(root);\n const allFiles: string[] = [];\n\n for (const gitRoot of gitRoots) {\n if (signal?.aborted) return allFiles;\n try {\n const output = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 10_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n if (signal?.aborted) return allFiles;\n const files = output.split('\\n').filter(Boolean)\n .map(rel => path.join(gitRoot, rel))\n .filter(abs => {\n const rel = path.relative(root, abs).replace(/\\\\/g, '/');\n return shouldIncludeFile(rel, config)
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,WAAsB;AACtB,SAAoB;AACpB,aAAwB;AACxB,2BAA6B;AAC7B,uBAAsB;AAEtB,uBAA+B;AAO/B,SAAS,qBAAqB,QAAqD;AACjF,QAAM,WAAW,CAAC,GAAG,OAAO,OAAO;AAEnC,SAAO,SAAS,IAAI,OAAK;AACvB,QAAI;AACF,iBAAO,iBAAAA,SAAU,CAAC;AAAA,IACpB,QAAQ;AACN,aAAO,MAAM;AAAA,IACf;AAAA,EACF,CAAC;AACH;AAKA,SAAS,oBAAoB,QAA0D;AACrF,MAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,WAAW,EAAG,QAAO;AAC3D,QAAM,WAAW,OAAO,QAAQ,IAAI,OAAK;AACvC,QAAI;AACF,iBAAO,iBAAAA,SAAU,CAAC;AAAA,IACpB,QAAQ;AACN,aAAO,MAAM;AAAA,IACf;AAAA,EACF,CAAC;AACD,SAAO,CAAC,MAAc,SAAS,KAAK,OAAK,EAAE,CAAC,CAAC;AAC/C;AAMA,SAAS,kBACP,MACA,QACA,QACA,UAAuB,oBAAI,IAAI,GACrB;AACV,QAAM,UAAoB,CAAC;AAE3B,QAAM,OAAO,CAAC,QAAgB;AAC5B,QAAI,QAAQ,QAAS;AAGrB,QAAI;AACJ,QAAI;AAAE,gBAAU,GAAG,aAAa,GAAG;AAAA,IAAG,QAAQ;AAAE;AAAA,IAAQ;AACxD,QAAI,QAAQ,IAAI,OAAO,EAAG;AAC1B,YAAQ,IAAI,OAAO;AAGnB,UAAM,aAAa,KAAK,KAAK,KAAK,kBAAkB;AACpD,QAAI,GAAG,WAAW,UAAU,EAAG;AAE/B,QAAI;AACJ,QAAI;AAAE,gBAAU,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IAAG,QAAQ;AAAE;AAAA,IAAQ;AAEhF,eAAW,SAAS,SAAS;AAC3B,UAAI,QAAQ,QAAS;AAErB,YAAM,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AACtC,YAAM,MAAM,KAAK,SAAS,MAAM,IAAI,EAAE,QAAQ,OAAO,GAAG;AAExD,UAAI,MAAM,YAAY,GAAG;AACvB,aAAK,IAAI;AAAA,MACX,WAAW,MAAM,OAAO,GAAG;AACzB,YAAI,kBAAkB,KAAK,MAAM,GAAG;
|
|
4
|
+
"sourcesContent": ["/**\n * Sync Module \u2014 file discovery and change detection\n *\n * Extracted from KiroGraph class private methods in src/index.ts.\n * Provides scanDirectory, hashContent, shouldIncludeFile, and getChangedFiles.\n */\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport * as crypto from 'crypto';\nimport { execFileSync } from 'child_process';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport { detectLanguage } from '../extraction/languages';\n\n// \u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Returns picomatch matchers for all exclude patterns plus .kirographignore root file lines.\n */\nfunction buildExcludeMatchers(config: KiroGraphConfig): ((s: string) => boolean)[] {\n const patterns = [...config.exclude];\n\n return patterns.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n}\n\n/**\n * Returns a combined picomatch matcher or null if no include patterns.\n */\nfunction buildIncludeMatcher(config: KiroGraphConfig): ((s: string) => boolean) | null {\n if (!config.include || config.include.length === 0) return null;\n const matchers = config.include.map(p => {\n try {\n return picomatch(p);\n } catch {\n return () => false;\n }\n });\n return (s: string) => matchers.some(m => m(s));\n}\n\n/**\n * Returns true when the file at absPath exceeds config.maxFileSize.\n *\n * Uses fs.statSync so the size is checked WITHOUT reading the file into memory.\n * This is what prevents multi-MB compiled bundles from reaching the tree-sitter\n * parser, where the WASM runtime aborts the whole process (issue #33).\n * A stat failure is treated as \"does not exceed\" so the normal read path can\n * handle (and gracefully skip) an unreadable file.\n */\nfunction exceedsMaxFileSize(absPath: string, config: KiroGraphConfig): boolean {\n try {\n return fs.statSync(absPath).size > config.maxFileSize;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursive filesystem walk. Checks .kirographignore, guards against symlink cycles,\n * respects AbortSignal, and filters files through shouldIncludeFile.\n */\nfunction scanDirectoryWalk(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal,\n visited: Set<string> = new Set()\n): string[] {\n const results: string[] = [];\n\n const walk = (dir: string) => {\n if (signal?.aborted) return;\n\n // Guard against symlink cycles\n let realDir: string;\n try { realDir = fs.realpathSync(dir); } catch { return; }\n if (visited.has(realDir)) return;\n visited.add(realDir);\n\n // Check for .kirographignore in this directory\n const ignoreFile = path.join(dir, '.kirographignore');\n if (fs.existsSync(ignoreFile)) return;\n\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n if (signal?.aborted) return;\n\n const full = path.join(dir, entry.name);\n const rel = path.relative(root, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile()) {\n if (shouldIncludeFile(rel, config) && !exceedsMaxFileSize(full, config)) {\n const lang = detectLanguage(full);\n if (lang !== 'unknown') results.push(full);\n }\n }\n }\n };\n\n walk(root);\n return results;\n}\n\n// \u2500\u2500 Git root helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 * Finds all git roots at or under `root` (including root itself).\n * Returns paths sorted shallowest-first so parent roots are processed before children.\n */\nfunction findGitRoots(root: string): string[] {\n const roots: string[] = [];\n\n const walk = (dir: string) => {\n if (fs.existsSync(path.join(dir, '.git'))) {\n roots.push(dir);\n return; // don't recurse into nested git repos\n }\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n for (const entry of entries) {\n if (entry.isDirectory()) walk(path.join(dir, entry.name));\n }\n };\n\n walk(root);\n return roots.length > 0 ? roots : [root];\n}\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\n/**\n * Returns true when relPath passes the include/exclude filter from config.\n * Exclude patterns are checked first; include patterns (if any) are checked second.\n */\nexport function shouldIncludeFile(relPath: string, config: KiroGraphConfig): boolean {\n const excludeMatchers = buildExcludeMatchers(config);\n if (excludeMatchers.some(m => {\n try { return m(relPath) || m(relPath + '/'); } catch { return false; }\n })) return false;\n\n const includeMatcher = buildIncludeMatcher(config);\n if (includeMatcher && !includeMatcher(relPath)) return false;\n\n return true;\n}\n\n/**\n * SHA-256 hex digest of the given content.\n */\nexport function hashContent(content: Buffer | string): string {\n return crypto.createHash('sha256').update(content).digest('hex');\n}\n\n/**\n * Returns absolute paths of all indexable files under root.\n * Handles monorepos with multiple nested git roots by running git ls-files\n * from each sub-repo root independently, then merging results.\n * Falls back to filesystem walk if no git roots found.\n * Respects AbortSignal.\n */\nexport async function scanDirectory(\n root: string,\n config: KiroGraphConfig,\n signal?: AbortSignal\n): Promise<string[]> {\n if (signal?.aborted) return [];\n\n const gitRoots = findGitRoots(root);\n const allFiles: string[] = [];\n\n for (const gitRoot of gitRoots) {\n if (signal?.aborted) return allFiles;\n try {\n const output = execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 10_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n if (signal?.aborted) return allFiles;\n const files = output.split('\\n').filter(Boolean)\n .map(rel => path.join(gitRoot, rel))\n .filter(abs => {\n const rel = path.relative(root, abs).replace(/\\\\/g, '/');\n return shouldIncludeFile(rel, config)\n && detectLanguage(abs) !== 'unknown'\n && !exceedsMaxFileSize(abs, config);\n });\n allFiles.push(...files);\n } catch {\n // git unavailable for this root \u2014 fall back to walk\n allFiles.push(...scanDirectoryWalk(gitRoot, config, signal));\n }\n }\n\n return allFiles;\n}\n\n/**\n * Classifies git-changed files into added / modified / removed across all nested git roots.\n * Returns absolute paths. Returns empty arrays if git is unavailable.\n * Excludes paths whose detected language is 'unknown'.\n */\nexport async function getChangedFiles(\n root: string,\n config: KiroGraphConfig\n): Promise<{ added: string[]; modified: string[]; removed: string[] }> {\n const added: string[] = [];\n const modified: string[] = [];\n const removed: string[] = [];\n\n const gitRoots = findGitRoots(root);\n\n for (const gitRoot of gitRoots) {\n try {\n const output = execFileSync('git', ['status', '--porcelain', '--no-renames'], {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 10_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n\n for (const line of output.split('\\n').filter(Boolean)) {\n const statusCode = line.slice(0, 2);\n const relPath = line.slice(3).trim();\n const absPath = path.join(gitRoot, relPath);\n if (detectLanguage(absPath) === 'unknown') continue;\n if (statusCode === '??') {\n added.push(absPath);\n } else if (statusCode[0] === 'D' || statusCode[1] === 'D') {\n removed.push(absPath);\n } else {\n modified.push(absPath);\n }\n }\n } catch {\n // git unavailable for this root \u2014 skip\n }\n }\n\n return { added, modified, removed };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["picomatch"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var libsql_index_exports = {};
|
|
30
|
+
__export(libsql_index_exports, {
|
|
31
|
+
LibSQLIndex: () => LibSQLIndex
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(libsql_index_exports);
|
|
34
|
+
var path = __toESM(require("path"));
|
|
35
|
+
var import_errors = require("../errors");
|
|
36
|
+
const DEFAULT_DIM = 768;
|
|
37
|
+
const DB_FILE = "libsql.db";
|
|
38
|
+
const VECTOR_WEIGHT = 0.7;
|
|
39
|
+
const FTS_WEIGHT = 0.3;
|
|
40
|
+
class LibSQLIndex {
|
|
41
|
+
constructor(kirographDir, dim = DEFAULT_DIM) {
|
|
42
|
+
this.kirographDir = kirographDir;
|
|
43
|
+
this.dim = dim;
|
|
44
|
+
this.db = null;
|
|
45
|
+
this._available = false;
|
|
46
|
+
this.dbPath = path.join(kirographDir, DB_FILE);
|
|
47
|
+
}
|
|
48
|
+
isAvailable() {
|
|
49
|
+
return this._available;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Load the libsql package, open the database, and apply the schema.
|
|
53
|
+
* Silent no-op when the optional dep is missing.
|
|
54
|
+
*/
|
|
55
|
+
async initialize() {
|
|
56
|
+
if (this._available) return;
|
|
57
|
+
let Database;
|
|
58
|
+
try {
|
|
59
|
+
const mod = require("libsql");
|
|
60
|
+
Database = mod.Database ?? mod.default ?? mod;
|
|
61
|
+
} catch {
|
|
62
|
+
(0, import_errors.logDebug)("LibSQLIndex: libsql not installed \u2014 libSQL engine unavailable");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
this.db = new Database(this.dbPath);
|
|
67
|
+
this.db.exec(`
|
|
68
|
+
PRAGMA journal_mode=WAL;
|
|
69
|
+
PRAGMA synchronous=NORMAL;
|
|
70
|
+
PRAGMA cache_size=-32000;
|
|
71
|
+
PRAGMA temp_store=MEMORY;
|
|
72
|
+
`);
|
|
73
|
+
this.db.exec(`
|
|
74
|
+
CREATE TABLE IF NOT EXISTS kg_nodes (
|
|
75
|
+
node_id TEXT PRIMARY KEY,
|
|
76
|
+
name TEXT NOT NULL DEFAULT '',
|
|
77
|
+
kind TEXT NOT NULL DEFAULT '',
|
|
78
|
+
file_path TEXT NOT NULL DEFAULT '',
|
|
79
|
+
signature TEXT NOT NULL DEFAULT '',
|
|
80
|
+
embedding F32_BLOB(${this.dim})
|
|
81
|
+
);
|
|
82
|
+
`);
|
|
83
|
+
this.db.exec(`
|
|
84
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS kg_nodes_fts USING fts5(
|
|
85
|
+
node_id UNINDEXED,
|
|
86
|
+
name,
|
|
87
|
+
signature,
|
|
88
|
+
content='kg_nodes',
|
|
89
|
+
content_rowid='rowid'
|
|
90
|
+
);
|
|
91
|
+
`);
|
|
92
|
+
this.db.exec(`
|
|
93
|
+
CREATE TRIGGER IF NOT EXISTS kg_nodes_ai AFTER INSERT ON kg_nodes BEGIN
|
|
94
|
+
INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)
|
|
95
|
+
VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);
|
|
96
|
+
END;
|
|
97
|
+
|
|
98
|
+
CREATE TRIGGER IF NOT EXISTS kg_nodes_ad AFTER DELETE ON kg_nodes BEGIN
|
|
99
|
+
INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)
|
|
100
|
+
VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);
|
|
101
|
+
END;
|
|
102
|
+
|
|
103
|
+
CREATE TRIGGER IF NOT EXISTS kg_nodes_au AFTER UPDATE ON kg_nodes BEGIN
|
|
104
|
+
INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)
|
|
105
|
+
VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);
|
|
106
|
+
INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)
|
|
107
|
+
VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);
|
|
108
|
+
END;
|
|
109
|
+
`);
|
|
110
|
+
this._available = true;
|
|
111
|
+
(0, import_errors.logDebug)("LibSQLIndex: ready", { path: this.dbPath, dim: this.dim });
|
|
112
|
+
} catch (err) {
|
|
113
|
+
(0, import_errors.logError)("LibSQLIndex: initialization failed", { error: String(err) });
|
|
114
|
+
this._available = false;
|
|
115
|
+
if (this.db) {
|
|
116
|
+
try {
|
|
117
|
+
this.db.close();
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
this.db = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Insert or update a node's record using SQLite's native ON CONFLICT upsert.
|
|
126
|
+
*/
|
|
127
|
+
upsert(node, embedding) {
|
|
128
|
+
if (!this._available || !this.db) return;
|
|
129
|
+
try {
|
|
130
|
+
this.db.prepare(`
|
|
131
|
+
INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
|
|
132
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
133
|
+
ON CONFLICT (node_id) DO UPDATE SET
|
|
134
|
+
name = excluded.name,
|
|
135
|
+
kind = excluded.kind,
|
|
136
|
+
file_path = excluded.file_path,
|
|
137
|
+
signature = excluded.signature,
|
|
138
|
+
embedding = excluded.embedding
|
|
139
|
+
`).run(
|
|
140
|
+
node.id,
|
|
141
|
+
node.name,
|
|
142
|
+
node.kind,
|
|
143
|
+
node.filePath,
|
|
144
|
+
node.signature ?? "",
|
|
145
|
+
embedding
|
|
146
|
+
);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
(0, import_errors.logWarn)("LibSQLIndex: upsert failed", { nodeId: node.id, error: String(err) });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.
|
|
153
|
+
*/
|
|
154
|
+
bulkUpsert(nodes, embeddings) {
|
|
155
|
+
if (!this._available || !this.db) return;
|
|
156
|
+
if (nodes.length === 0) return;
|
|
157
|
+
const stmt = this.db.prepare(`
|
|
158
|
+
INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)
|
|
159
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
160
|
+
ON CONFLICT (node_id) DO UPDATE SET
|
|
161
|
+
name = excluded.name,
|
|
162
|
+
kind = excluded.kind,
|
|
163
|
+
file_path = excluded.file_path,
|
|
164
|
+
signature = excluded.signature,
|
|
165
|
+
embedding = excluded.embedding
|
|
166
|
+
`);
|
|
167
|
+
const tx = this.db.transaction(() => {
|
|
168
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
169
|
+
const node = nodes[i];
|
|
170
|
+
stmt.run(
|
|
171
|
+
node.id,
|
|
172
|
+
node.name,
|
|
173
|
+
node.kind,
|
|
174
|
+
node.filePath,
|
|
175
|
+
node.signature ?? "",
|
|
176
|
+
embeddings[i]
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
try {
|
|
181
|
+
tx();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
(0, import_errors.logWarn)("LibSQLIndex: bulkUpsert failed", { count: nodes.length, error: String(err) });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Remove a node's record from the index.
|
|
188
|
+
*/
|
|
189
|
+
delete(nodeId) {
|
|
190
|
+
if (!this._available || !this.db) return;
|
|
191
|
+
try {
|
|
192
|
+
this.db.prepare("DELETE FROM kg_nodes WHERE node_id = ?").run(nodeId);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
(0, import_errors.logWarn)("LibSQLIndex: delete failed", { nodeId, error: String(err) });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Hybrid search: combines libSQL native vector_distance_cos with FTS5
|
|
199
|
+
* bm25 ranking. Returns node IDs ordered by combined score.
|
|
200
|
+
*
|
|
201
|
+
* Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_rank
|
|
202
|
+
*
|
|
203
|
+
* Falls back to pure vector search when the query text produces no FTS matches.
|
|
204
|
+
*/
|
|
205
|
+
search(queryText, queryVec, topN = 10) {
|
|
206
|
+
if (!this._available || !this.db) return [];
|
|
207
|
+
try {
|
|
208
|
+
const hybridRows = this.db.prepare(`
|
|
209
|
+
SELECT
|
|
210
|
+
n.node_id,
|
|
211
|
+
(${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ?))
|
|
212
|
+
+ ${FTS_WEIGHT} * COALESCE(f.rank_score, 0.0)
|
|
213
|
+
) AS score
|
|
214
|
+
FROM kg_nodes n
|
|
215
|
+
LEFT JOIN (
|
|
216
|
+
SELECT node_id, -rank AS rank_score
|
|
217
|
+
FROM kg_nodes_fts
|
|
218
|
+
WHERE kg_nodes_fts MATCH ?
|
|
219
|
+
) f ON f.node_id = n.node_id
|
|
220
|
+
WHERE n.embedding IS NOT NULL
|
|
221
|
+
ORDER BY score DESC
|
|
222
|
+
LIMIT ?
|
|
223
|
+
`).all(queryVec, queryText, topN);
|
|
224
|
+
if (hybridRows.length > 0) {
|
|
225
|
+
return hybridRows.map((r) => r.node_id);
|
|
226
|
+
}
|
|
227
|
+
return this.vectorSearch(queryVec, topN);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
(0, import_errors.logDebug)("LibSQLIndex: hybrid search failed, trying pure vector", { error: String(err) });
|
|
230
|
+
try {
|
|
231
|
+
return this.vectorSearch(queryVec, topN);
|
|
232
|
+
} catch (vecErr) {
|
|
233
|
+
(0, import_errors.logWarn)("LibSQLIndex: search failed", { error: String(vecErr) });
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Pure vector similarity search using libSQL's native vector_distance_cos.
|
|
240
|
+
*/
|
|
241
|
+
vectorSearch(queryVec, topN) {
|
|
242
|
+
const rows = this.db.prepare(`
|
|
243
|
+
SELECT node_id, vector_distance_cos(embedding, ?) AS distance
|
|
244
|
+
FROM kg_nodes
|
|
245
|
+
WHERE embedding IS NOT NULL
|
|
246
|
+
ORDER BY distance ASC
|
|
247
|
+
LIMIT ?
|
|
248
|
+
`).all(queryVec, topN);
|
|
249
|
+
return rows.map((r) => r.node_id);
|
|
250
|
+
}
|
|
251
|
+
/** Return all node IDs currently stored in the index. */
|
|
252
|
+
getEmbeddedNodeIds() {
|
|
253
|
+
if (!this._available || !this.db) return [];
|
|
254
|
+
try {
|
|
255
|
+
const rows = this.db.prepare("SELECT node_id FROM kg_nodes").all();
|
|
256
|
+
return rows.map((r) => r.node_id);
|
|
257
|
+
} catch {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Number of documents currently in the index. */
|
|
262
|
+
count() {
|
|
263
|
+
if (!this._available || !this.db) return 0;
|
|
264
|
+
try {
|
|
265
|
+
const row = this.db.prepare("SELECT COUNT(*) AS c FROM kg_nodes").get();
|
|
266
|
+
return row ? row.c : 0;
|
|
267
|
+
} catch {
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Close the database connection and release resources. */
|
|
272
|
+
close() {
|
|
273
|
+
if (this.db) {
|
|
274
|
+
try {
|
|
275
|
+
this.db.close();
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
this.db = null;
|
|
279
|
+
this._available = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
284
|
+
0 && (module.exports = {
|
|
285
|
+
LibSQLIndex
|
|
286
|
+
});
|
|
287
|
+
//# sourceMappingURL=libsql-index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/vectors/libsql-index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph libSQL Index\n *\n * Hybrid search (native vector similarity + FTS5 full-text) backed by libSQL,\n * the open-source fork of SQLite with built-in vector search support.\n * The database is persisted to .kirograph/libsql.db.\n *\n * Opt-in: set config.semanticEngine = 'libsql'\n * Required optional dependency (not installed by default):\n * npm install libsql\n *\n * Key advantages:\n * - Native vector_distance_cos() \u2014 no extensions to load, no WASM overhead\n * - better-sqlite3\u2013compatible synchronous API (fast, no async tax)\n * - FTS5 full-text search for hybrid ranking (vector + keyword blending)\n * - Single-file persistence, portable across platforms with prebuilds\n * - Superset of SQLite \u2014 all standard pragmas and features work\n */\n\nimport * as path from 'path';\nimport { logDebug, logWarn, logError } from '../errors';\nimport type { Node } from '../types';\n\n// \u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nconst DEFAULT_DIM = 768;\nconst DB_FILE = 'libsql.db';\n\n// Weight for vector score vs full-text score in hybrid ranking (must sum to 1.0)\nconst VECTOR_WEIGHT = 0.7;\nconst FTS_WEIGHT = 0.3;\n\n// \u2500\u2500 LibSQLIndex \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 LibSQLIndex {\n private db: any = null;\n private _available = false;\n private dbPath: string;\n\n constructor(\n private readonly kirographDir: string,\n private readonly dim = DEFAULT_DIM,\n ) {\n this.dbPath = path.join(kirographDir, DB_FILE);\n }\n\n isAvailable(): boolean {\n return this._available;\n }\n\n /**\n * Load the libsql package, open the database, and apply the schema.\n * Silent no-op when the optional dep is missing.\n */\n async initialize(): Promise<void> {\n if (this._available) return;\n\n let Database: any;\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const mod = require('libsql');\n Database = mod.Database ?? mod.default ?? mod;\n } catch {\n logDebug('LibSQLIndex: libsql not installed \u2014 libSQL engine unavailable');\n return;\n }\n\n try {\n this.db = new Database(this.dbPath);\n\n // Standard SQLite pragmas for performance\n this.db.exec(`\n PRAGMA journal_mode=WAL;\n PRAGMA synchronous=NORMAL;\n PRAGMA cache_size=-32000;\n PRAGMA temp_store=MEMORY;\n `);\n\n // Main table with native libSQL vector column\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS kg_nodes (\n node_id TEXT PRIMARY KEY,\n name TEXT NOT NULL DEFAULT '',\n kind TEXT NOT NULL DEFAULT '',\n file_path TEXT NOT NULL DEFAULT '',\n signature TEXT NOT NULL DEFAULT '',\n embedding F32_BLOB(${this.dim})\n );\n `);\n\n // FTS5 virtual table for full-text search (hybrid ranking)\n this.db.exec(`\n CREATE VIRTUAL TABLE IF NOT EXISTS kg_nodes_fts USING fts5(\n node_id UNINDEXED,\n name,\n signature,\n content='kg_nodes',\n content_rowid='rowid'\n );\n `);\n\n // Triggers to keep FTS5 in sync with the main table\n this.db.exec(`\n CREATE TRIGGER IF NOT EXISTS kg_nodes_ai AFTER INSERT ON kg_nodes BEGIN\n INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)\n VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);\n END;\n\n CREATE TRIGGER IF NOT EXISTS kg_nodes_ad AFTER DELETE ON kg_nodes BEGIN\n INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)\n VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);\n END;\n\n CREATE TRIGGER IF NOT EXISTS kg_nodes_au AFTER UPDATE ON kg_nodes BEGIN\n INSERT INTO kg_nodes_fts(kg_nodes_fts, rowid, node_id, name, signature)\n VALUES ('delete', OLD.rowid, OLD.node_id, OLD.name, OLD.signature);\n INSERT INTO kg_nodes_fts(rowid, node_id, name, signature)\n VALUES (NEW.rowid, NEW.node_id, NEW.name, NEW.signature);\n END;\n `);\n\n this._available = true;\n logDebug('LibSQLIndex: ready', { path: this.dbPath, dim: this.dim });\n } catch (err) {\n logError('LibSQLIndex: initialization failed', { error: String(err) });\n this._available = false;\n if (this.db) {\n try { this.db.close(); } catch { /* ignore */ }\n this.db = null;\n }\n }\n }\n\n /**\n * Insert or update a node's record using SQLite's native ON CONFLICT upsert.\n */\n upsert(node: Node, embedding: Float32Array): void {\n if (!this._available || !this.db) return;\n\n try {\n this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `).run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n embedding,\n );\n } catch (err) {\n logWarn('LibSQLIndex: upsert failed', { nodeId: node.id, error: String(err) });\n }\n }\n\n /**\n * Bulk upsert for batch embedding. Uses a transaction for atomicity and speed.\n */\n bulkUpsert(nodes: Node[], embeddings: Float32Array[]): void {\n if (!this._available || !this.db) return;\n if (nodes.length === 0) return;\n\n const stmt = this.db.prepare(`\n INSERT INTO kg_nodes (node_id, name, kind, file_path, signature, embedding)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n name = excluded.name,\n kind = excluded.kind,\n file_path = excluded.file_path,\n signature = excluded.signature,\n embedding = excluded.embedding\n `);\n\n const tx = this.db.transaction(() => {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]!;\n stmt.run(\n node.id,\n node.name,\n node.kind,\n node.filePath,\n node.signature ?? '',\n embeddings[i]!,\n );\n }\n });\n\n try {\n tx();\n } catch (err) {\n logWarn('LibSQLIndex: bulkUpsert failed', { count: nodes.length, error: String(err) });\n }\n }\n\n /**\n * Remove a node's record from the index.\n */\n delete(nodeId: string): void {\n if (!this._available || !this.db) return;\n\n try {\n this.db.prepare('DELETE FROM kg_nodes WHERE node_id = ?').run(nodeId);\n } catch (err) {\n logWarn('LibSQLIndex: delete failed', { nodeId, error: String(err) });\n }\n }\n\n /**\n * Hybrid search: combines libSQL native vector_distance_cos with FTS5\n * bm25 ranking. Returns node IDs ordered by combined score.\n *\n * Score = VECTOR_WEIGHT * (1 - cosine_distance) + FTS_WEIGHT * fts_rank\n *\n * Falls back to pure vector search when the query text produces no FTS matches.\n */\n search(queryText: string, queryVec: Float32Array, topN = 10): string[] {\n if (!this._available || !this.db) return [];\n\n try {\n // Try hybrid search first\n const hybridRows = this.db.prepare(`\n SELECT\n n.node_id,\n (${VECTOR_WEIGHT} * (1.0 - vector_distance_cos(n.embedding, ?))\n + ${FTS_WEIGHT} * COALESCE(f.rank_score, 0.0)\n ) AS score\n FROM kg_nodes n\n LEFT JOIN (\n SELECT node_id, -rank AS rank_score\n FROM kg_nodes_fts\n WHERE kg_nodes_fts MATCH ?\n ) f ON f.node_id = n.node_id\n WHERE n.embedding IS NOT NULL\n ORDER BY score DESC\n LIMIT ?\n `).all(queryVec, queryText, topN);\n\n if (hybridRows.length > 0) {\n return hybridRows.map((r: any) => r.node_id as string);\n }\n\n // Fallback: pure vector search (query text may not produce FTS matches)\n return this.vectorSearch(queryVec, topN);\n } catch (err) {\n // If hybrid fails (e.g. FTS parse error), fall back to pure vector\n logDebug('LibSQLIndex: hybrid search failed, trying pure vector', { error: String(err) });\n try {\n return this.vectorSearch(queryVec, topN);\n } catch (vecErr) {\n logWarn('LibSQLIndex: search failed', { error: String(vecErr) });\n return [];\n }\n }\n }\n\n /**\n * Pure vector similarity search using libSQL's native vector_distance_cos.\n */\n private vectorSearch(queryVec: Float32Array, topN: number): string[] {\n const rows = this.db.prepare(`\n SELECT node_id, vector_distance_cos(embedding, ?) AS distance\n FROM kg_nodes\n WHERE embedding IS NOT NULL\n ORDER BY distance ASC\n LIMIT ?\n `).all(queryVec, topN);\n\n return rows.map((r: any) => r.node_id as string);\n }\n\n /** Return all node IDs currently stored in the index. */\n getEmbeddedNodeIds(): string[] {\n if (!this._available || !this.db) return [];\n try {\n const rows = this.db.prepare('SELECT node_id FROM kg_nodes').all();\n return rows.map((r: any) => r.node_id as string);\n } catch {\n return [];\n }\n }\n\n /** Number of documents currently in the index. */\n count(): number {\n if (!this._available || !this.db) return 0;\n try {\n const row = this.db.prepare('SELECT COUNT(*) AS c FROM kg_nodes').get();\n return row ? (row as any).c : 0;\n } catch {\n return 0;\n }\n }\n\n /** Close the database connection and release resources. */\n close(): void {\n if (this.db) {\n try { this.db.close(); } catch { /* ignore */ }\n this.db = null;\n this._available = false;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,WAAsB;AACtB,oBAA4C;AAK5C,MAAM,cAAc;AACpB,MAAM,UAAU;AAGhB,MAAM,gBAAgB;AACtB,MAAM,aAAa;AAIZ,MAAM,YAAY;AAAA,EAKvB,YACmB,cACA,MAAM,aACvB;AAFiB;AACA;AANnB,SAAQ,KAAU;AAClB,SAAQ,aAAa;AAOnB,SAAK,SAAS,KAAK,KAAK,cAAc,OAAO;AAAA,EAC/C;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,aAA4B;AAChC,QAAI,KAAK,WAAY;AAErB,QAAI;AACJ,QAAI;AAEF,YAAM,MAAM,QAAQ,QAAQ;AAC5B,iBAAW,IAAI,YAAY,IAAI,WAAW;AAAA,IAC5C,QAAQ;AACN,kCAAS,oEAA+D;AACxE;AAAA,IACF;AAEA,QAAI;AACF,WAAK,KAAK,IAAI,SAAS,KAAK,MAAM;AAGlC,WAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,OAKZ;AAGD,WAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAOY,KAAK,GAAG;AAAA;AAAA,OAEhC;AAGD,WAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAQZ;AAGD,WAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAiBZ;AAED,WAAK,aAAa;AAClB,kCAAS,sBAAsB,EAAE,MAAM,KAAK,QAAQ,KAAK,KAAK,IAAI,CAAC;AAAA,IACrE,SAAS,KAAK;AACZ,kCAAS,sCAAsC,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AACrE,WAAK,aAAa;AAClB,UAAI,KAAK,IAAI;AACX,YAAI;AAAE,eAAK,GAAG,MAAM;AAAA,QAAG,QAAQ;AAAA,QAAe;AAC9C,aAAK,KAAK;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,MAAY,WAA+B;AAChD,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,GAAI;AAElC,QAAI;AACF,WAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OASf,EAAE;AAAA,QACD,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK,aAAa;AAAA,QAClB;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,iCAAQ,8BAA8B,EAAE,QAAQ,KAAK,IAAI,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IAC/E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,OAAe,YAAkC;AAC1D,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,GAAI;AAClC,QAAI,MAAM,WAAW,EAAG;AAExB,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAS5B;AAED,UAAM,KAAK,KAAK,GAAG,YAAY,MAAM;AACnC,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,OAAO,MAAM,CAAC;AACpB,aAAK;AAAA,UACH,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK,aAAa;AAAA,UAClB,WAAW,CAAC;AAAA,QACd;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI;AACF,SAAG;AAAA,IACL,SAAS,KAAK;AACZ,iCAAQ,kCAAkC,EAAE,OAAO,MAAM,QAAQ,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IACvF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,QAAsB;AAC3B,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,GAAI;AAElC,QAAI;AACF,WAAK,GAAG,QAAQ,wCAAwC,EAAE,IAAI,MAAM;AAAA,IACtE,SAAS,KAAK;AACZ,iCAAQ,8BAA8B,EAAE,QAAQ,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,WAAmB,UAAwB,OAAO,IAAc;AACrE,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,GAAI,QAAO,CAAC;AAE1C,QAAI;AAEF,YAAM,aAAa,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,aAG5B,aAAa;AAAA,eACX,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAWlB,EAAE,IAAI,UAAU,WAAW,IAAI;AAEhC,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO,WAAW,IAAI,CAAC,MAAW,EAAE,OAAiB;AAAA,MACvD;AAGA,aAAO,KAAK,aAAa,UAAU,IAAI;AAAA,IACzC,SAAS,KAAK;AAEZ,kCAAS,yDAAyD,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC;AACxF,UAAI;AACF,eAAO,KAAK,aAAa,UAAU,IAAI;AAAA,MACzC,SAAS,QAAQ;AACf,mCAAQ,8BAA8B,EAAE,OAAO,OAAO,MAAM,EAAE,CAAC;AAC/D,eAAO,CAAC;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,UAAwB,MAAwB;AACnE,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAM5B,EAAE,IAAI,UAAU,IAAI;AAErB,WAAO,KAAK,IAAI,CAAC,MAAW,EAAE,OAAiB;AAAA,EACjD;AAAA;AAAA,EAGA,qBAA+B;AAC7B,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,GAAI,QAAO,CAAC;AAC1C,QAAI;AACF,YAAM,OAAO,KAAK,GAAG,QAAQ,8BAA8B,EAAE,IAAI;AACjE,aAAO,KAAK,IAAI,CAAC,MAAW,EAAE,OAAiB;AAAA,IACjD,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA,EAGA,QAAgB;AACd,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,GAAI,QAAO;AACzC,QAAI;AACF,YAAM,MAAM,KAAK,GAAG,QAAQ,oCAAoC,EAAE,IAAI;AACtE,aAAO,MAAO,IAAY,IAAI;AAAA,IAChC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,IAAI;AACX,UAAI;AAAE,aAAK,GAAG,MAAM;AAAA,MAAG,QAAQ;AAAA,MAAe;AAC9C,WAAK,KAAK;AACV,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|