@optave/codegraph 3.0.4 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +59 -52
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +9 -10
  4. package/src/ast-analysis/rules/csharp.js +201 -0
  5. package/src/ast-analysis/rules/go.js +182 -0
  6. package/src/ast-analysis/rules/index.js +82 -0
  7. package/src/ast-analysis/rules/java.js +175 -0
  8. package/src/ast-analysis/rules/javascript.js +246 -0
  9. package/src/ast-analysis/rules/php.js +219 -0
  10. package/src/ast-analysis/rules/python.js +196 -0
  11. package/src/ast-analysis/rules/ruby.js +204 -0
  12. package/src/ast-analysis/rules/rust.js +173 -0
  13. package/src/ast-analysis/shared.js +223 -0
  14. package/src/ast.js +15 -28
  15. package/src/audit.js +4 -5
  16. package/src/boundaries.js +1 -1
  17. package/src/branch-compare.js +84 -79
  18. package/src/builder.js +274 -159
  19. package/src/cfg.js +111 -341
  20. package/src/check.js +3 -3
  21. package/src/cli.js +122 -167
  22. package/src/cochange.js +1 -1
  23. package/src/communities.js +13 -16
  24. package/src/complexity.js +196 -1239
  25. package/src/cycles.js +1 -1
  26. package/src/dataflow.js +274 -697
  27. package/src/db/connection.js +88 -0
  28. package/src/db/migrations.js +312 -0
  29. package/src/db/query-builder.js +280 -0
  30. package/src/db/repository.js +134 -0
  31. package/src/db.js +19 -392
  32. package/src/embedder.js +145 -141
  33. package/src/export.js +1 -1
  34. package/src/flow.js +160 -228
  35. package/src/index.js +36 -2
  36. package/src/kinds.js +49 -0
  37. package/src/manifesto.js +3 -8
  38. package/src/mcp.js +97 -20
  39. package/src/owners.js +132 -132
  40. package/src/parser.js +58 -131
  41. package/src/queries-cli.js +866 -0
  42. package/src/queries.js +1356 -2261
  43. package/src/resolve.js +11 -2
  44. package/src/result-formatter.js +21 -0
  45. package/src/sequence.js +364 -0
  46. package/src/structure.js +200 -199
  47. package/src/test-filter.js +7 -0
  48. package/src/triage.js +120 -162
  49. package/src/viewer.js +1 -1
package/src/embedder.js CHANGED
@@ -625,37 +625,39 @@ export async function searchData(query, customDbPath, opts = {}) {
625
625
  if (!prepared) return null;
626
626
  const { db, rows, modelKey, storedDim } = prepared;
627
627
 
628
- const {
629
- vectors: [queryVec],
630
- dim,
631
- } = await embed([query], modelKey);
632
-
633
- if (storedDim && dim !== storedDim) {
634
- console.log(
635
- `Warning: query model dimension (${dim}) doesn't match stored embeddings (${storedDim}).`,
636
- );
637
- console.log(` Re-run \`codegraph embed\` with the same model, or use --model to match.`);
638
- db.close();
639
- return null;
640
- }
628
+ try {
629
+ const {
630
+ vectors: [queryVec],
631
+ dim,
632
+ } = await embed([query], modelKey);
641
633
 
642
- const hc = new Map();
643
- const results = [];
644
- for (const row of rows) {
645
- const vec = new Float32Array(new Uint8Array(row.vector).buffer);
646
- const sim = cosineSim(queryVec, vec);
634
+ if (storedDim && dim !== storedDim) {
635
+ console.log(
636
+ `Warning: query model dimension (${dim}) doesn't match stored embeddings (${storedDim}).`,
637
+ );
638
+ console.log(` Re-run \`codegraph embed\` with the same model, or use --model to match.`);
639
+ return null;
640
+ }
647
641
 
648
- if (sim >= minScore) {
649
- results.push({
650
- ...normalizeSymbol(row, db, hc),
651
- similarity: sim,
652
- });
642
+ const hc = new Map();
643
+ const results = [];
644
+ for (const row of rows) {
645
+ const vec = new Float32Array(new Uint8Array(row.vector).buffer);
646
+ const sim = cosineSim(queryVec, vec);
647
+
648
+ if (sim >= minScore) {
649
+ results.push({
650
+ ...normalizeSymbol(row, db, hc),
651
+ similarity: sim,
652
+ });
653
+ }
653
654
  }
654
- }
655
655
 
656
- results.sort((a, b) => b.similarity - a.similarity);
657
- db.close();
658
- return { results: results.slice(0, limit) };
656
+ results.sort((a, b) => b.similarity - a.similarity);
657
+ return { results: results.slice(0, limit) };
658
+ } finally {
659
+ db.close();
660
+ }
659
661
  }
660
662
 
661
663
  /**
@@ -671,82 +673,84 @@ export async function multiSearchData(queries, customDbPath, opts = {}) {
671
673
  if (!prepared) return null;
672
674
  const { db, rows, modelKey, storedDim } = prepared;
673
675
 
674
- const { vectors: queryVecs, dim } = await embed(queries, modelKey);
675
-
676
- // Warn about similar queries that may bias RRF results
677
- const SIMILARITY_WARN_THRESHOLD = 0.85;
678
- for (let i = 0; i < queryVecs.length; i++) {
679
- for (let j = i + 1; j < queryVecs.length; j++) {
680
- const sim = cosineSim(queryVecs[i], queryVecs[j]);
681
- if (sim >= SIMILARITY_WARN_THRESHOLD) {
682
- warn(
683
- `Queries "${queries[i]}" and "${queries[j]}" are very similar ` +
684
- `(${(sim * 100).toFixed(0)}% cosine similarity). ` +
685
- `This may bias RRF results toward their shared matches. ` +
686
- `Consider using more distinct queries.`,
687
- );
676
+ try {
677
+ const { vectors: queryVecs, dim } = await embed(queries, modelKey);
678
+
679
+ // Warn about similar queries that may bias RRF results
680
+ const SIMILARITY_WARN_THRESHOLD = 0.85;
681
+ for (let i = 0; i < queryVecs.length; i++) {
682
+ for (let j = i + 1; j < queryVecs.length; j++) {
683
+ const sim = cosineSim(queryVecs[i], queryVecs[j]);
684
+ if (sim >= SIMILARITY_WARN_THRESHOLD) {
685
+ warn(
686
+ `Queries "${queries[i]}" and "${queries[j]}" are very similar ` +
687
+ `(${(sim * 100).toFixed(0)}% cosine similarity). ` +
688
+ `This may bias RRF results toward their shared matches. ` +
689
+ `Consider using more distinct queries.`,
690
+ );
691
+ }
688
692
  }
689
693
  }
690
- }
691
694
 
692
- if (storedDim && dim !== storedDim) {
693
- console.log(
694
- `Warning: query model dimension (${dim}) doesn't match stored embeddings (${storedDim}).`,
695
- );
696
- console.log(` Re-run \`codegraph embed\` with the same model, or use --model to match.`);
697
- db.close();
698
- return null;
699
- }
695
+ if (storedDim && dim !== storedDim) {
696
+ console.log(
697
+ `Warning: query model dimension (${dim}) doesn't match stored embeddings (${storedDim}).`,
698
+ );
699
+ console.log(` Re-run \`codegraph embed\` with the same model, or use --model to match.`);
700
+ return null;
701
+ }
700
702
 
701
- // Parse row vectors once
702
- const rowVecs = rows.map((row) => new Float32Array(new Uint8Array(row.vector).buffer));
703
+ // Parse row vectors once
704
+ const rowVecs = rows.map((row) => new Float32Array(new Uint8Array(row.vector).buffer));
703
705
 
704
- // For each query: compute similarities, filter by minScore, rank
705
- const perQueryRanked = queries.map((_query, qi) => {
706
- const scored = [];
707
- for (let ri = 0; ri < rows.length; ri++) {
708
- const sim = cosineSim(queryVecs[qi], rowVecs[ri]);
709
- if (sim >= minScore) {
710
- scored.push({ rowIndex: ri, similarity: sim });
706
+ // For each query: compute similarities, filter by minScore, rank
707
+ const perQueryRanked = queries.map((_query, qi) => {
708
+ const scored = [];
709
+ for (let ri = 0; ri < rows.length; ri++) {
710
+ const sim = cosineSim(queryVecs[qi], rowVecs[ri]);
711
+ if (sim >= minScore) {
712
+ scored.push({ rowIndex: ri, similarity: sim });
713
+ }
711
714
  }
712
- }
713
- scored.sort((a, b) => b.similarity - a.similarity);
714
- // Assign 1-indexed ranks
715
- return scored.map((item, rank) => ({ ...item, rank: rank + 1 }));
716
- });
715
+ scored.sort((a, b) => b.similarity - a.similarity);
716
+ // Assign 1-indexed ranks
717
+ return scored.map((item, rank) => ({ ...item, rank: rank + 1 }));
718
+ });
717
719
 
718
- // Fuse results using RRF: for each unique row, sum 1/(k + rank_i) across queries
719
- const fusionMap = new Map(); // rowIndex -> { rrfScore, queryScores[] }
720
- for (let qi = 0; qi < queries.length; qi++) {
721
- for (const item of perQueryRanked[qi]) {
722
- if (!fusionMap.has(item.rowIndex)) {
723
- fusionMap.set(item.rowIndex, { rrfScore: 0, queryScores: [] });
720
+ // Fuse results using RRF: for each unique row, sum 1/(k + rank_i) across queries
721
+ const fusionMap = new Map(); // rowIndex -> { rrfScore, queryScores[] }
722
+ for (let qi = 0; qi < queries.length; qi++) {
723
+ for (const item of perQueryRanked[qi]) {
724
+ if (!fusionMap.has(item.rowIndex)) {
725
+ fusionMap.set(item.rowIndex, { rrfScore: 0, queryScores: [] });
726
+ }
727
+ const entry = fusionMap.get(item.rowIndex);
728
+ entry.rrfScore += 1 / (k + item.rank);
729
+ entry.queryScores.push({
730
+ query: queries[qi],
731
+ similarity: item.similarity,
732
+ rank: item.rank,
733
+ });
724
734
  }
725
- const entry = fusionMap.get(item.rowIndex);
726
- entry.rrfScore += 1 / (k + item.rank);
727
- entry.queryScores.push({
728
- query: queries[qi],
729
- similarity: item.similarity,
730
- rank: item.rank,
735
+ }
736
+
737
+ // Build results sorted by RRF score
738
+ const hc = new Map();
739
+ const results = [];
740
+ for (const [rowIndex, entry] of fusionMap) {
741
+ const row = rows[rowIndex];
742
+ results.push({
743
+ ...normalizeSymbol(row, db, hc),
744
+ rrf: entry.rrfScore,
745
+ queryScores: entry.queryScores,
731
746
  });
732
747
  }
733
- }
734
748
 
735
- // Build results sorted by RRF score
736
- const hc = new Map();
737
- const results = [];
738
- for (const [rowIndex, entry] of fusionMap) {
739
- const row = rows[rowIndex];
740
- results.push({
741
- ...normalizeSymbol(row, db, hc),
742
- rrf: entry.rrfScore,
743
- queryScores: entry.queryScores,
744
- });
749
+ results.sort((a, b) => b.rrf - a.rrf);
750
+ return { results: results.slice(0, limit) };
751
+ } finally {
752
+ db.close();
745
753
  }
746
-
747
- results.sort((a, b) => b.rrf - a.rrf);
748
- db.close();
749
- return { results: results.slice(0, limit) };
750
754
  }
751
755
 
752
756
  /**
@@ -788,64 +792,64 @@ export function ftsSearchData(query, customDbPath, opts = {}) {
788
792
 
789
793
  const db = openReadonlyOrFail(customDbPath);
790
794
 
791
- if (!hasFtsIndex(db)) {
792
- db.close();
793
- return null;
794
- }
795
-
796
- const ftsQuery = sanitizeFtsQuery(query);
797
- if (!ftsQuery) {
798
- db.close();
799
- return { results: [] };
800
- }
795
+ try {
796
+ if (!hasFtsIndex(db)) {
797
+ return null;
798
+ }
801
799
 
802
- let sql = `
803
- SELECT f.rowid AS node_id, rank AS bm25_score,
804
- n.name, n.kind, n.file, n.line, n.end_line, n.role
805
- FROM fts_index f
806
- JOIN nodes n ON f.rowid = n.id
807
- WHERE fts_index MATCH ?
808
- `;
809
- const params = [ftsQuery];
800
+ const ftsQuery = sanitizeFtsQuery(query);
801
+ if (!ftsQuery) {
802
+ return { results: [] };
803
+ }
810
804
 
811
- if (opts.kind) {
812
- sql += ' AND n.kind = ?';
813
- params.push(opts.kind);
814
- }
805
+ let sql = `
806
+ SELECT f.rowid AS node_id, rank AS bm25_score,
807
+ n.name, n.kind, n.file, n.line, n.end_line, n.role
808
+ FROM fts_index f
809
+ JOIN nodes n ON f.rowid = n.id
810
+ WHERE fts_index MATCH ?
811
+ `;
812
+ const params = [ftsQuery];
813
+
814
+ if (opts.kind) {
815
+ sql += ' AND n.kind = ?';
816
+ params.push(opts.kind);
817
+ }
815
818
 
816
- const isGlob = opts.filePattern && /[*?[\]]/.test(opts.filePattern);
817
- if (opts.filePattern && !isGlob) {
818
- sql += ' AND n.file LIKE ?';
819
- params.push(`%${opts.filePattern}%`);
820
- }
819
+ const isGlob = opts.filePattern && /[*?[\]]/.test(opts.filePattern);
820
+ if (opts.filePattern && !isGlob) {
821
+ sql += ' AND n.file LIKE ?';
822
+ params.push(`%${opts.filePattern}%`);
823
+ }
821
824
 
822
- sql += ' ORDER BY rank LIMIT ?';
823
- params.push(limit * 5); // fetch generous set for post-filtering
825
+ sql += ' ORDER BY rank LIMIT ?';
826
+ params.push(limit * 5); // fetch generous set for post-filtering
824
827
 
825
- let rows;
826
- try {
827
- rows = db.prepare(sql).all(...params);
828
- } catch {
829
- // Invalid FTS5 query syntax — return empty
830
- db.close();
831
- return { results: [] };
832
- }
828
+ let rows;
829
+ try {
830
+ rows = db.prepare(sql).all(...params);
831
+ } catch {
832
+ // Invalid FTS5 query syntax — return empty
833
+ return { results: [] };
834
+ }
833
835
 
834
- if (isGlob) {
835
- rows = rows.filter((row) => globMatch(row.file, opts.filePattern));
836
- }
837
- if (noTests) {
838
- rows = rows.filter((row) => !TEST_PATTERN.test(row.file));
839
- }
836
+ if (isGlob) {
837
+ rows = rows.filter((row) => globMatch(row.file, opts.filePattern));
838
+ }
839
+ if (noTests) {
840
+ rows = rows.filter((row) => !TEST_PATTERN.test(row.file));
841
+ }
840
842
 
841
- const hc = new Map();
842
- const results = rows.slice(0, limit).map((row) => ({
843
- ...normalizeSymbol(row, db, hc),
844
- bm25Score: -row.bm25_score, // FTS5 rank is negative; negate for display
845
- }));
843
+ const hc = new Map();
844
+ const results = rows.slice(0, limit).map((row) => ({
845
+ ...normalizeSymbol(row, db, hc),
846
+ bm25Score: -row.bm25_score, // FTS5 rank is negative; negate for display
847
+ }));
846
848
 
847
- db.close();
848
- return { results };
849
+ return { results };
850
+ } finally {
851
+ db.close();
852
+ }
849
853
  }
850
854
 
851
855
  /**
package/src/export.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { paginateResult } from './paginate.js';
3
- import { isTestFile } from './queries.js';
3
+ import { isTestFile } from './test-filter.js';
4
4
 
5
5
  const DEFAULT_MIN_CONFIDENCE = 0.5;
6
6