Pārlūkot izejas kodu

Add WASM fallbacks for tree-sitter and SQLite, fix installer

Replace native tree-sitter with web-tree-sitter + tree-sitter-wasms for
universal cross-platform support. Add node-sqlite3-wasm as a fallback
when better-sqlite3 native bindings aren't available. Move better-sqlite3
and sqlite-vss to optionalDependencies so installs never fail.

Fix installer to use npx fallback when global npm install fails, so MCP
config, hooks, and quick-start instructions all work without the bare
codegraph command in PATH.

Fix tests: update schema version expectation, fix db test paths and
method names, extract MAX_OUTPUT_LENGTH as module constant, normalize
Windows path separators in import resolver.
Colby McHenry 4 mēneši atpakaļ
vecāks
revīzija
8346440592

+ 6 - 2
__tests__/extraction.test.ts

@@ -4,16 +4,20 @@
  * Tests for the tree-sitter extraction system.
  */
 
-import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
 import * as fs from 'fs';
 import * as path from 'path';
 import * as os from 'os';
 import { CodeGraph } from '../src';
 import { extractFromSource, scanDirectory, shouldIncludeFile } from '../src/extraction';
-import { detectLanguage, isLanguageSupported, getSupportedLanguages } from '../src/extraction/grammars';
+import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars } from '../src/extraction/grammars';
 import { normalizePath } from '../src/utils';
 import { DEFAULT_CONFIG } from '../src/types';
 
+beforeAll(async () => {
+  await initGrammars();
+});
+
 // Create a temporary directory for each test
 function createTempDir(): string {
   return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-test-'));

+ 1 - 1
__tests__/foundation.test.ts

@@ -317,7 +317,7 @@ describe('Database Connection', () => {
 
     const version = db.getSchemaVersion();
     expect(version).not.toBeNull();
-    expect(version?.version).toBe(1);
+    expect(version?.version).toBe(2);
 
     db.close();
   });

+ 25 - 23
__tests__/pr19-improvements.test.ts

@@ -13,7 +13,7 @@
  * - CLI uninit command
  */
 
-import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
 import * as fs from 'fs';
 import * as path from 'path';
 import * as os from 'os';
@@ -24,8 +24,13 @@ import {
   getSupportedLanguages,
   clearParserCache,
   getUnavailableGrammarErrors,
+  initGrammars,
 } from '../src/extraction/grammars';
 
+beforeAll(async () => {
+  await initGrammars();
+});
+
 // Create a temporary directory for each test
 function createTempDir(): string {
   return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-pr19-test-'));
@@ -320,8 +325,9 @@ describe('Database Layer Improvements', () => {
     const { DatabaseConnection } = await import('../src/db');
     const { QueryBuilder } = await import('../src/db/queries');
 
-    const db = DatabaseConnection.initialize(testDir);
-    const queries = new QueryBuilder(db.getDatabase());
+    const dbPath = path.join(testDir, 'codegraph.db');
+    const db = DatabaseConnection.initialize(dbPath);
+    const queries = new QueryBuilder(db.getDb());
 
     // Insert a node first (needed as foreign key)
     queries.insertNode({
@@ -375,8 +381,9 @@ describe('Database Layer Improvements', () => {
     const { DatabaseConnection } = await import('../src/db');
     const { QueryBuilder } = await import('../src/db/queries');
 
-    const db = DatabaseConnection.initialize(testDir);
-    const queries = new QueryBuilder(db.getDatabase());
+    const dbPath = path.join(testDir, 'codegraph.db');
+    const db = DatabaseConnection.initialize(dbPath);
+    const queries = new QueryBuilder(db.getDb());
 
     // Insert some nodes
     for (let i = 0; i < 3; i++) {
@@ -405,8 +412,9 @@ describe('Database Layer Improvements', () => {
   it.skipIf(!HAS_SQLITE)('should set performance pragmas on initialization', async () => {
     const { DatabaseConnection } = await import('../src/db');
 
-    const db = DatabaseConnection.initialize(testDir);
-    const rawDb = db.getDatabase();
+    const dbPath = path.join(testDir, 'codegraph.db');
+    const db = DatabaseConnection.initialize(dbPath);
+    const rawDb = db.getDb();
 
     // Check pragmas were set
     const synchronous = rawDb.pragma('synchronous', { simple: true });
@@ -428,8 +436,9 @@ describe('Database Layer Improvements', () => {
     const { DatabaseConnection } = await import('../src/db');
     const { QueryBuilder } = await import('../src/db/queries');
 
-    const db = DatabaseConnection.initialize(testDir);
-    const queries = new QueryBuilder(db.getDatabase());
+    const dbPath = path.join(testDir, 'codegraph.db');
+    const db = DatabaseConnection.initialize(dbPath);
+    const queries = new QueryBuilder(db.getDb());
 
     // Should not throw on empty array
     expect(() => queries.insertUnresolvedRefsBatch([])).not.toThrow();
@@ -665,28 +674,21 @@ describe('CLI uninit', () => {
 // Tree-sitter Version Pinning
 // =============================================================================
 
-describe('Tree-sitter Version Pinning', () => {
-  it('should have exact versions (no caret) in package.json', () => {
+describe('Tree-sitter WASM Setup', () => {
+  it('should use web-tree-sitter and tree-sitter-wasms in dependencies', () => {
     const pkgPath = path.join(__dirname, '..', 'package.json');
     const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
 
-    const treeSitterDeps = Object.entries(pkg.dependencies as Record<string, string>)
-      .filter(([name]) => name.startsWith('tree-sitter') || name.includes('tree-sitter'));
-
-    for (const [name, version] of treeSitterDeps) {
-      // Skip github: references
-      if (version.startsWith('github:')) continue;
-      expect(version, `${name} should not use caret range`).not.toMatch(/^\^/);
-    }
+    expect(pkg.dependencies['web-tree-sitter']).toBeDefined();
+    expect(pkg.dependencies['tree-sitter-wasms']).toBeDefined();
   });
 
-  it('should have tree-sitter override pinned', () => {
+  it('should not have native tree-sitter in dependencies', () => {
     const pkgPath = path.join(__dirname, '..', 'package.json');
     const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
 
-    expect(pkg.overrides).toBeDefined();
-    expect(pkg.overrides['tree-sitter']).toBeDefined();
-    expect(pkg.overrides['tree-sitter']).not.toMatch(/^\^/);
+    expect(pkg.dependencies['tree-sitter']).toBeUndefined();
+    expect(pkg.overrides).toBeUndefined();
   });
 });
 

+ 31 - 534
package-lock.json

@@ -11,11 +11,12 @@
       "license": "MIT",
       "dependencies": {
         "@xenova/transformers": "^2.17.0",
-        "better-sqlite3": "^11.0.0",
         "commander": "^14.0.2",
         "figlet": "^1.8.0",
+        "node-sqlite3-wasm": "^0.8.30",
         "picomatch": "^4.0.3",
-        "tree-sitter": "0.22.4"
+        "tree-sitter-wasms": "^0.1.11",
+        "web-tree-sitter": "^0.25.3"
       },
       "bin": {
         "codegraph": "dist/bin/codegraph.js"
@@ -32,21 +33,8 @@
         "node": ">=18.0.0"
       },
       "optionalDependencies": {
-        "@sengac/tree-sitter-dart": "1.1.6",
-        "sqlite-vss": "^0.1.2",
-        "tree-sitter-c": "0.23.2",
-        "tree-sitter-c-sharp": "0.23.1",
-        "tree-sitter-cpp": "0.23.4",
-        "tree-sitter-go": "0.23.4",
-        "tree-sitter-java": "0.23.5",
-        "tree-sitter-javascript": "0.23.1",
-        "tree-sitter-kotlin": "0.3.8",
-        "tree-sitter-php": "0.23.11",
-        "tree-sitter-python": "0.23.4",
-        "tree-sitter-ruby": "0.23.1",
-        "tree-sitter-rust": "0.23.1",
-        "tree-sitter-swift": "0.6.0",
-        "tree-sitter-typescript": "0.23.2"
+        "better-sqlite3": "^11.0.0",
+        "sqlite-vss": "^0.1.2"
       }
     },
     "node_modules/@esbuild/aix-ppc64": {
@@ -870,50 +858,6 @@
         "win32"
       ]
     },
-    "node_modules/@sengac/tree-sitter": {
-      "version": "0.25.15",
-      "resolved": "https://registry.npmjs.org/@sengac/tree-sitter/-/tree-sitter-0.25.15.tgz",
-      "integrity": "sha512-FQlxMNWYYp/tw03qoN9gpUZ3Lrhp1ti/MoG5Gcc4h98PFa6tbvN3qMkPRt4mWhmyKrL3QrOiLxEab8Gj6ZTHbw==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "node-addon-api": "^8.3.0",
-        "node-gyp-build": "^4.8.4"
-      }
-    },
-    "node_modules/@sengac/tree-sitter-dart": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/@sengac/tree-sitter-dart/-/tree-sitter-dart-1.1.6.tgz",
-      "integrity": "sha512-lLsF6pVmsC8+JkCnSvRzqa1jJYs+129EOn93MZCsvNnmDrZ2gcEaiqhTj69ttsjQZ2sR+LNxumdphHsw/Ln0Ew==",
-      "hasInstallScript": true,
-      "license": "ISC",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^7.1.0",
-        "node-gyp-build": "^4.8.0"
-      },
-      "peerDependencies": {
-        "@sengac/tree-sitter": "^0.25.10"
-      },
-      "peerDependenciesMeta": {
-        "tree_sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@sengac/tree-sitter/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
     "node_modules/@types/better-sqlite3": {
       "version": "7.6.13",
       "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
@@ -1228,6 +1172,7 @@
       "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
       "hasInstallScript": true,
       "license": "MIT",
+      "optional": true,
       "dependencies": {
         "bindings": "^1.5.0",
         "prebuild-install": "^7.1.1"
@@ -1238,6 +1183,7 @@
       "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
       "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
       "license": "MIT",
+      "optional": true,
       "dependencies": {
         "file-uri-to-path": "1.0.0"
       }
@@ -1549,7 +1495,8 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
       "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-      "license": "MIT"
+      "license": "MIT",
+      "optional": true
     },
     "node_modules/flatbuffers": {
       "version": "1.12.0",
@@ -1628,13 +1575,6 @@
       "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
       "license": "MIT"
     },
-    "node_modules/isexe": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-      "license": "ISC",
-      "optional": true
-    },
     "node_modules/long": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
@@ -1729,23 +1669,11 @@
         "node": ">=10"
       }
     },
-    "node_modules/node-addon-api": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
-      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
-      "license": "MIT",
-      "optional": true
-    },
-    "node_modules/node-gyp-build": {
-      "version": "4.8.4",
-      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
-      "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
-      "license": "MIT",
-      "bin": {
-        "node-gyp-build": "bin.js",
-        "node-gyp-build-optional": "optional.js",
-        "node-gyp-build-test": "build-test.js"
-      }
+    "node_modules/node-sqlite3-wasm": {
+      "version": "0.8.53",
+      "resolved": "https://registry.npmjs.org/node-sqlite3-wasm/-/node-sqlite3-wasm-0.8.53.tgz",
+      "integrity": "sha512-HPuGOPj3L+h3WSf0XikIXTDpsRxlVmzBC3RMgqi3yDg9CEbm/4Hw3rrDodeITqITjm07X4atWLlDMMI8KERMiQ==",
+      "license": "MIT"
     },
     "node_modules/once": {
       "version": "1.4.0",
@@ -2336,442 +2264,13 @@
         "node": ">=14.0.0"
       }
     },
-    "node_modules/tree-sitter": {
-      "version": "0.22.4",
-      "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz",
-      "integrity": "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "dependencies": {
-        "node-addon-api": "^8.3.0",
-        "node-gyp-build": "^4.8.4"
-      }
-    },
-    "node_modules/tree-sitter-c": {
-      "version": "0.23.2",
-      "resolved": "https://registry.npmjs.org/tree-sitter-c/-/tree-sitter-c-0.23.2.tgz",
-      "integrity": "sha512-9kADOx31AF94DHcrsMGW0zM/2LS6v7wFkPHPVm7RQU+vYVVZMKZ2FJ9e99pm5feqsAcjUzB9CarqDLgRT1Fe/w==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-c-sharp": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/tree-sitter-c-sharp/-/tree-sitter-c-sharp-0.23.1.tgz",
-      "integrity": "sha512-9zZ4FlcTRWWfRf6f4PgGhG8saPls6qOOt75tDfX7un9vQZJmARjPrAC6yBNCX2T/VKcCjIDbgq0evFaB3iGhQw==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-c-sharp/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-c/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-cli": {
-      "version": "0.23.2",
-      "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.23.2.tgz",
-      "integrity": "sha512-kPPXprOqREX+C/FgUp2Qpt9jd0vSwn+hOgjzVv/7hapdoWpa+VeWId53rf4oNNd29ikheF12BYtGD/W90feMbA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "bin": {
-        "tree-sitter": "cli.js"
-      },
-      "engines": {
-        "node": ">=12.0.0"
-      }
-    },
-    "node_modules/tree-sitter-cpp": {
-      "version": "0.23.4",
-      "resolved": "https://registry.npmjs.org/tree-sitter-cpp/-/tree-sitter-cpp-0.23.4.tgz",
-      "integrity": "sha512-qR5qUDyhZ5jJ6V8/umiBxokRbe89bCGmcq/dk94wI4kN86qfdV8k0GHIUEKaqWgcu42wKal5E97LKpLeVW8sKw==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.1",
-        "node-gyp-build": "^4.8.2",
-        "tree-sitter-c": "^0.23.1"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-cpp/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-cpp/node_modules/tree-sitter-c": {
-      "version": "0.23.6",
-      "resolved": "https://registry.npmjs.org/tree-sitter-c/-/tree-sitter-c-0.23.6.tgz",
-      "integrity": "sha512-0dxXKznVyUA0s6PjNolJNs2yF87O5aL538A/eR6njA5oqX3C3vH4vnx3QdOKwuUdpKEcFdHuiDpRKLLCA/tjvQ==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.3.0",
-        "node-gyp-build": "^4.8.4"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.22.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-go": {
-      "version": "0.23.4",
-      "resolved": "https://registry.npmjs.org/tree-sitter-go/-/tree-sitter-go-0.23.4.tgz",
-      "integrity": "sha512-iQaHEs4yMa/hMo/ZCGqLfG61F0miinULU1fFh+GZreCRtKylFLtvn798ocCZjO2r/ungNZgAY1s1hPFyAwkc7w==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.1",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-go/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-java": {
-      "version": "0.23.5",
-      "resolved": "https://registry.npmjs.org/tree-sitter-java/-/tree-sitter-java-0.23.5.tgz",
-      "integrity": "sha512-Yju7oQ0Xx7GcUT01mUglPP+bYfvqjNCGdxqigTnew9nLGoII42PNVP3bHrYeMxswiCRM0yubWmN5qk+zsg0zMA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-java/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-javascript": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/tree-sitter-javascript/-/tree-sitter-javascript-0.23.1.tgz",
-      "integrity": "sha512-/bnhbrTD9frUYHQTiYnPcxyHORIw157ERBa6dqzaKxvR/x3PC4Yzd+D1pZIMS6zNg2v3a8BZ0oK7jHqsQo9fWA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-javascript/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-kotlin": {
-      "version": "0.3.8",
-      "resolved": "https://registry.npmjs.org/tree-sitter-kotlin/-/tree-sitter-kotlin-0.3.8.tgz",
-      "integrity": "sha512-A4obq6bjzmYrA+F0JLLoheFPcofFkctNaZSpnDd+GPn1SfVZLY4/GG4C0cYVBTOShuPBGGAOPLM1JWLZQV4m1g==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^7.1.0",
-        "node-gyp-build": "^4.8.0"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.0"
-      },
-      "peerDependenciesMeta": {
-        "tree_sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-php": {
-      "version": "0.23.11",
-      "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.23.11.tgz",
-      "integrity": "sha512-n+YHSKmYKCyPXsg72rqoUtXyCmNRsG/xe7ExrF2g6bXDERcQ/NPOKIzNfRIcI3f3TtbD6PooA0gMW0EpuuUjVA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-php/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-python": {
-      "version": "0.23.4",
-      "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.23.4.tgz",
-      "integrity": "sha512-MbmUAl7y5UCUWqHscHke7DdRDwQnVNMNKQYQc4Gq2p09j+fgPxaU8JVsuOI/0HD3BSEEe5k9j3xmdtIWbDtDgw==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.1",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-python/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-ruby": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/tree-sitter-ruby/-/tree-sitter-ruby-0.23.1.tgz",
-      "integrity": "sha512-d9/RXgWjR6HanN7wTYhS5bpBQLz1VkH048Vm3CodPGyJVnamXMGb8oEhDypVCBq4QnHui9sTXuJBBP3WtCw5RA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-ruby/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-rust": {
-      "version": "0.23.1",
-      "resolved": "https://registry.npmjs.org/tree-sitter-rust/-/tree-sitter-rust-0.23.1.tgz",
-      "integrity": "sha512-wrMptzUAfbl3DbNrldZveyNM2CWmRw2VvEo2j/855qQbMMz4dlCF+TBwRN/1FL1S6cYvAEAJaCMesGqhocFJhQ==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-rust/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-swift": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/tree-sitter-swift/-/tree-sitter-swift-0.6.0.tgz",
-      "integrity": "sha512-9vOJZes4/UFjBr4COHtp6ZHVuZYwfChSQbpneXQog04dAstfx5px3ybVX2cN+ylvLqsvVpmXLpidxxgF2rDQ7A==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "node-addon-api": "^8.0.0",
-        "node-gyp-build": "^4.8.0",
-        "tree-sitter-cli": "^0.23",
-        "which": "2.0.2"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.1"
-      },
-      "peerDependenciesMeta": {
-        "tree_sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-swift/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter-typescript": {
-      "version": "0.23.2",
-      "resolved": "https://registry.npmjs.org/tree-sitter-typescript/-/tree-sitter-typescript-0.23.2.tgz",
-      "integrity": "sha512-e04JUUKxTT53/x3Uq1zIL45DoYKVfHH4CZqwgZhPg5qYROl5nQjV+85ruFzFGZxu+QeFVbRTPDRnqL9UbU4VeA==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
+    "node_modules/tree-sitter-wasms": {
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/tree-sitter-wasms/-/tree-sitter-wasms-0.1.13.tgz",
+      "integrity": "sha512-wT+cR6DwaIz80/vho3AvSF0N4txuNx/5bcRKoXouOfClpxh/qqrF4URNLQXbbt8MaAxeksZcZd1j8gcGjc+QxQ==",
+      "license": "Unlicense",
       "dependencies": {
-        "node-addon-api": "^8.2.2",
-        "node-gyp-build": "^4.8.2",
-        "tree-sitter-javascript": "^0.23.1"
-      },
-      "peerDependencies": {
-        "tree-sitter": "^0.21.0"
-      },
-      "peerDependenciesMeta": {
-        "tree-sitter": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tree-sitter-typescript/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "optional": true,
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
-      }
-    },
-    "node_modules/tree-sitter/node_modules/node-addon-api": {
-      "version": "8.5.0",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
-      "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
-      "license": "MIT",
-      "engines": {
-        "node": "^18 || ^20 || >= 21"
+        "tree-sitter-wasms": "^0.1.11"
       }
     },
     "node_modules/tunnel-agent": {
@@ -2961,20 +2460,18 @@
         }
       }
     },
-    "node_modules/which": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-      "license": "ISC",
-      "optional": true,
-      "dependencies": {
-        "isexe": "^2.0.0"
-      },
-      "bin": {
-        "node-which": "bin/node-which"
+    "node_modules/web-tree-sitter": {
+      "version": "0.25.10",
+      "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz",
+      "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/emscripten": "^1.40.0"
       },
-      "engines": {
-        "node": ">= 8"
+      "peerDependenciesMeta": {
+        "@types/emscripten": {
+          "optional": true
+        }
       }
     },
     "node_modules/why-is-node-running": {

+ 5 - 20
package.json

@@ -34,11 +34,12 @@
   "license": "MIT",
   "dependencies": {
     "@xenova/transformers": "^2.17.0",
-    "better-sqlite3": "^11.0.0",
     "commander": "^14.0.2",
     "figlet": "^1.8.0",
+    "node-sqlite3-wasm": "^0.8.30",
     "picomatch": "^4.0.3",
-    "tree-sitter": "0.22.4"
+    "tree-sitter-wasms": "^0.1.11",
+    "web-tree-sitter": "^0.25.3"
   },
   "devDependencies": {
     "@types/better-sqlite3": "^7.6.0",
@@ -49,26 +50,10 @@
     "vitest": "^2.1.9"
   },
   "optionalDependencies": {
-    "@sengac/tree-sitter-dart": "1.1.6",
-    "sqlite-vss": "^0.1.2",
-    "tree-sitter-c": "0.23.2",
-    "tree-sitter-c-sharp": "0.23.1",
-    "tree-sitter-cpp": "0.23.4",
-    "tree-sitter-go": "0.23.4",
-    "tree-sitter-java": "0.23.5",
-    "tree-sitter-javascript": "0.23.1",
-    "tree-sitter-kotlin": "0.3.8",
-    "tree-sitter-php": "0.23.11",
-    "tree-sitter-python": "0.23.4",
-    "tree-sitter-ruby": "0.23.1",
-    "tree-sitter-rust": "0.23.1",
-    "tree-sitter-swift": "0.6.0",
-    "tree-sitter-typescript": "0.23.2"
+    "better-sqlite3": "^11.0.0",
+    "sqlite-vss": "^0.1.2"
   },
   "engines": {
     "node": ">=18.0.0"
-  },
-  "overrides": {
-    "tree-sitter": "0.22.4"
   }
 }

+ 0 - 3
scripts/postinstall.js

@@ -62,9 +62,6 @@ async function downloadModel() {
   }
 }
 
-// @sengac/tree-sitter-dart ships with NAPI prebuilds for all platforms
-// No patching needed (replaced old tree-sitter-dart v1.0.0 which used NAN bindings)
-
 downloadModel().catch(() => {
   // Silent exit - don't break npm install
   process.exit(0);

+ 41 - 16
src/bin/codegraph.ts

@@ -26,11 +26,25 @@ import { Command } from 'commander';
 import * as path from 'path';
 import * as fs from 'fs';
 import { spawn } from 'child_process';
-import CodeGraph, { getCodeGraphDir, findNearestCodeGraphRoot } from '../index';
-import type { IndexProgress } from '../index';
-import { runInstaller } from '../installer';
+import { getCodeGraphDir, findNearestCodeGraphRoot, isInitialized } from '../directory';
 import { initSentry, captureException } from '../sentry';
 
+// Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast.
+async function loadCodeGraph(): Promise<typeof import('../index')> {
+  try {
+    return await import('../index');
+  } catch (err) {
+    const msg = err instanceof Error ? err.message : String(err);
+    console.error('\x1b[31m✗\x1b[0m Failed to load CodeGraph modules.');
+    console.error(`\n  Node: ${process.version}  Platform: ${process.platform} ${process.arch}`);
+    console.error(`\n  Error: ${msg}`);
+    console.error('\n  Try reinstalling with: npm install -g @colbymchenry/codegraph\n');
+    process.exit(1);
+  }
+}
+
+type IndexProgress = import('../index').IndexProgress;
+
 // Check if running with no arguments - run installer
 // Read version for Sentry release tag
 const pkgVersion = (() => {
@@ -41,9 +55,11 @@ const pkgVersion = (() => {
 initSentry({ processName: 'codegraph-cli', version: pkgVersion });
 
 if (process.argv.length === 2) {
-  runInstaller().catch((err) => {
+  import('../installer').then(({ runInstaller }) =>
+    runInstaller()
+  ).catch((err) => {
     captureException(err);
-    console.error('Installation failed:', err.message);
+    console.error('Installation failed:', err instanceof Error ? err.message : String(err));
     process.exit(1);
   });
 } else {
@@ -117,7 +133,7 @@ function resolveProjectPath(pathArg?: string): string {
   const absolutePath = path.resolve(pathArg || process.cwd());
 
   // If exact path is initialized (has codegraph.db), use it
-  if (CodeGraph.isInitialized(absolutePath)) {
+  if (isInitialized(absolutePath)) {
     return absolutePath;
   }
 
@@ -131,7 +147,7 @@ function resolveProjectPath(pathArg?: string): string {
     if (parent === current) break;
     current = parent;
 
-    if (CodeGraph.isInitialized(current)) {
+    if (isInitialized(current)) {
       return current;
     }
   }
@@ -240,13 +256,14 @@ program
 
     try {
       // Check if already initialized
-      if (CodeGraph.isInitialized(projectPath)) {
+      if (isInitialized(projectPath)) {
         warn(`CodeGraph already initialized in ${projectPath}`);
         info('Use "codegraph index" to re-index or "codegraph sync" to update');
         return;
       }
 
       // Initialize
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.init(projectPath, {
         index: false, // We'll handle indexing ourselves for progress
       });
@@ -295,7 +312,7 @@ program
     const projectPath = resolveProjectPath(pathArg);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         warn(`CodeGraph is not initialized in ${projectPath}`);
         return;
       }
@@ -318,6 +335,7 @@ program
         }
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = CodeGraph.openSync(projectPath);
       cg.uninitialize();
 
@@ -341,12 +359,13 @@ program
     const projectPath = resolveProjectPath(pathArg);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         error(`CodeGraph not initialized in ${projectPath}`);
         info('Run "codegraph init" first');
         process.exit(1);
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.open(projectPath);
 
       if (!options.quiet) {
@@ -408,13 +427,14 @@ program
     const projectPath = resolveProjectPath(pathArg);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         if (!options.quiet) {
           error(`CodeGraph not initialized in ${projectPath}`);
         }
         process.exit(1);
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.open(projectPath);
 
       const result = await cg.sync({
@@ -467,7 +487,7 @@ program
     const projectPath = resolveProjectPath(pathArg);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         if (options.json) {
           console.log(JSON.stringify({ initialized: false, projectPath }));
           return;
@@ -479,6 +499,7 @@ program
         return;
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.open(projectPath);
       const stats = cg.getStats();
       const changes = cg.getChangedFiles();
@@ -579,11 +600,12 @@ program
     const projectPath = resolveProjectPath(options.path);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         error(`CodeGraph not initialized in ${projectPath}`);
         process.exit(1);
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.open(projectPath);
 
       const limit = parseInt(options.limit || '10', 10);
@@ -652,11 +674,12 @@ program
     const projectPath = resolveProjectPath(options.path);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         error(`CodeGraph not initialized in ${projectPath}`);
         process.exit(1);
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.open(projectPath);
       let files = cg.getFiles();
 
@@ -854,11 +877,12 @@ program
     const projectPath = resolveProjectPath(options.path);
 
     try {
-      if (!CodeGraph.isInitialized(projectPath)) {
+      if (!isInitialized(projectPath)) {
         error(`CodeGraph not initialized in ${projectPath}`);
         process.exit(1);
       }
 
+      const { default: CodeGraph } = await loadCodeGraph();
       const cg = await CodeGraph.open(projectPath);
 
       const context = await cg.buildContext(task, {
@@ -987,7 +1011,7 @@ program
       try { fs.unlinkSync(dirtyPath); } catch { /* ignore */ }
 
       // If not fully initialized (no DB), exit
-      if (!CodeGraph.isInitialized(projectRoot!)) {
+      if (!isInitialized(projectRoot!)) {
         process.exit(0);
       }
 
@@ -1018,6 +1042,7 @@ program
   .command('install')
   .description('Run interactive installer for Claude Code integration')
   .action(async () => {
+    const { runInstaller } = await import('../installer');
     await runInstaller();
   });
 

+ 8 - 6
src/db/index.ts

@@ -4,20 +4,22 @@
  * Handles SQLite database initialization and connection management.
  */
 
-import Database from 'better-sqlite3';
+import { SqliteDatabase, createDatabase } from './sqlite-adapter';
 import * as fs from 'fs';
 import * as path from 'path';
 import { SchemaVersion } from '../types';
 import { runMigrations, getCurrentVersion, CURRENT_SCHEMA_VERSION } from './migrations';
 
+export { SqliteDatabase, getActiveBackend } from './sqlite-adapter';
+
 /**
  * Database connection wrapper with lifecycle management
  */
 export class DatabaseConnection {
-  private db: Database.Database;
+  private db: SqliteDatabase;
   private dbPath: string;
 
-  private constructor(db: Database.Database, dbPath: string) {
+  private constructor(db: SqliteDatabase, dbPath: string) {
     this.db = db;
     this.dbPath = dbPath;
   }
@@ -33,7 +35,7 @@ export class DatabaseConnection {
     }
 
     // Create and configure database
-    const db = new Database(dbPath);
+    const db = createDatabase(dbPath);
 
     // Enable foreign keys and WAL mode for better performance
     db.pragma('foreign_keys = ON');
@@ -71,7 +73,7 @@ export class DatabaseConnection {
       throw new Error(`Database not found: ${dbPath}`);
     }
 
-    const db = new Database(dbPath);
+    const db = createDatabase(dbPath);
 
     // Enable foreign keys and WAL mode
     db.pragma('foreign_keys = ON');
@@ -99,7 +101,7 @@ export class DatabaseConnection {
   /**
    * Get the underlying database instance
    */
-  getDb(): Database.Database {
+  getDb(): SqliteDatabase {
     return this.db;
   }
 

+ 8 - 8
src/db/migrations.ts

@@ -4,7 +4,7 @@
  * Schema versioning and migration support.
  */
 
-import Database from 'better-sqlite3';
+import { SqliteDatabase } from './sqlite-adapter';
 
 /**
  * Current schema version
@@ -17,7 +17,7 @@ export const CURRENT_SCHEMA_VERSION = 2;
 interface Migration {
   version: number;
   description: string;
-  up: (db: Database.Database) => void;
+  up: (db: SqliteDatabase) => void;
 }
 
 /**
@@ -50,7 +50,7 @@ const migrations: Migration[] = [
 /**
  * Get the current schema version from the database
  */
-export function getCurrentVersion(db: Database.Database): number {
+export function getCurrentVersion(db: SqliteDatabase): number {
   try {
     const row = db
       .prepare('SELECT MAX(version) as version FROM schema_versions')
@@ -65,7 +65,7 @@ export function getCurrentVersion(db: Database.Database): number {
 /**
  * Record a migration as applied
  */
-function recordMigration(db: Database.Database, version: number, description: string): void {
+function recordMigration(db: SqliteDatabase, version: number, description: string): void {
   db.prepare(
     'INSERT INTO schema_versions (version, applied_at, description) VALUES (?, ?, ?)'
   ).run(version, Date.now(), description);
@@ -74,7 +74,7 @@ function recordMigration(db: Database.Database, version: number, description: st
 /**
  * Run all pending migrations
  */
-export function runMigrations(db: Database.Database, fromVersion: number): void {
+export function runMigrations(db: SqliteDatabase, fromVersion: number): void {
   const pending = migrations.filter((m) => m.version > fromVersion);
 
   if (pending.length === 0) {
@@ -96,7 +96,7 @@ export function runMigrations(db: Database.Database, fromVersion: number): void
 /**
  * Check if the database needs migration
  */
-export function needsMigration(db: Database.Database): boolean {
+export function needsMigration(db: SqliteDatabase): boolean {
   const current = getCurrentVersion(db);
   return current < CURRENT_SCHEMA_VERSION;
 }
@@ -104,7 +104,7 @@ export function needsMigration(db: Database.Database): boolean {
 /**
  * Get list of pending migrations
  */
-export function getPendingMigrations(db: Database.Database): Migration[] {
+export function getPendingMigrations(db: SqliteDatabase): Migration[] {
   const current = getCurrentVersion(db);
   return migrations
     .filter((m) => m.version > current)
@@ -115,7 +115,7 @@ export function getPendingMigrations(db: Database.Database): Migration[] {
  * Get migration history from database
  */
 export function getMigrationHistory(
-  db: Database.Database
+  db: SqliteDatabase
 ): Array<{ version: number; appliedAt: number; description: string | null }> {
   const rows = db
     .prepare('SELECT version, applied_at, description FROM schema_versions ORDER BY version')

+ 24 - 24
src/db/queries.ts

@@ -4,7 +4,7 @@
  * Prepared statements for CRUD operations on the knowledge graph.
  */
 
-import Database from 'better-sqlite3';
+import { SqliteDatabase, SqliteStatement } from './sqlite-adapter';
 import {
   Node,
   Edge,
@@ -143,7 +143,7 @@ function rowToFileRecord(row: FileRow): FileRecord {
  * Query builder for the knowledge graph database
  */
 export class QueryBuilder {
-  private db: Database.Database;
+  private db: SqliteDatabase;
 
   // Node cache for frequently accessed nodes (LRU-style, max 1000 entries)
   private nodeCache: Map<string, Node> = new Map();
@@ -151,30 +151,30 @@ export class QueryBuilder {
 
   // Prepared statements (lazily initialized)
   private stmts: {
-    insertNode?: Database.Statement;
-    updateNode?: Database.Statement;
-    deleteNode?: Database.Statement;
-    deleteNodesByFile?: Database.Statement;
-    getNodeById?: Database.Statement;
-    getNodesByFile?: Database.Statement;
-    getNodesByKind?: Database.Statement;
-    insertEdge?: Database.Statement;
-    upsertFile?: Database.Statement;
-    deleteEdgesBySource?: Database.Statement;
-    deleteEdgesByTarget?: Database.Statement;
-    getEdgesBySource?: Database.Statement;
-    getEdgesByTarget?: Database.Statement;
-    insertFile?: Database.Statement;
-    updateFile?: Database.Statement;
-    deleteFile?: Database.Statement;
-    getFileByPath?: Database.Statement;
-    getAllFiles?: Database.Statement;
-    insertUnresolved?: Database.Statement;
-    deleteUnresolvedByNode?: Database.Statement;
-    getUnresolvedByName?: Database.Statement;
+    insertNode?: SqliteStatement;
+    updateNode?: SqliteStatement;
+    deleteNode?: SqliteStatement;
+    deleteNodesByFile?: SqliteStatement;
+    getNodeById?: SqliteStatement;
+    getNodesByFile?: SqliteStatement;
+    getNodesByKind?: SqliteStatement;
+    insertEdge?: SqliteStatement;
+    upsertFile?: SqliteStatement;
+    deleteEdgesBySource?: SqliteStatement;
+    deleteEdgesByTarget?: SqliteStatement;
+    getEdgesBySource?: SqliteStatement;
+    getEdgesByTarget?: SqliteStatement;
+    insertFile?: SqliteStatement;
+    updateFile?: SqliteStatement;
+    deleteFile?: SqliteStatement;
+    getFileByPath?: SqliteStatement;
+    getAllFiles?: SqliteStatement;
+    insertUnresolved?: SqliteStatement;
+    deleteUnresolvedByNode?: SqliteStatement;
+    getUnresolvedByName?: SqliteStatement;
   } = {};
 
-  constructor(db: Database.Database) {
+  constructor(db: SqliteDatabase) {
     this.db = db;
   }
 

+ 227 - 0
src/db/sqlite-adapter.ts

@@ -0,0 +1,227 @@
+/**
+ * SQLite Adapter
+ *
+ * Provides a unified interface over better-sqlite3 (native) and
+ * node-sqlite3-wasm (WASM fallback) for universal cross-platform support.
+ */
+
+export interface SqliteStatement {
+  run(...params: any[]): { changes: number; lastInsertRowid: number | bigint };
+  get(...params: any[]): any;
+  all(...params: any[]): any[];
+}
+
+export interface SqliteDatabase {
+  prepare(sql: string): SqliteStatement;
+  exec(sql: string): void;
+  pragma(str: string): any;
+  transaction<T>(fn: (...args: any[]) => T): (...args: any[]) => T;
+  close(): void;
+  readonly open: boolean;
+}
+
+export type SqliteBackend = 'native' | 'wasm';
+
+let activeBackend: SqliteBackend | null = null;
+
+/**
+ * Get the currently active SQLite backend.
+ */
+export function getActiveBackend(): SqliteBackend | null {
+  return activeBackend;
+}
+
+/**
+ * Translate @named parameters (better-sqlite3 style) to positional ? params
+ * for node-sqlite3-wasm, which only supports positional binding.
+ *
+ * Returns the rewritten SQL and an ordered list of parameter names.
+ * If no named params are found, returns null for paramOrder (positional mode).
+ */
+function translateNamedParams(sql: string): { sql: string; paramOrder: string[] | null } {
+  const paramOrder: string[] = [];
+  const rewritten = sql.replace(/@(\w+)/g, (_match, name: string) => {
+    paramOrder.push(name);
+    return '?';
+  });
+  if (paramOrder.length === 0) {
+    return { sql, paramOrder: null };
+  }
+  return { sql: rewritten, paramOrder };
+}
+
+/**
+ * Convert better-sqlite3-style params to a positional array for node-sqlite3-wasm.
+ *
+ * Handles three calling conventions:
+ * - Named object: run({ id: '1', name: 'a' }) → positional array via paramOrder
+ * - Positional args: run('a', 'b') → ['a', 'b']
+ * - No args: run() → undefined
+ */
+function resolveParams(params: any[], paramOrder: string[] | null): any {
+  if (params.length === 0) return undefined;
+
+  // If paramOrder exists and first arg is a plain object, do named→positional translation
+  if (paramOrder && params.length === 1 && params[0] !== null && typeof params[0] === 'object' && !Array.isArray(params[0]) && !(params[0] instanceof Buffer) && !(params[0] instanceof Uint8Array)) {
+    const obj = params[0];
+    return paramOrder.map(name => obj[name]);
+  }
+
+  // Positional: single value or already an array
+  if (params.length === 1) return params[0];
+  return params;
+}
+
+/**
+ * Wraps node-sqlite3-wasm to match the better-sqlite3 interface.
+ *
+ * Key differences handled:
+ * - better-sqlite3 uses @named params; node-sqlite3-wasm uses positional ? only
+ * - better-sqlite3 uses variadic args: stmt.run(a, b, c)
+ * - node-sqlite3-wasm uses a single array/object: stmt.run([a, b, c])
+ * - node-sqlite3-wasm has `isOpen` instead of `open`
+ * - node-sqlite3-wasm doesn't have a `pragma()` method
+ * - node-sqlite3-wasm doesn't have a `transaction()` method
+ */
+class WasmDatabaseAdapter implements SqliteDatabase {
+  private _db: any;
+  // Track raw WASM statements so we can finalize them on close.
+  // node-sqlite3-wasm won't release its file lock if statements are left open.
+  private _openStmts = new Set<any>();
+
+  constructor(dbPath: string) {
+    // eslint-disable-next-line @typescript-eslint/no-require-imports
+    const { Database } = require('node-sqlite3-wasm');
+    this._db = new Database(dbPath);
+  }
+
+  get open(): boolean {
+    return this._db.isOpen;
+  }
+
+  prepare(sql: string): SqliteStatement {
+    const { sql: rewrittenSql, paramOrder } = translateNamedParams(sql);
+    const stmt = this._db.prepare(rewrittenSql);
+    this._openStmts.add(stmt);
+    return {
+      run(...params: any[]) {
+        const resolved = resolveParams(params, paramOrder);
+        const result = resolved !== undefined ? stmt.run(resolved) : stmt.run();
+        return {
+          changes: result?.changes ?? 0,
+          lastInsertRowid: result?.lastInsertRowid ?? 0,
+        };
+      },
+      get(...params: any[]) {
+        const resolved = resolveParams(params, paramOrder);
+        return resolved !== undefined ? stmt.get(resolved) : stmt.get();
+      },
+      all(...params: any[]) {
+        const resolved = resolveParams(params, paramOrder);
+        return resolved !== undefined ? stmt.all(resolved) : stmt.all();
+      },
+    };
+  }
+
+  exec(sql: string): void {
+    this._db.exec(sql);
+  }
+
+  pragma(str: string): any {
+    const trimmed = str.trim();
+
+    // Write pragma: "key = value"
+    if (trimmed.includes('=')) {
+      const eqIdx = trimmed.indexOf('=');
+      const key = trimmed.substring(0, eqIdx).trim();
+      const value = trimmed.substring(eqIdx + 1).trim();
+
+      // WAL is not supported in WASM SQLite — use DELETE journal mode
+      if (key === 'journal_mode' && value.toUpperCase() === 'WAL') {
+        this._db.exec('PRAGMA journal_mode = DELETE');
+        return;
+      }
+
+      // mmap is not available in WASM — silently skip
+      if (key === 'mmap_size') {
+        return;
+      }
+
+      // synchronous = NORMAL is unsafe without WAL — use FULL
+      if (key === 'synchronous' && value.toUpperCase() === 'NORMAL') {
+        this._db.exec('PRAGMA synchronous = FULL');
+        return;
+      }
+
+      this._db.exec(`PRAGMA ${key} = ${value}`);
+      return;
+    }
+
+    // Read pragma: "key" — return the value
+    const stmt = this._db.prepare(`PRAGMA ${trimmed}`);
+    const result = stmt.get();
+    stmt.finalize();
+    return result;
+  }
+
+  transaction<T>(fn: (...args: any[]) => T): (...args: any[]) => T {
+    return (...args: any[]) => {
+      this._db.exec('BEGIN');
+      try {
+        const result = fn(...args);
+        this._db.exec('COMMIT');
+        return result;
+      } catch (error) {
+        this._db.exec('ROLLBACK');
+        throw error;
+      }
+    };
+  }
+
+  close(): void {
+    // Finalize all tracked statements before closing.
+    // node-sqlite3-wasm won't release its directory-based file lock
+    // if any prepared statements remain open.
+    for (const stmt of this._openStmts) {
+      try { stmt.finalize(); } catch { /* already finalized */ }
+    }
+    this._openStmts.clear();
+    this._db.close();
+  }
+}
+
+/**
+ * Create a database connection. Tries native better-sqlite3 first,
+ * falls back to node-sqlite3-wasm.
+ */
+export function createDatabase(dbPath: string): SqliteDatabase {
+  let nativeError: string | undefined;
+  let wasmError: string | undefined;
+
+  // Try native better-sqlite3 first
+  try {
+    // eslint-disable-next-line @typescript-eslint/no-require-imports
+    const Database = require('better-sqlite3');
+    const db = new Database(dbPath);
+    activeBackend = 'native';
+    return db as SqliteDatabase;
+  } catch (error) {
+    nativeError = error instanceof Error ? error.message : String(error);
+  }
+
+  // Fall back to WASM
+  try {
+    const db = new WasmDatabaseAdapter(dbPath);
+    activeBackend = 'wasm';
+    console.warn('[CodeGraph] Using WASM SQLite backend (native better-sqlite3 unavailable)');
+    return db;
+  } catch (error) {
+    wasmError = error instanceof Error ? error.message : String(error);
+  }
+
+  throw new Error(
+    `Failed to load any SQLite backend.\n` +
+    `  Native (better-sqlite3): ${nativeError}\n` +
+    `  WASM (node-sqlite3-wasm): ${wasmError}`
+  );
+}

+ 66 - 108
src/extraction/grammars.ts

@@ -1,87 +1,37 @@
 /**
  * Grammar Loading and Caching
  *
- * Uses lazy per-language loading so one missing native grammar does not
- * break extraction for all other languages.
+ * Uses web-tree-sitter (WASM) for universal cross-platform support.
+ * All grammars are pre-loaded asynchronously via initGrammars(), then
+ * getParser() returns synchronously from cache.
  */
 
-import Parser from 'tree-sitter';
+import { Parser, Language as WasmLanguage } from 'web-tree-sitter';
 import { Language } from '../types';
 
-type GrammarLoader = () => unknown;
 type GrammarLanguage = Exclude<Language, 'svelte' | 'liquid' | 'unknown'>;
 
 /**
- * Lazy grammar loaders — each language's native binding is only loaded
- * on first use, so a failure in one grammar doesn't affect others.
+ * WASM filename map — maps each language to its .wasm grammar file
+ * in the tree-sitter-wasms package.
  */
-const grammarLoaders: Record<GrammarLanguage, GrammarLoader> = {
-  typescript: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-typescript').typescript;
-  },
-  tsx: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-typescript').tsx;
-  },
-  javascript: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-javascript');
-  },
-  jsx: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-javascript');
-  },
-  python: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-python');
-  },
-  go: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-go');
-  },
-  rust: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-rust');
-  },
-  java: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-java');
-  },
-  c: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-c');
-  },
-  cpp: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-cpp');
-  },
-  csharp: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-c-sharp');
-  },
-  php: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-php').php;
-  },
-  ruby: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-ruby');
-  },
-  swift: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-swift');
-  },
-  kotlin: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('tree-sitter-kotlin');
-  },
-  dart: () => {
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    return require('@sengac/tree-sitter-dart');
-  },
-  // Note: tree-sitter-liquid has ABI compatibility issues with tree-sitter 0.22+
-  // Liquid extraction is handled separately via regex in tree-sitter.ts
+const WASM_GRAMMAR_FILES: Record<GrammarLanguage, string> = {
+  typescript: 'tree-sitter-typescript.wasm',
+  tsx: 'tree-sitter-tsx.wasm',
+  javascript: 'tree-sitter-javascript.wasm',
+  jsx: 'tree-sitter-javascript.wasm',
+  python: 'tree-sitter-python.wasm',
+  go: 'tree-sitter-go.wasm',
+  rust: 'tree-sitter-rust.wasm',
+  java: 'tree-sitter-java.wasm',
+  c: 'tree-sitter-c.wasm',
+  cpp: 'tree-sitter-cpp.wasm',
+  csharp: 'tree-sitter-c_sharp.wasm',
+  php: 'tree-sitter-php.wasm',
+  ruby: 'tree-sitter-ruby.wasm',
+  swift: 'tree-sitter-swift.wasm',
+  kotlin: 'tree-sitter-kotlin.wasm',
+  dart: 'tree-sitter-dart.wasm',
 };
 
 /**
@@ -122,55 +72,62 @@ export const EXTENSION_MAP: Record<string, Language> = {
  * Caches for loaded grammars and parsers
  */
 const parserCache = new Map<Language, Parser>();
-const grammarCache = new Map<Language, unknown | null>();
+const languageCache = new Map<Language, WasmLanguage>();
 const unavailableGrammarErrors = new Map<Language, string>();
 
+let grammarsInitialized = false;
+
 /**
- * Load a grammar on demand, caching the result.
- * Returns null if the grammar is not available on this platform.
+ * Initialize all WASM grammars. Must be called before any parsing.
+ * Idempotent — safe to call multiple times.
  */
-function loadGrammar(language: Language): unknown | null {
-  if (grammarCache.has(language)) {
-    return grammarCache.get(language) ?? null;
-  }
-
-  const loader = grammarLoaders[language as GrammarLanguage];
-  if (!loader) {
-    grammarCache.set(language, null);
-    return null;
-  }
+export async function initGrammars(): Promise<void> {
+  if (grammarsInitialized) return;
+
+  await Parser.init();
+
+  // Load all grammars in parallel
+  const entries = Object.entries(WASM_GRAMMAR_FILES) as [GrammarLanguage, string][];
+  await Promise.allSettled(
+    entries.map(async ([lang, wasmFile]) => {
+      try {
+        const wasmPath = require.resolve(`tree-sitter-wasms/out/${wasmFile}`);
+        const language = await WasmLanguage.load(wasmPath);
+        languageCache.set(lang, language);
+      } catch (error) {
+        const message = error instanceof Error ? error.message : String(error);
+        console.warn(`[CodeGraph] Failed to load ${lang} grammar — parsing will be unavailable: ${message}`);
+        unavailableGrammarErrors.set(lang, message);
+      }
+    })
+  );
+
+  grammarsInitialized = true;
+}
 
-  try {
-    const grammar = loader();
-    if (!grammar) {
-      throw new Error(`Grammar loader returned empty value for ${language}`);
-    }
-    grammarCache.set(language, grammar);
-    return grammar;
-  } catch (error) {
-    const message = error instanceof Error ? error.message : String(error);
-    console.warn(`[CodeGraph] Failed to load ${language} grammar — parsing will be unavailable: ${message}`);
-    unavailableGrammarErrors.set(language, message);
-    grammarCache.set(language, null);
-    return null;
-  }
+/**
+ * Check if grammars have been initialized
+ */
+export function isGrammarsInitialized(): boolean {
+  return grammarsInitialized;
 }
 
 /**
- * Get a parser for the specified language
+ * Get a parser for the specified language.
+ * Returns synchronously from pre-loaded cache.
  */
 export function getParser(language: Language): Parser | null {
   if (parserCache.has(language)) {
     return parserCache.get(language)!;
   }
 
-  const grammar = loadGrammar(language);
-  if (!grammar) {
+  const lang = languageCache.get(language);
+  if (!lang) {
     return null;
   }
 
   const parser = new Parser();
-  parser.setLanguage(grammar as Parameters<typeof parser.setLanguage>[0]);
+  parser.setLanguage(lang);
   parserCache.set(language, parser);
   return parser;
 }
@@ -190,15 +147,15 @@ export function isLanguageSupported(language: Language): boolean {
   if (language === 'svelte') return true; // custom extractor (script block delegation)
   if (language === 'liquid') return true; // custom regex extractor
   if (language === 'unknown') return false;
-  return loadGrammar(language) !== null;
+  return languageCache.has(language);
 }
 
 /**
  * Get all currently supported languages.
  */
 export function getSupportedLanguages(): Language[] {
-  const available = (Object.keys(grammarLoaders) as GrammarLanguage[])
-    .filter((language) => loadGrammar(language) !== null);
+  const available = (Object.keys(WASM_GRAMMAR_FILES) as GrammarLanguage[])
+    .filter((language) => languageCache.has(language));
   return [...available, 'svelte', 'liquid'];
 }
 
@@ -207,7 +164,8 @@ export function getSupportedLanguages(): Language[] {
  */
 export function clearParserCache(): void {
   parserCache.clear();
-  grammarCache.clear();
+  // Note: languageCache is NOT cleared — WASM languages persist.
+  // To fully re-init, set grammarsInitialized = false and call initGrammars() again.
   unavailableGrammarErrors.clear();
 }
 

+ 4 - 2
src/extraction/index.ts

@@ -18,7 +18,7 @@ import {
 } from '../types';
 import { QueryBuilder } from '../db/queries';
 import { extractFromSource } from './tree-sitter';
-import { detectLanguage, isLanguageSupported } from './grammars';
+import { detectLanguage, isLanguageSupported, initGrammars } from './grammars';
 import { logDebug, logWarn } from '../errors';
 import { captureException } from '../sentry';
 import { validatePathWithinRoot, normalizePath } from '../utils';
@@ -342,6 +342,7 @@ export class ExtractionOrchestrator {
     onProgress?: (progress: IndexProgress) => void,
     signal?: AbortSignal
   ): Promise<IndexResult> {
+    await initGrammars();
     const startTime = Date.now();
     const errors: ExtractionError[] = [];
     let filesIndexed = 0;
@@ -682,6 +683,7 @@ export class ExtractionOrchestrator {
    * Uses git status as a fast path when available, falling back to full scan.
    */
   async sync(onProgress?: (progress: IndexProgress) => void): Promise<SyncResult> {
+    await initGrammars();
     const startTime = Date.now();
     let filesChecked = 0;
     let filesAdded = 0;
@@ -918,4 +920,4 @@ export class ExtractionOrchestrator {
 
 // Re-export useful types and functions
 export { extractFromSource } from './tree-sitter';
-export { detectLanguage, isLanguageSupported, getSupportedLanguages } from './grammars';
+export { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars } from './grammars';

+ 13 - 4
src/extraction/tree-sitter.ts

@@ -4,7 +4,7 @@
  * Handles parsing source code and extracting structural information.
  */
 
-import { SyntaxNode, Tree } from 'tree-sitter';
+import { Node as SyntaxNode, Tree } from 'web-tree-sitter';
 import * as crypto from 'crypto';
 import * as path from 'path';
 import {
@@ -875,7 +875,10 @@ export class TreeSitterExtractor {
     }
 
     try {
-      this.tree = parser.parse(this.source);
+      this.tree = parser.parse(this.source) ?? null;
+      if (!this.tree) {
+        throw new Error('Parser returned null tree');
+      }
 
       // Create file node representing the source file
       const fileNode: Node = {
@@ -1710,9 +1713,15 @@ export class TreeSitterExtractor {
       if (namespacePrefix && useGroup) {
         // Grouped import - create one import per item
         const prefix = getNodeText(namespacePrefix, this.source);
-        const useClauses = useGroup.namedChildren.filter((c: SyntaxNode) => c.type === 'namespace_use_clause');
+        const useClauses = useGroup.namedChildren.filter((c: SyntaxNode) =>
+          c.type === 'namespace_use_group_clause' || c.type === 'namespace_use_clause'
+        );
         for (const clause of useClauses) {
-          const name = clause.namedChildren.find((c: SyntaxNode) => c.type === 'name');
+          // WASM grammar wraps names in namespace_name; native uses name directly
+          const nsName = clause.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_name');
+          const name = nsName
+            ? nsName.namedChildren.find((c: SyntaxNode) => c.type === 'name')
+            : clause.namedChildren.find((c: SyntaxNode) => c.type === 'name');
           if (name) {
             const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
             this.createNode('import', fullPath, node, {

+ 4 - 1
src/index.ts

@@ -38,6 +38,7 @@ import {
   IndexResult,
   SyncResult,
   extractFromSource,
+  initGrammars,
 } from './extraction';
 import {
   ReferenceResolver,
@@ -60,7 +61,7 @@ export {
   CODEGRAPH_DIR,
 } from './directory';
 export { IndexProgress, IndexResult, SyncResult } from './extraction';
-export { detectLanguage, isLanguageSupported, getSupportedLanguages } from './extraction';
+export { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars } from './extraction';
 export { ResolutionResult } from './resolution';
 export { EmbeddingProgress } from './vectors';
 export {
@@ -184,6 +185,7 @@ export class CodeGraph {
    * @returns A new CodeGraph instance
    */
   static async init(projectRoot: string, options: InitOptions = {}): Promise<CodeGraph> {
+    await initGrammars();
     const resolvedRoot = path.resolve(projectRoot);
 
     // Check if already initialized
@@ -253,6 +255,7 @@ export class CodeGraph {
    * @returns A CodeGraph instance
    */
   static async open(projectRoot: string, options: OpenOptions = {}): Promise<CodeGraph> {
+    await initGrammars();
     const resolvedRoot = path.resolve(projectRoot);
 
     // Check if initialized

+ 3 - 2
src/installer/banner.ts

@@ -113,15 +113,16 @@ export function warn(message: string): void {
 /**
  * Show the "next steps" section after installation
  */
-export function showNextSteps(location: 'global' | 'local'): void {
+export function showNextSteps(location: 'global' | 'local', useNpx?: boolean): void {
   console.log();
   console.log(chalk.bold('  Done!') + ' Restart Claude Code to use CodeGraph.');
   console.log();
 
   if (location === 'global') {
+    const cmd = useNpx ? 'npx @colbymchenry/codegraph' : 'codegraph';
     console.log(chalk.dim('  Quick start:'));
     console.log(chalk.dim('    cd your-project'));
-    console.log(chalk.cyan('    codegraph init -i'));
+    console.log(chalk.cyan(`    ${cmd} init -i`));
   } else {
     console.log(chalk.dim('  CodeGraph is ready to use in this project!'));
   }

+ 15 - 4
src/installer/config-writer.ts

@@ -97,19 +97,29 @@ function writeJsonFile(filePath: string, data: Record<string, any>): void {
   atomicWriteFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
 }
 
+/**
+ * When true, all configs use `npx @colbymchenry/codegraph` instead of the
+ * bare `codegraph` command.  Set by the installer when global install fails.
+ */
+let useNpxFallback = false;
+
+export function setUseNpxFallback(value: boolean): void {
+  useNpxFallback = value;
+}
+
 /**
  * Get the MCP server configuration for the given location
  */
 function getMcpServerConfig(location: InstallLocation): Record<string, any> {
-  if (location === 'global') {
-    // Global: use 'codegraph' command directly (assumes globally installed)
+  if (location === 'global' && !useNpxFallback) {
+    // Global: use 'codegraph' command directly (globally installed and in PATH)
     return {
       type: 'stdio',
       command: 'codegraph',
       args: ['serve', '--mcp'],
     };
   }
-  // Local: use npx to run the package
+  // Local or npx fallback: use npx to run the package
   return {
     type: 'stdio',
     command: 'npx',
@@ -212,7 +222,7 @@ export function hasPermissions(location: InstallLocation): boolean {
  * Stop → sync-if-dirty (sync, ensures fresh index before next user turn)
  */
 function getHooksConfig(location: InstallLocation): Record<string, any> {
-  const command = location === 'global' ? 'codegraph' : 'npx @colbymchenry/codegraph';
+  const command = (location === 'global' && !useNpxFallback) ? 'codegraph' : 'npx @colbymchenry/codegraph';
 
   return {
     PostToolUse: [
@@ -229,6 +239,7 @@ function getHooksConfig(location: InstallLocation): Record<string, any> {
     ],
     Stop: [
       {
+        matcher: '.*',
         hooks: [
           {
             type: 'command',

+ 24 - 5
src/installer/index.ts

@@ -8,8 +8,7 @@
 import { execSync } from 'child_process';
 import { showBanner, showNextSteps, success, error, info, chalk } from './banner';
 import { promptInstallLocation, promptAutoAllow, InstallLocation } from './prompts';
-import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks } from './config-writer';
-import CodeGraph from '../index';
+import { writeMcpConfig, writePermissions, writeClaudeMd, writeHooks, hasMcpConfig, hasPermissions, hasHooks, setUseNpxFallback } from './config-writer';
 
 /**
  * Format a number with commas
@@ -29,7 +28,8 @@ export async function runInstaller(): Promise<void> {
     // Step 1: Check if codegraph is available (skip install if already there)
     let codegraphAvailable = false;
     try {
-      execSync('which codegraph', { stdio: 'pipe' });
+      const checkCmd = process.platform === 'win32' ? 'where codegraph' : 'command -v codegraph';
+      execSync(checkCmd, { stdio: 'pipe' });
       codegraphAvailable = true;
     } catch {
       // Not installed globally yet
@@ -40,13 +40,20 @@ export async function runInstaller(): Promise<void> {
       try {
         execSync('npm install -g @colbymchenry/codegraph', { stdio: 'pipe' });
         success('Installed codegraph command globally');
+        codegraphAvailable = true;
       } catch {
         // May fail if no permissions, but that's ok - npx still works
-        info('Could not install globally (try with sudo if needed)');
+        info('Could not install globally — will use npx instead');
+        info('(MCP server and hooks will use npx @colbymchenry/codegraph)');
       }
       console.log();
     }
 
+    // If codegraph binary isn't in PATH, tell config-writer to use npx for everything
+    if (!codegraphAvailable) {
+      setUseNpxFallback(true);
+    }
+
     // Step 2: Ask for installation location
     const location = await promptInstallLocation();
     console.log();
@@ -104,7 +111,7 @@ export async function runInstaller(): Promise<void> {
     }
 
     // Show next steps
-    showNextSteps(location);
+    showNextSteps(location, !codegraphAvailable);
   } catch (err) {
     console.log();
     if (err instanceof Error && err.message.includes('readline was closed')) {
@@ -123,6 +130,18 @@ export async function runInstaller(): Promise<void> {
 async function initializeLocalProject(): Promise<void> {
   const projectPath = process.cwd();
 
+  // Lazy-load CodeGraph (requires native modules)
+  let CodeGraph: typeof import('../index').default;
+  try {
+    CodeGraph = (await import('../index')).default;
+  } catch (err) {
+    const msg = err instanceof Error ? err.message : String(err);
+    error(`Could not load native modules: ${msg}`);
+    info('Skipping project initialization. You can run "codegraph init -i" later.');
+    info('If this persists, try a Node.js LTS version (20 or 22).');
+    return;
+  }
+
   // Check if already initialized
   if (CodeGraph.isInitialized(projectPath)) {
     info('CodeGraph already initialized in this project');

+ 6 - 8
src/mcp/tools.ts

@@ -12,6 +12,9 @@ import { clamp } from '../utils';
 import { tmpdir } from 'os';
 import { join } from 'path';
 
+/** Maximum output length to prevent context bloat (characters) */
+const MAX_OUTPUT_LENGTH = 15000;
+
 /**
  * Mark a Claude session as having consulted MCP tools.
  * This enables Grep/Glob/Bash commands that would otherwise be blocked.
@@ -820,19 +823,14 @@ export class ToolHandler {
     return { node: results[0]!.node, note: '' };
   }
 
-  /**
-   * Maximum output length to prevent context bloat (characters)
-   */
-  private readonly MAX_OUTPUT_LENGTH = 15000;
-
   /**
    * Truncate output if it exceeds the maximum length
    */
   private truncateOutput(text: string): string {
-    if (text.length <= this.MAX_OUTPUT_LENGTH) return text;
-    const truncated = text.slice(0, this.MAX_OUTPUT_LENGTH);
+    if (text.length <= MAX_OUTPUT_LENGTH) return text;
+    const truncated = text.slice(0, MAX_OUTPUT_LENGTH);
     const lastNewline = truncated.lastIndexOf('\n');
-    const cutPoint = lastNewline > this.MAX_OUTPUT_LENGTH * 0.8 ? lastNewline : this.MAX_OUTPUT_LENGTH;
+    const cutPoint = lastNewline > MAX_OUTPUT_LENGTH * 0.8 ? lastNewline : MAX_OUTPUT_LENGTH;
     return truncated.slice(0, cutPoint) + '\n\n... (output truncated)';
   }
 

+ 1 - 1
src/resolution/import-resolver.ts

@@ -105,7 +105,7 @@ function resolveRelativeImport(
 
   // Try the path as-is first
   const basePath = path.resolve(fromDir, importPath);
-  const relativePath = path.relative(projectRoot, basePath);
+  const relativePath = path.relative(projectRoot, basePath).replace(/\\/g, '/');
 
   // Try each extension
   for (const ext of extensions) {

+ 3 - 3
src/vectors/manager.ts

@@ -4,7 +4,7 @@
  * High-level manager that coordinates embedding generation and vector search.
  */
 
-import Database from 'better-sqlite3';
+import { SqliteDatabase } from '../db/sqlite-adapter';
 import { Node, SearchResult, SearchOptions } from '../types';
 import { TextEmbedder, createEmbedder, EmbedderOptions, EMBEDDING_DIMENSION } from './embedder';
 import { VectorSearchManager, createVectorSearch } from './search';
@@ -68,7 +68,7 @@ export class VectorManager {
   private initialized = false;
 
   constructor(
-    db: Database.Database,
+    db: SqliteDatabase,
     queries: QueryBuilder,
     options: VectorManagerOptions = {}
   ) {
@@ -355,7 +355,7 @@ export class VectorManager {
  * Create a vector manager
  */
 export function createVectorManager(
-  db: Database.Database,
+  db: SqliteDatabase,
   queries: QueryBuilder,
   options?: VectorManagerOptions
 ): VectorManager {

+ 7 - 6
src/vectors/search.ts

@@ -5,7 +5,7 @@
  * Falls back to brute-force cosine similarity if sqlite-vss is not available.
  */
 
-import Database from 'better-sqlite3';
+import { SqliteDatabase } from '../db/sqlite-adapter';
 import { Node } from '../types';
 import { TextEmbedder, EMBEDDING_DIMENSION } from './embedder';
 
@@ -29,11 +29,11 @@ export interface VectorSearchOptions {
  * Handles vector storage and similarity search for semantic code search.
  */
 export class VectorSearchManager {
-  private db: Database.Database;
+  private db: SqliteDatabase;
   private vssEnabled = false;
   private embeddingDimension: number;
 
-  constructor(db: Database.Database, dimension: number = EMBEDDING_DIMENSION) {
+  constructor(db: SqliteDatabase, dimension: number = EMBEDDING_DIMENSION) {
     this.db = db;
     this.embeddingDimension = dimension;
   }
@@ -75,10 +75,11 @@ export class VectorSearchManager {
       const vss = await import('sqlite-vss');
 
       // Use the load function which loads both vector0 and vss0
+      // VSS extension expects the raw better-sqlite3 Database instance
       if (typeof vss.load === 'function') {
-        vss.load(this.db);
+        vss.load(this.db as any);
       } else if (typeof vss.default?.load === 'function') {
-        vss.default.load(this.db);
+        vss.default.load(this.db as any);
       } else {
         throw new Error('sqlite-vss load function not found');
       }
@@ -464,7 +465,7 @@ export class VectorSearchManager {
  * Create a vector search manager
  */
 export function createVectorSearch(
-  db: Database.Database,
+  db: SqliteDatabase,
   dimension?: number
 ): VectorSearchManager {
   return new VectorSearchManager(db, dimension);

+ 182 - 0
src/web-tree-sitter.d.ts

@@ -0,0 +1,182 @@
+/**
+ * Local type override for web-tree-sitter.
+ *
+ * The upstream types declare children/namedChildren as (Node | null)[],
+ * but in practice they never contain null entries. This override uses
+ * non-nullable arrays to match native tree-sitter's API and avoid
+ * pervasive null-check changes across the extraction pipeline.
+ *
+ * This file takes precedence over node_modules/web-tree-sitter/web-tree-sitter.d.ts
+ * because TypeScript resolves local declarations first.
+ */
+declare module 'web-tree-sitter' {
+  export interface Point {
+    row: number;
+    column: number;
+  }
+
+  export interface Range {
+    startPosition: Point;
+    endPosition: Point;
+    startIndex: number;
+    endIndex: number;
+  }
+
+  export interface Edit {
+    startPosition: Point;
+    oldEndPosition: Point;
+    newEndPosition: Point;
+    startIndex: number;
+    oldEndIndex: number;
+    newEndIndex: number;
+  }
+
+  export type ParseCallback = (index: number, position: Point) => string | undefined;
+
+  export interface ParseOptions {
+    includedRanges?: Range[];
+    progressCallback?: (state: { currentOffset: number; hasError: boolean }) => void;
+  }
+
+  export interface EmscriptenModule {
+    [key: string]: any;
+  }
+
+  export class Parser {
+    language: Language | null;
+    static init(moduleOptions?: EmscriptenModule): Promise<void>;
+    constructor();
+    delete(): void;
+    setLanguage(language: Language | null): this;
+    parse(callback: string | ParseCallback, oldTree?: Tree | null, options?: ParseOptions): Tree | null;
+    reset(): void;
+    getIncludedRanges(): Range[];
+    getTimeoutMicros(): number;
+    setTimeoutMicros(timeout: number): void;
+    setLogger(callback: ((message: string, isLex: boolean) => void) | boolean | null): this;
+    getLogger(): ((message: string, isLex: boolean) => void) | null;
+  }
+
+  export class Language {
+    types: string[];
+    fields: (string | null)[];
+    get name(): string | null;
+    get version(): number;
+    get abiVersion(): number;
+    get fieldCount(): number;
+    get stateCount(): number;
+    fieldIdForName(fieldName: string): number | null;
+    fieldNameForId(fieldId: number): string | null;
+    idForNodeType(type: string, named: boolean): number | null;
+    get nodeTypeCount(): number;
+    nodeTypeForId(typeId: number): string | null;
+    nodeTypeIsNamed(typeId: number): boolean;
+    nodeTypeIsVisible(typeId: number): boolean;
+    get supertypes(): number[];
+    subtypes(supertype: number): number[];
+    nextState(stateId: number, typeId: number): number;
+    lookaheadIterator(stateId: number): any;
+    query(source: string): any;
+    static load(input: string | Uint8Array): Promise<Language>;
+  }
+
+  export class Tree {
+    language: Language;
+    copy(): Tree;
+    delete(): void;
+    get rootNode(): Node;
+    rootNodeWithOffset(offsetBytes: number, offsetExtent: Point): Node;
+    edit(edit: Edit): void;
+    walk(): TreeCursor;
+    getChangedRanges(other: Tree): Range[];
+    getIncludedRanges(): Range[];
+  }
+
+  export class Node {
+    id: number;
+    startIndex: number;
+    startPosition: Point;
+    tree: Tree;
+    get typeId(): number;
+    get grammarId(): number;
+    get type(): string;
+    get grammarType(): string;
+    get isNamed(): boolean;
+    get isExtra(): boolean;
+    get isError(): boolean;
+    get isMissing(): boolean;
+    get hasChanges(): boolean;
+    get hasError(): boolean;
+    get endIndex(): number;
+    get endPosition(): Point;
+    get text(): string;
+    get parseState(): number;
+    get nextParseState(): number;
+    equals(other: Node): boolean;
+    child(index: number): Node | null;
+    namedChild(index: number): Node | null;
+    childForFieldId(fieldId: number): Node | null;
+    childForFieldName(fieldName: string): Node | null;
+    fieldNameForChild(index: number): string | null;
+    fieldNameForNamedChild(index: number): string | null;
+    childrenForFieldName(fieldName: string): Node[];
+    childrenForFieldId(fieldId: number): Node[];
+    firstChildForIndex(index: number): Node | null;
+    firstNamedChildForIndex(index: number): Node | null;
+    get childCount(): number;
+    get namedChildCount(): number;
+    get firstChild(): Node | null;
+    get firstNamedChild(): Node | null;
+    get lastChild(): Node | null;
+    get lastNamedChild(): Node | null;
+    // Override: non-nullable arrays (tree-sitter never returns null in these)
+    get children(): Node[];
+    get namedChildren(): Node[];
+    descendantsOfType(types: string | string[], startPosition?: Point, endPosition?: Point): Node[];
+    get nextSibling(): Node | null;
+    get previousSibling(): Node | null;
+    get nextNamedSibling(): Node | null;
+    get previousNamedSibling(): Node | null;
+    get descendantCount(): number;
+    get parent(): Node | null;
+    childWithDescendant(descendant: Node): Node | null;
+    descendantForIndex(start: number, end?: number): Node | null;
+    namedDescendantForIndex(start: number, end?: number): Node | null;
+    descendantForPosition(start: Point, end?: Point): Node | null;
+    namedDescendantForPosition(start: Point, end?: Point): Node | null;
+    walk(): TreeCursor;
+    edit(edit: Edit): void;
+    toString(): string;
+  }
+
+  export class TreeCursor {
+    copy(): TreeCursor;
+    delete(): void;
+    get currentNode(): Node;
+    get currentFieldId(): number;
+    get currentFieldName(): string | null;
+    get currentDepth(): number;
+    get currentDescendantIndex(): number;
+    get nodeType(): string;
+    get nodeTypeId(): number;
+    get nodeStateId(): number;
+    get nodeId(): number;
+    get nodeIsNamed(): boolean;
+    get nodeIsMissing(): boolean;
+    get nodeText(): string;
+    get startPosition(): Point;
+    get endPosition(): Point;
+    get startIndex(): number;
+    get endIndex(): number;
+    gotoFirstChild(): boolean;
+    gotoLastChild(): boolean;
+    gotoParent(): boolean;
+    gotoNextSibling(): boolean;
+    gotoPreviousSibling(): boolean;
+    gotoDescendant(goalDescendantIndex: number): void;
+    gotoFirstChildForIndex(goalIndex: number): boolean;
+    gotoFirstChildForPosition(goalPosition: Point): boolean;
+    reset(node: Node): void;
+    resetTo(cursor: TreeCursor): void;
+  }
+}

+ 4 - 1
tsconfig.json

@@ -24,7 +24,10 @@
     "esModuleInterop": true,
     "skipLibCheck": true,
     "forceConsistentCasingInFileNames": true,
-    "resolveJsonModule": true
+    "resolveJsonModule": true,
+    "paths": {
+      "web-tree-sitter": ["./src/web-tree-sitter.d.ts"]
+    }
   },
   "include": ["src/**/*"],
   "exclude": ["node_modules", "dist", "__tests__"]