@quolu/lattice 0.63.0 → 0.63.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +147 -147
  2. package/README.ja.md +378 -378
  3. package/README.md +303 -303
  4. package/bin/lattice-mcp.mjs +0 -0
  5. package/bin/lattice-scripted-adapter.mjs +0 -0
  6. package/bin/lattice-scripted-worker.mjs +0 -0
  7. package/bin/lattice-work-order-adapter.mjs +0 -0
  8. package/bin/lattice.mjs +3 -1
  9. package/docs/bridge-setup.md +248 -248
  10. package/docs/schemas/lattice.executor_packet.v1.schema.json +57 -57
  11. package/docs/schemas/lattice.executor_receipt.v1.schema.json +66 -66
  12. package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +360 -360
  13. package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -56
  14. package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -72
  15. package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -81
  16. package/docs/schemas/lattice.plan_create_input.v4.schema.json +357 -85
  17. package/docs/schemas/lattice.plan_scope_review.v1.schema.json +55 -55
  18. package/docs/schemas/lattice.run_request.v1.schema.json +238 -238
  19. package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -55
  20. package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -78
  21. package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -86
  22. package/docs/schemas/lattice.todo_extraction.v2.schema.json +298 -298
  23. package/docs/schemas/lattice.todo_extraction.v3.schema.json +150 -150
  24. package/docs/schemas/lattice.todo_extraction.v4.schema.json +161 -161
  25. package/docs/schemas/lattice.todo_revision.v2.schema.json +260 -260
  26. package/docs/schemas/lattice.todo_revision_set.v3.schema.json +363 -363
  27. package/docs/schemas/lattice.todo_structure_binding.v1.schema.json +47 -47
  28. package/docs/schemas/lattice.todo_structure_realization.v1.schema.json +55 -55
  29. package/docs/schemas/lattice.todo_structure_set.v1.schema.json +264 -264
  30. package/package.json +109 -109
  31. package/sensor/LICENSE +21 -21
  32. package/sensor/NOTICE +19 -19
  33. package/sensor/dist/bin/lattice-sensor.js +9 -9
  34. package/sensor/dist/db/index.js +24 -24
  35. package/sensor/dist/db/migrations.js +41 -41
  36. package/sensor/dist/db/queries.js +164 -164
  37. package/sensor/dist/db/schema.sql +205 -205
  38. package/sensor/dist/directory.js +5 -5
  39. package/sensor/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
  40. package/sensor/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  41. package/sensor/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  42. package/sensor/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  43. package/sensor/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  44. package/sensor/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  45. package/sensor/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
  46. package/sensor/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
  47. package/sensor/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
  48. package/sensor/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  49. package/sensor/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  50. package/sensor/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
  51. package/sensor/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
  52. package/sensor/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
  53. package/sensor/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  54. package/sensor/dist/mcp/liveness-watchdog.js +53 -53
  55. package/sensor/dist/mcp/server-instructions.js +95 -95
  56. package/sensor/package.json +56 -56
  57. package/src/project-cli.mjs +164 -24
  58. package/src/todo-authoring-input.mjs +35 -5
  59. package/src/todo-cli.mjs +2 -2
  60. package/src/todo-store.mjs +52 -21
@@ -265,20 +265,20 @@ class QueryBuilder {
265
265
  */
266
266
  insertNode(node) {
267
267
  if (!this.stmts.insertNode) {
268
- this.stmts.insertNode = this.db.prepare(`
269
- INSERT OR REPLACE INTO nodes (
270
- id, kind, name, qualified_name, file_path, language,
271
- start_line, end_line, start_column, end_column,
272
- docstring, signature, visibility,
273
- is_exported, is_async, is_static, is_abstract,
274
- decorators, type_parameters, return_type, extent_start_line, updated_at
275
- ) VALUES (
276
- @id, @kind, @name, @qualifiedName, @filePath, @language,
277
- @startLine, @endLine, @startColumn, @endColumn,
278
- @docstring, @signature, @visibility,
279
- @isExported, @isAsync, @isStatic, @isAbstract,
280
- @decorators, @typeParameters, @returnType, @extentStartLine, @updatedAt
281
- )
268
+ this.stmts.insertNode = this.db.prepare(`
269
+ INSERT OR REPLACE INTO nodes (
270
+ id, kind, name, qualified_name, file_path, language,
271
+ start_line, end_line, start_column, end_column,
272
+ docstring, signature, visibility,
273
+ is_exported, is_async, is_static, is_abstract,
274
+ decorators, type_parameters, return_type, extent_start_line, updated_at
275
+ ) VALUES (
276
+ @id, @kind, @name, @qualifiedName, @filePath, @language,
277
+ @startLine, @endLine, @startColumn, @endColumn,
278
+ @docstring, @signature, @visibility,
279
+ @isExported, @isAsync, @isStatic, @isAbstract,
280
+ @decorators, @typeParameters, @returnType, @extentStartLine, @updatedAt
281
+ )
282
282
  `);
283
283
  }
284
284
  // Validate required fields to prevent SQLite bind errors
@@ -396,12 +396,12 @@ class QueryBuilder {
396
396
  if (this.isSegmentableKind(node.kind))
397
397
  this.collectNameSegmentRows(node.name, segmentRows);
398
398
  }
399
- this.runBatched('insertNodes', `INSERT OR REPLACE INTO nodes (
400
- id, kind, name, qualified_name, file_path, language,
401
- start_line, end_line, start_column, end_column,
402
- docstring, signature, visibility,
403
- is_exported, is_async, is_static, is_abstract,
404
- decorators, type_parameters, return_type, extent_start_line, updated_at
399
+ this.runBatched('insertNodes', `INSERT OR REPLACE INTO nodes (
400
+ id, kind, name, qualified_name, file_path, language,
401
+ start_line, end_line, start_column, end_column,
402
+ docstring, signature, visibility,
403
+ is_exported, is_async, is_static, is_abstract,
404
+ decorators, type_parameters, return_type, extent_start_line, updated_at
405
405
  ) VALUES `, '(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)', rows);
406
406
  this.runBatched('insertNameSegments', 'INSERT OR IGNORE INTO name_segment_vocab (segment, name) VALUES ', '(?,?)', segmentRows);
407
407
  })();
@@ -460,30 +460,30 @@ class QueryBuilder {
460
460
  */
461
461
  updateNode(node) {
462
462
  if (!this.stmts.updateNode) {
463
- this.stmts.updateNode = this.db.prepare(`
464
- UPDATE nodes SET
465
- kind = @kind,
466
- name = @name,
467
- qualified_name = @qualifiedName,
468
- file_path = @filePath,
469
- language = @language,
470
- start_line = @startLine,
471
- end_line = @endLine,
472
- start_column = @startColumn,
473
- end_column = @endColumn,
474
- docstring = @docstring,
475
- signature = @signature,
476
- visibility = @visibility,
477
- is_exported = @isExported,
478
- is_async = @isAsync,
479
- is_static = @isStatic,
480
- is_abstract = @isAbstract,
481
- decorators = @decorators,
482
- type_parameters = @typeParameters,
483
- return_type = @returnType,
484
- extent_start_line = @extentStartLine,
485
- updated_at = @updatedAt
486
- WHERE id = @id
463
+ this.stmts.updateNode = this.db.prepare(`
464
+ UPDATE nodes SET
465
+ kind = @kind,
466
+ name = @name,
467
+ qualified_name = @qualifiedName,
468
+ file_path = @filePath,
469
+ language = @language,
470
+ start_line = @startLine,
471
+ end_line = @endLine,
472
+ start_column = @startColumn,
473
+ end_column = @endColumn,
474
+ docstring = @docstring,
475
+ signature = @signature,
476
+ visibility = @visibility,
477
+ is_exported = @isExported,
478
+ is_async = @isAsync,
479
+ is_static = @isStatic,
480
+ is_abstract = @isAbstract,
481
+ decorators = @decorators,
482
+ type_parameters = @typeParameters,
483
+ return_type = @returnType,
484
+ extent_start_line = @extentStartLine,
485
+ updated_at = @updatedAt
486
+ WHERE id = @id
487
487
  `);
488
488
  }
489
489
  // Invalidate cache before update
@@ -607,12 +607,12 @@ class QueryBuilder {
607
607
  const placeholders = variants.map(() => '?').join(', ');
608
608
  const whens = variants.map(() => 'WHEN ? THEN ?').join(' ');
609
609
  const rows = this.db
610
- .prepare(`SELECT name, COUNT(DISTINCT CASE segment ${whens} END) AS matches
611
- FROM name_segment_vocab
612
- WHERE segment IN (${placeholders})
613
- GROUP BY name
614
- HAVING matches >= ?
615
- ORDER BY matches DESC, length(name) ASC
610
+ .prepare(`SELECT name, COUNT(DISTINCT CASE segment ${whens} END) AS matches
611
+ FROM name_segment_vocab
612
+ WHERE segment IN (${placeholders})
613
+ GROUP BY name
614
+ HAVING matches >= ?
615
+ ORDER BY matches DESC, length(name) ASC
616
616
  LIMIT ?`)
617
617
  .all(...variants.flatMap((v) => [v.segment, v.word]), ...variants.map((v) => v.segment), minWords, limit);
618
618
  return rows;
@@ -624,7 +624,7 @@ class QueryBuilder {
624
624
  return new Map();
625
625
  const placeholders = segments.map(() => '?').join(', ');
626
626
  const rows = this.db
627
- .prepare(`SELECT segment, COUNT(*) AS n FROM name_segment_vocab
627
+ .prepare(`SELECT segment, COUNT(*) AS n FROM name_segment_vocab
628
628
  WHERE segment IN (${placeholders}) GROUP BY segment`)
629
629
  .all(...segments);
630
630
  return new Map(rows.map((r) => [r.segment, r.n]));
@@ -784,15 +784,15 @@ class QueryBuilder {
784
784
  // protobuf stub) outranks the real `server/etcdserver/server.go`
785
785
  // (470 edges) by 4×, and the boost would push the agent toward
786
786
  // generated code.
787
- this.stmts.getDominantFile = this.db.prepare(`
788
- SELECT n.file_path AS file_path, COUNT(*) AS edge_count
789
- FROM edges e
790
- JOIN nodes n ON e.source = n.id
791
- JOIN nodes m ON e.target = m.id
792
- WHERE n.file_path = m.file_path
793
- GROUP BY n.file_path
794
- ORDER BY edge_count DESC
795
- LIMIT 20
787
+ this.stmts.getDominantFile = this.db.prepare(`
788
+ SELECT n.file_path AS file_path, COUNT(*) AS edge_count
789
+ FROM edges e
790
+ JOIN nodes n ON e.source = n.id
791
+ JOIN nodes m ON e.target = m.id
792
+ WHERE n.file_path = m.file_path
793
+ GROUP BY n.file_path
794
+ ORDER BY edge_count DESC
795
+ LIMIT 20
796
796
  `);
797
797
  }
798
798
  const rows = this.stmts.getDominantFile.all();
@@ -819,13 +819,13 @@ class QueryBuilder {
819
819
  */
820
820
  getTopRouteFile() {
821
821
  if (!this.stmts.getTopRouteFile) {
822
- this.stmts.getTopRouteFile = this.db.prepare(`
823
- SELECT file_path, COUNT(*) AS cnt
824
- FROM nodes
825
- WHERE kind = 'route'
826
- GROUP BY file_path
827
- ORDER BY cnt DESC
828
- LIMIT 20
822
+ this.stmts.getTopRouteFile = this.db.prepare(`
823
+ SELECT file_path, COUNT(*) AS cnt
824
+ FROM nodes
825
+ WHERE kind = 'route'
826
+ GROUP BY file_path
827
+ ORDER BY cnt DESC
828
+ LIMIT 20
829
829
  `);
830
830
  }
831
831
  const rows = this.stmts.getTopRouteFile.all();
@@ -856,21 +856,21 @@ class QueryBuilder {
856
856
  // Edge kind varies across framework resolvers: Spring/Rails/
857
857
  // Laravel/Drupal emit `references`, Express emits `calls`. Accept
858
858
  // both — the semantic is the same (route → its handler).
859
- this.stmts.getRoutingManifest = this.db.prepare(`
860
- SELECT
861
- r.name AS url,
862
- h.name AS handler,
863
- h.file_path AS handler_file,
864
- h.start_line AS handler_line,
865
- h.kind AS handler_kind
866
- FROM nodes r
867
- JOIN edges e ON e.source = r.id
868
- JOIN nodes h ON e.target = h.id
869
- WHERE r.kind = 'route'
870
- AND e.kind IN ('references', 'calls')
871
- AND h.kind IN ('function', 'method', 'class')
872
- ORDER BY r.file_path, r.start_line
873
- LIMIT ?
859
+ this.stmts.getRoutingManifest = this.db.prepare(`
860
+ SELECT
861
+ r.name AS url,
862
+ h.name AS handler,
863
+ h.file_path AS handler_file,
864
+ h.start_line AS handler_line,
865
+ h.kind AS handler_kind
866
+ FROM nodes r
867
+ JOIN edges e ON e.source = r.id
868
+ JOIN nodes h ON e.target = h.id
869
+ WHERE r.kind = 'route'
870
+ AND e.kind IN ('references', 'calls')
871
+ AND h.kind IN ('function', 'method', 'class')
872
+ ORDER BY r.file_path, r.start_line
873
+ LIMIT ?
874
874
  `);
875
875
  }
876
876
  const rows = this.stmts.getRoutingManifest.all(limit);
@@ -1234,11 +1234,11 @@ class QueryBuilder {
1234
1234
  // Fetch 5x requested limit so post-hoc rescoring (kindBonus, pathRelevance,
1235
1235
  // nameMatchBonus) can promote results that BM25 alone undervalues.
1236
1236
  const ftsLimit = Math.max(limit * 5, 100);
1237
- let sql = `
1238
- SELECT nodes.*, bm25(nodes_fts, 0, 20, 5, 1, 2) as score
1239
- FROM nodes_fts
1240
- JOIN nodes ON nodes_fts.id = nodes.id
1241
- WHERE nodes_fts MATCH ?
1237
+ let sql = `
1238
+ SELECT nodes.*, bm25(nodes_fts, 0, 20, 5, 1, 2) as score
1239
+ FROM nodes_fts
1240
+ JOIN nodes ON nodes_fts.id = nodes.id
1241
+ WHERE nodes_fts MATCH ?
1242
1242
  `;
1243
1243
  const params = [ftsQuery];
1244
1244
  if (kinds && kinds.length > 0) {
@@ -1269,21 +1269,21 @@ class QueryBuilder {
1269
1269
  */
1270
1270
  searchNodesLike(query, options) {
1271
1271
  const { kinds, languages, limit = 100, offset = 0 } = options;
1272
- let sql = `
1273
- SELECT nodes.*,
1274
- CASE
1275
- WHEN name = ? THEN 1.0
1276
- WHEN name LIKE ? THEN 0.9
1277
- WHEN name LIKE ? THEN 0.8
1278
- WHEN qualified_name LIKE ? THEN 0.7
1279
- ELSE 0.5
1280
- END as score
1281
- FROM nodes
1282
- WHERE (
1283
- name LIKE ? OR
1284
- qualified_name LIKE ? OR
1285
- name LIKE ?
1286
- )
1272
+ let sql = `
1273
+ SELECT nodes.*,
1274
+ CASE
1275
+ WHEN name = ? THEN 1.0
1276
+ WHEN name LIKE ? THEN 0.9
1277
+ WHEN name LIKE ? THEN 0.8
1278
+ WHEN qualified_name LIKE ? THEN 0.7
1279
+ ELSE 0.5
1280
+ END as score
1281
+ FROM nodes
1282
+ WHERE (
1283
+ name LIKE ? OR
1284
+ qualified_name LIKE ? OR
1285
+ name LIKE ?
1286
+ )
1287
1287
  `;
1288
1288
  // Pattern variants for better matching
1289
1289
  const exactMatch = query;
@@ -1357,10 +1357,10 @@ class QueryBuilder {
1357
1357
  const allResults = [];
1358
1358
  const seenIds = new Set();
1359
1359
  for (const name of names) {
1360
- let sql = `
1361
- SELECT nodes.*, 1.0 as score
1362
- FROM nodes
1363
- WHERE name COLLATE NOCASE = ?
1360
+ let sql = `
1361
+ SELECT nodes.*, 1.0 as score
1362
+ FROM nodes
1363
+ WHERE name COLLATE NOCASE = ?
1364
1364
  `;
1365
1365
  const params = [name];
1366
1366
  if (kinds && kinds.length > 0) {
@@ -1404,10 +1404,10 @@ class QueryBuilder {
1404
1404
  */
1405
1405
  findNodesByNameSubstring(substring, options = {}) {
1406
1406
  const { kinds, languages, limit = 30, excludePrefix } = options;
1407
- let sql = `
1408
- SELECT nodes.*, 1.0 as score
1409
- FROM nodes
1410
- WHERE name LIKE ?
1407
+ let sql = `
1408
+ SELECT nodes.*, 1.0 as score
1409
+ FROM nodes
1410
+ WHERE name LIKE ?
1411
1411
  `;
1412
1412
  const params = [`%${substring}%`];
1413
1413
  // Exclude prefix matches (handled by FTS-based prefix search in Step 2b)
@@ -1439,9 +1439,9 @@ class QueryBuilder {
1439
1439
  */
1440
1440
  insertEdge(edge) {
1441
1441
  if (!this.stmts.insertEdge) {
1442
- this.stmts.insertEdge = this.db.prepare(`
1443
- INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance, confidence, resolved_by)
1444
- VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance, @confidence, @resolvedBy)
1442
+ this.stmts.insertEdge = this.db.prepare(`
1443
+ INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance, confidence, resolved_by)
1444
+ VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance, @confidence, @resolvedBy)
1445
1445
  `);
1446
1446
  }
1447
1447
  this.stmts.insertEdge.run({
@@ -1571,18 +1571,18 @@ class QueryBuilder {
1571
1571
  * it. One indexed query (idx_nodes_file_path + idx_edges_target_kind).
1572
1572
  */
1573
1573
  getDependentFilePaths(filePath) {
1574
- const sql = `SELECT DISTINCT src.file_path AS fp
1575
- FROM edges e
1576
- JOIN nodes tgt ON tgt.id = e.target
1577
- JOIN nodes src ON src.id = e.source
1578
- WHERE tgt.file_path = ?
1579
- AND e.kind != 'contains'
1580
- AND src.file_path != ?
1581
- AND (
1582
- e.kind = 'imports'
1583
- OR src.language NOT IN (${UNCORROBORATED_FILTER_LANGUAGES_SQL_LIST})
1584
- OR e.resolved_by IS NULL
1585
- OR e.resolved_by NOT IN (${UNCORROBORATED_RESOLVED_BY_SQL_LIST})
1574
+ const sql = `SELECT DISTINCT src.file_path AS fp
1575
+ FROM edges e
1576
+ JOIN nodes tgt ON tgt.id = e.target
1577
+ JOIN nodes src ON src.id = e.source
1578
+ WHERE tgt.file_path = ?
1579
+ AND e.kind != 'contains'
1580
+ AND src.file_path != ?
1581
+ AND (
1582
+ e.kind = 'imports'
1583
+ OR src.language NOT IN (${UNCORROBORATED_FILTER_LANGUAGES_SQL_LIST})
1584
+ OR e.resolved_by IS NULL
1585
+ OR e.resolved_by NOT IN (${UNCORROBORATED_RESOLVED_BY_SQL_LIST})
1586
1586
  )`;
1587
1587
  const rows = this.db.prepare(sql).all(filePath, filePath);
1588
1588
  return rows.map((r) => r.fp);
@@ -1594,18 +1594,18 @@ class QueryBuilder {
1594
1594
  * (all kinds except `contains`); same reason imports-only is insufficient.
1595
1595
  */
1596
1596
  getDependencyFilePaths(filePath) {
1597
- const sql = `SELECT DISTINCT tgt.file_path AS fp
1598
- FROM edges e
1599
- JOIN nodes src ON src.id = e.source
1600
- JOIN nodes tgt ON tgt.id = e.target
1601
- WHERE src.file_path = ?
1602
- AND e.kind != 'contains'
1603
- AND tgt.file_path != ?
1604
- AND (
1605
- e.kind = 'imports'
1606
- OR src.language NOT IN (${UNCORROBORATED_FILTER_LANGUAGES_SQL_LIST})
1607
- OR e.resolved_by IS NULL
1608
- OR e.resolved_by NOT IN (${UNCORROBORATED_RESOLVED_BY_SQL_LIST})
1597
+ const sql = `SELECT DISTINCT tgt.file_path AS fp
1598
+ FROM edges e
1599
+ JOIN nodes src ON src.id = e.source
1600
+ JOIN nodes tgt ON tgt.id = e.target
1601
+ WHERE src.file_path = ?
1602
+ AND e.kind != 'contains'
1603
+ AND tgt.file_path != ?
1604
+ AND (
1605
+ e.kind = 'imports'
1606
+ OR src.language NOT IN (${UNCORROBORATED_FILTER_LANGUAGES_SQL_LIST})
1607
+ OR e.resolved_by IS NULL
1608
+ OR e.resolved_by NOT IN (${UNCORROBORATED_RESOLVED_BY_SQL_LIST})
1609
1609
  )`;
1610
1610
  const rows = this.db.prepare(sql).all(filePath, filePath);
1611
1611
  return rows.map((r) => r.fp);
@@ -1621,13 +1621,13 @@ class QueryBuilder {
1621
1621
  * {@link getDependentFilePaths}: all kinds except `contains`.
1622
1622
  */
1623
1623
  getCrossFileIncomingEdgesWithTarget(filePath) {
1624
- const sql = `SELECT e.*, tgt.name AS target_name, tgt.kind AS target_kind,
1625
- src.file_path AS source_file_path, src.language AS source_language
1626
- FROM edges e
1627
- JOIN nodes tgt ON tgt.id = e.target
1628
- JOIN nodes src ON src.id = e.source
1629
- WHERE tgt.file_path = ?
1630
- AND e.kind != 'contains'
1624
+ const sql = `SELECT e.*, tgt.name AS target_name, tgt.kind AS target_kind,
1625
+ src.file_path AS source_file_path, src.language AS source_language
1626
+ FROM edges e
1627
+ JOIN nodes tgt ON tgt.id = e.target
1628
+ JOIN nodes src ON src.id = e.source
1629
+ WHERE tgt.file_path = ?
1630
+ AND e.kind != 'contains'
1631
1631
  AND src.file_path != ?`;
1632
1632
  const rows = this.db.prepare(sql).all(filePath, filePath);
1633
1633
  return rows.map(row => ({
@@ -1646,18 +1646,18 @@ class QueryBuilder {
1646
1646
  */
1647
1647
  upsertFile(file) {
1648
1648
  if (!this.stmts.upsertFile) {
1649
- this.stmts.upsertFile = this.db.prepare(`
1650
- INSERT INTO files (path, content_hash, language, size, modified_at, indexed_at, node_count, extraction_version, errors)
1651
- VALUES (@path, @contentHash, @language, @size, @modifiedAt, @indexedAt, @nodeCount, @extractionVersion, @errors)
1652
- ON CONFLICT(path) DO UPDATE SET
1653
- content_hash = @contentHash,
1654
- language = @language,
1655
- size = @size,
1656
- modified_at = @modifiedAt,
1657
- indexed_at = @indexedAt,
1658
- node_count = @nodeCount,
1659
- extraction_version = @extractionVersion,
1660
- errors = @errors
1649
+ this.stmts.upsertFile = this.db.prepare(`
1650
+ INSERT INTO files (path, content_hash, language, size, modified_at, indexed_at, node_count, extraction_version, errors)
1651
+ VALUES (@path, @contentHash, @language, @size, @modifiedAt, @indexedAt, @nodeCount, @extractionVersion, @errors)
1652
+ ON CONFLICT(path) DO UPDATE SET
1653
+ content_hash = @contentHash,
1654
+ language = @language,
1655
+ size = @size,
1656
+ modified_at = @modifiedAt,
1657
+ indexed_at = @indexedAt,
1658
+ node_count = @nodeCount,
1659
+ extraction_version = @extractionVersion,
1660
+ errors = @errors
1661
1661
  `);
1662
1662
  }
1663
1663
  this.stmts.upsertFile.run({
@@ -1771,9 +1771,9 @@ class QueryBuilder {
1771
1771
  */
1772
1772
  insertUnresolvedRef(ref) {
1773
1773
  if (!this.stmts.insertUnresolved) {
1774
- this.stmts.insertUnresolved = this.db.prepare(`
1775
- INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates, file_path, language, binding_form, imported_name)
1776
- VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates, @filePath, @language, @bindingForm, @importedName)
1774
+ this.stmts.insertUnresolved = this.db.prepare(`
1775
+ INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates, file_path, language, binding_form, imported_name)
1776
+ VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates, @filePath, @language, @bindingForm, @importedName)
1777
1777
  `);
1778
1778
  }
1779
1779
  this.stmts.insertUnresolved.run({
@@ -2246,11 +2246,11 @@ class QueryBuilder {
2246
2246
  */
2247
2247
  getStats() {
2248
2248
  // Single query for all three aggregate counts
2249
- const counts = this.db.prepare(`
2250
- SELECT
2251
- (SELECT COUNT(*) FROM nodes) AS node_count,
2252
- (SELECT COUNT(*) FROM edges) AS edge_count,
2253
- (SELECT COUNT(*) FROM files) AS file_count
2249
+ const counts = this.db.prepare(`
2250
+ SELECT
2251
+ (SELECT COUNT(*) FROM nodes) AS node_count,
2252
+ (SELECT COUNT(*) FROM edges) AS edge_count,
2253
+ (SELECT COUNT(*) FROM files) AS file_count
2254
2254
  `).get();
2255
2255
  const nodesByKind = {};
2256
2256
  const nodeKindRows = this.db