@optave/codegraph 3.9.1 → 3.9.3

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 (148) hide show
  1. package/README.md +95 -14
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +64 -0
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/cli/commands/batch.d.ts.map +1 -1
  6. package/dist/cli/commands/batch.js +5 -17
  7. package/dist/cli/commands/batch.js.map +1 -1
  8. package/dist/cli/commands/structure.d.ts.map +1 -1
  9. package/dist/cli/commands/structure.js +18 -1
  10. package/dist/cli/commands/structure.js.map +1 -1
  11. package/dist/db/connection.d.ts +2 -0
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +2 -2
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/domain/analysis/context.d.ts.map +1 -1
  20. package/dist/domain/analysis/context.js +5 -15
  21. package/dist/domain/analysis/context.js.map +1 -1
  22. package/dist/domain/analysis/dependencies.d.ts +5 -5
  23. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  24. package/dist/domain/analysis/dependencies.js +6 -16
  25. package/dist/domain/analysis/dependencies.js.map +1 -1
  26. package/dist/domain/analysis/diff-impact.d.ts +12 -0
  27. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  28. package/dist/domain/analysis/diff-impact.js +20 -1
  29. package/dist/domain/analysis/diff-impact.js.map +1 -1
  30. package/dist/domain/analysis/fn-impact.js +2 -2
  31. package/dist/domain/analysis/fn-impact.js.map +1 -1
  32. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  33. package/dist/domain/analysis/implementations.js +3 -13
  34. package/dist/domain/analysis/implementations.js.map +1 -1
  35. package/dist/domain/graph/builder/context.d.ts +4 -0
  36. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  37. package/dist/domain/graph/builder/context.js +4 -0
  38. package/dist/domain/graph/builder/context.js.map +1 -1
  39. package/dist/domain/graph/builder/native-db-proxy.d.ts +24 -0
  40. package/dist/domain/graph/builder/native-db-proxy.d.ts.map +1 -0
  41. package/dist/domain/graph/builder/native-db-proxy.js +91 -0
  42. package/dist/domain/graph/builder/native-db-proxy.js.map +1 -0
  43. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  44. package/dist/domain/graph/builder/pipeline.js +148 -79
  45. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  46. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  47. package/dist/domain/graph/builder/stages/build-edges.js +15 -2
  48. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  49. package/dist/domain/graph/builder/stages/build-structure.js +2 -2
  50. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  51. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  52. package/dist/domain/graph/builder/stages/detect-changes.js +6 -28
  53. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  54. package/dist/domain/graph/builder/stages/finalize.js +1 -1
  55. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/insert-nodes.js +16 -12
  58. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  60. package/dist/domain/graph/builder/stages/resolve-imports.js +2 -3
  61. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  62. package/dist/domain/parser.d.ts.map +1 -1
  63. package/dist/domain/parser.js +11 -4
  64. package/dist/domain/parser.js.map +1 -1
  65. package/dist/domain/queries.d.ts +1 -1
  66. package/dist/domain/queries.d.ts.map +1 -1
  67. package/dist/domain/queries.js +1 -1
  68. package/dist/domain/queries.js.map +1 -1
  69. package/dist/features/ast.js +2 -2
  70. package/dist/features/ast.js.map +1 -1
  71. package/dist/features/audit.d.ts.map +1 -1
  72. package/dist/features/audit.js +3 -2
  73. package/dist/features/audit.js.map +1 -1
  74. package/dist/features/boundaries.d.ts.map +1 -1
  75. package/dist/features/boundaries.js +3 -5
  76. package/dist/features/boundaries.js.map +1 -1
  77. package/dist/features/branch-compare.d.ts.map +1 -1
  78. package/dist/features/branch-compare.js +2 -1
  79. package/dist/features/branch-compare.js.map +1 -1
  80. package/dist/features/cfg.d.ts +1 -1
  81. package/dist/features/cfg.d.ts.map +1 -1
  82. package/dist/features/cfg.js +52 -6
  83. package/dist/features/cfg.js.map +1 -1
  84. package/dist/features/complexity.d.ts.map +1 -1
  85. package/dist/features/complexity.js +7 -0
  86. package/dist/features/complexity.js.map +1 -1
  87. package/dist/features/flow.d.ts.map +1 -1
  88. package/dist/features/flow.js +2 -1
  89. package/dist/features/flow.js.map +1 -1
  90. package/dist/features/manifesto.d.ts.map +1 -1
  91. package/dist/features/manifesto.js +15 -1
  92. package/dist/features/manifesto.js.map +1 -1
  93. package/dist/infrastructure/config.d.ts +1 -0
  94. package/dist/infrastructure/config.d.ts.map +1 -1
  95. package/dist/infrastructure/config.js +1 -0
  96. package/dist/infrastructure/config.js.map +1 -1
  97. package/dist/infrastructure/update-check.d.ts +1 -1
  98. package/dist/infrastructure/update-check.js +3 -3
  99. package/dist/infrastructure/update-check.js.map +1 -1
  100. package/dist/presentation/batch.d.ts.map +1 -1
  101. package/dist/presentation/batch.js +1 -0
  102. package/dist/presentation/batch.js.map +1 -1
  103. package/dist/presentation/structure.d.ts +1 -1
  104. package/dist/presentation/structure.d.ts.map +1 -1
  105. package/dist/presentation/structure.js +1 -1
  106. package/dist/presentation/structure.js.map +1 -1
  107. package/dist/shared/normalize.d.ts +12 -0
  108. package/dist/shared/normalize.d.ts.map +1 -1
  109. package/dist/shared/normalize.js +4 -0
  110. package/dist/shared/normalize.js.map +1 -1
  111. package/dist/types.d.ts +2 -0
  112. package/dist/types.d.ts.map +1 -1
  113. package/package.json +7 -7
  114. package/src/ast-analysis/engine.ts +83 -0
  115. package/src/cli/commands/batch.ts +5 -26
  116. package/src/cli/commands/structure.ts +21 -1
  117. package/src/db/connection.ts +2 -2
  118. package/src/db/index.ts +2 -0
  119. package/src/domain/analysis/context.ts +5 -15
  120. package/src/domain/analysis/dependencies.ts +6 -16
  121. package/src/domain/analysis/diff-impact.ts +28 -1
  122. package/src/domain/analysis/fn-impact.ts +2 -2
  123. package/src/domain/analysis/implementations.ts +3 -13
  124. package/src/domain/graph/builder/context.ts +4 -0
  125. package/src/domain/graph/builder/native-db-proxy.ts +104 -0
  126. package/src/domain/graph/builder/pipeline.ts +171 -84
  127. package/src/domain/graph/builder/stages/build-edges.ts +15 -2
  128. package/src/domain/graph/builder/stages/build-structure.ts +2 -2
  129. package/src/domain/graph/builder/stages/detect-changes.ts +11 -33
  130. package/src/domain/graph/builder/stages/finalize.ts +1 -1
  131. package/src/domain/graph/builder/stages/insert-nodes.ts +17 -14
  132. package/src/domain/graph/builder/stages/resolve-imports.ts +2 -3
  133. package/src/domain/parser.ts +12 -4
  134. package/src/domain/queries.ts +1 -1
  135. package/src/features/ast.ts +2 -2
  136. package/src/features/audit.ts +3 -2
  137. package/src/features/boundaries.ts +3 -5
  138. package/src/features/branch-compare.ts +2 -3
  139. package/src/features/cfg.ts +51 -6
  140. package/src/features/complexity.ts +7 -0
  141. package/src/features/flow.ts +2 -1
  142. package/src/features/manifesto.ts +15 -1
  143. package/src/infrastructure/config.ts +1 -0
  144. package/src/infrastructure/update-check.ts +3 -3
  145. package/src/presentation/batch.ts +1 -0
  146. package/src/presentation/structure.ts +2 -2
  147. package/src/shared/normalize.ts +10 -0
  148. package/src/types.ts +2 -0
@@ -5,7 +5,7 @@ import type { Tree } from 'web-tree-sitter';
5
5
  import { Language, Parser, Query } from 'web-tree-sitter';
6
6
  import { debug, warn } from '../infrastructure/logger.js';
7
7
  import { getNative, getNativePackageVersion, loadNative } from '../infrastructure/native.js';
8
- import { toErrorMessage } from '../shared/errors.js';
8
+ import { ParseError, toErrorMessage } from '../shared/errors.js';
9
9
  import type {
10
10
  EngineMode,
11
11
  ExtractorOutput,
@@ -188,7 +188,11 @@ async function doLoadLanguage(entry: LanguageRegistryEntry): Promise<void> {
188
188
  _queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
189
189
  }
190
190
  } catch (e: unknown) {
191
- if (entry.required) throw e;
191
+ if (entry.required)
192
+ throw new ParseError(`Required parser ${entry.id} failed to initialize`, {
193
+ file: entry.grammarFile,
194
+ cause: e as Error,
195
+ });
192
196
  warn(
193
197
  `${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
194
198
  );
@@ -776,7 +780,7 @@ export async function parseFileAuto(
776
780
  const { native } = resolveEngine(opts);
777
781
 
778
782
  if (native) {
779
- const result = native.parseFile(filePath, source, !!opts.dataflow, opts.ast !== false);
783
+ const result = native.parseFile(filePath, source, true, true);
780
784
  if (!result) return null;
781
785
  const patched = patchNativeResult(result);
782
786
  // Always backfill typeMap for TS/TSX from WASM — native parser's type
@@ -874,7 +878,11 @@ export async function parseFilesAuto(
874
878
  if (!native) return parseFilesWasm(filePaths, rootDir);
875
879
 
876
880
  const result = new Map<string, ExtractorOutput>();
877
- const nativeResults = native.parseFiles(filePaths, rootDir, !!opts.dataflow, opts.ast !== false);
881
+ // Always extract all analysis data (dataflow + AST nodes) during native parse.
882
+ // This eliminates the need for any downstream WASM re-parse or native standalone calls.
883
+ const nativeResults = native.parseFilesFull
884
+ ? native.parseFilesFull(filePaths, rootDir)
885
+ : native.parseFiles(filePaths, rootDir, true, true);
878
886
  const needsTypeMap: { filePath: string; relPath: string }[] = [];
879
887
  for (const r of nativeResults) {
880
888
  if (!r) continue;
@@ -22,7 +22,7 @@ export {
22
22
  VALID_ROLES,
23
23
  } from '../shared/kinds.js';
24
24
  // ── Shared utilities ─────────────────────────────────────────────────────
25
- export { kindIcon, normalizeSymbol } from '../shared/normalize.js';
25
+ export { kindIcon, normalizeSymbol, toSymbolRef } from '../shared/normalize.js';
26
26
  export { briefData } from './analysis/brief.js';
27
27
  export { contextData, explainData } from './analysis/context.js';
28
28
  export { fileDepsData, filePathData, fnDepsData, pathData } from './analysis/dependencies.js';
@@ -115,8 +115,8 @@ function tryNativeBulkInsert(
115
115
  receiver: n.receiver ?? '',
116
116
  })),
117
117
  });
118
- } else if (symbols.calls || symbols._tree) {
119
- return false; // needs JS fallback
118
+ } else if (symbols._tree) {
119
+ return false; // has WASM tree not yet processed — needs JS fallback
120
120
  }
121
121
  }
122
122
 
@@ -7,6 +7,7 @@ import { loadConfig } from '../infrastructure/config.js';
7
7
  import { debug } from '../infrastructure/logger.js';
8
8
  import { isTestFile } from '../infrastructure/test-filter.js';
9
9
  import { toErrorMessage } from '../shared/errors.js';
10
+ import { toSymbolRef } from '../shared/normalize.js';
10
11
  import type { BetterSqlite3Database, CodegraphConfig } from '../types.js';
11
12
  import { RULE_DEFS } from './manifesto.js';
12
13
 
@@ -317,7 +318,7 @@ function enrichSymbol(
317
318
  WHERE e.source_id = ? AND e.kind = 'calls'`,
318
319
  )
319
320
  .all(nodeId) as SymbolRef[]
320
- ).map((c) => ({ name: c.name, kind: c.kind, file: c.file, line: c.line }));
321
+ ).map(toSymbolRef);
321
322
 
322
323
  callers = (
323
324
  db
@@ -327,7 +328,7 @@ function enrichSymbol(
327
328
  WHERE e.target_id = ? AND e.kind = 'calls'`,
328
329
  )
329
330
  .all(nodeId) as SymbolRef[]
330
- ).map((c) => ({ name: c.name, kind: c.kind, file: c.file, line: c.line }));
331
+ ).map(toSymbolRef);
331
332
  if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
332
333
 
333
334
  const testCallerRows = db
@@ -1,5 +1,5 @@
1
- import { debug } from '../infrastructure/logger.js';
2
1
  import { isTestFile } from '../infrastructure/test-filter.js';
2
+ import { BoundaryError } from '../shared/errors.js';
3
3
  import type { BetterSqlite3Database } from '../types.js';
4
4
 
5
5
  // ─── Glob-to-Regex ───────────────────────────────────────────────────
@@ -269,8 +269,7 @@ export function evaluateBoundaries(
269
269
 
270
270
  const { valid, errors } = validateBoundaryConfig(boundaryConfig);
271
271
  if (!valid) {
272
- debug(`boundary config validation failed: ${errors.join('; ')}`);
273
- return { violations: [], violationCount: 0 };
272
+ throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
274
273
  }
275
274
 
276
275
  const modules = resolveModules(boundaryConfig);
@@ -297,8 +296,7 @@ export function evaluateBoundaries(
297
296
  )
298
297
  .all() as Array<{ source: string; target: string }>;
299
298
  } catch (err) {
300
- debug(`boundary edge query failed: ${(err as Error).message}`);
301
- return { violations: [], violationCount: 0 };
299
+ throw new BoundaryError('Boundary evaluation failed', { cause: err as Error });
302
300
  }
303
301
 
304
302
  if (opts.noTests) {
@@ -9,6 +9,7 @@ import { debug } from '../infrastructure/logger.js';
9
9
  import { getNative, isNativeAvailable } from '../infrastructure/native.js';
10
10
  import { isTestFile } from '../infrastructure/test-filter.js';
11
11
  import { toErrorMessage } from '../shared/errors.js';
12
+ import { toSymbolRef } from '../shared/normalize.js';
12
13
  import type { EngineMode, NativeDatabase } from '../types.js';
13
14
 
14
15
  // ─── Git Helpers ────────────────────────────────────────────────────────
@@ -255,9 +256,7 @@ function loadCallersFromDb(
255
256
  if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
256
257
  visited.add(c.id);
257
258
  nextFrontier.push(c.id);
258
- allCallers.add(
259
- JSON.stringify({ name: c.name, kind: c.kind, file: c.file, line: c.line }),
260
- );
259
+ allCallers.add(JSON.stringify(toSymbolRef(c)));
261
260
  }
262
261
  }
263
262
  }
@@ -369,7 +369,7 @@ export async function buildCFGData(
369
369
  db: BetterSqlite3Database,
370
370
  fileSymbols: Map<string, FileSymbols>,
371
371
  rootDir: string,
372
- _engineOpts?: {
372
+ engineOpts?: {
373
373
  nativeDb?: { bulkInsertCfg?(entries: Array<Record<string, unknown>>): number };
374
374
  suspendJsDb?: () => void;
375
375
  resumeJsDb?: () => void;
@@ -379,11 +379,56 @@ export async function buildCFGData(
379
379
  // skip WASM parser init, tree parsing, and JS visitor entirely — just persist.
380
380
  const allNative = allCfgNative(fileSymbols);
381
381
 
382
- // NOTE: nativeDb.bulkInsertCfg is intentionally NOT used here.
383
- // The CFG path requires delete-before-insert (deleteCfgForNode) which creates
384
- // a dual-connection WAL conflict when deletes go through JS (better-sqlite3)
385
- // and inserts go through native (rusqlite). The JS-only persistNativeFileCfg
386
- // path below handles both on a single connection safely.
382
+ // ── Native bulk-insert fast path ──────────────────────────────────────
383
+ // The Rust bulkInsertCfg handles delete-before-insert atomically on a
384
+ // single rusqlite connection, so there is no dual-connection WAL conflict.
385
+ const nativeDb = engineOpts?.nativeDb;
386
+ if (allNative && nativeDb?.bulkInsertCfg) {
387
+ const entries: Array<Record<string, unknown>> = [];
388
+ for (const [relPath, symbols] of fileSymbols) {
389
+ const ext = path.extname(relPath).toLowerCase();
390
+ if (!CFG_EXTENSIONS.has(ext)) continue;
391
+
392
+ for (const def of symbols.definitions) {
393
+ if (def.kind !== 'function' && def.kind !== 'method') continue;
394
+ if (!def.line) continue;
395
+
396
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
397
+ if (!nodeId) continue;
398
+
399
+ const cfg = def.cfg as { blocks?: CfgBuildBlock[]; edges?: CfgBuildEdge[] } | undefined;
400
+ if (!cfg?.blocks?.length) continue;
401
+
402
+ entries.push({
403
+ nodeId,
404
+ blocks: cfg.blocks.map((b) => ({
405
+ index: b.index,
406
+ blockType: b.type,
407
+ startLine: b.startLine ?? undefined,
408
+ endLine: b.endLine ?? undefined,
409
+ label: b.label ?? undefined,
410
+ })),
411
+ edges: (cfg.edges || []).map((e) => ({
412
+ sourceIndex: e.sourceIndex,
413
+ targetIndex: e.targetIndex,
414
+ kind: e.kind,
415
+ })),
416
+ });
417
+ }
418
+ }
419
+
420
+ if (entries.length > 0) {
421
+ let inserted = 0;
422
+ try {
423
+ engineOpts?.suspendJsDb?.();
424
+ inserted = nativeDb.bulkInsertCfg(entries);
425
+ } finally {
426
+ engineOpts?.resumeJsDb?.();
427
+ }
428
+ info(`CFG (native bulk): ${inserted} functions analyzed`);
429
+ }
430
+ return;
431
+ }
387
432
 
388
433
  const extToLang = buildExtToLangMap();
389
434
  let parsers: unknown = null;
@@ -545,6 +545,10 @@ function collectNativeBulkRows(
545
545
  const rows: Array<Record<string, unknown>> = [];
546
546
 
547
547
  for (const [relPath, symbols] of fileSymbols) {
548
+ const ext = path.extname(relPath).toLowerCase();
549
+ const langId = symbols._langId || '';
550
+ const langSupported = COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(langId);
551
+
548
552
  for (const def of symbols.definitions) {
549
553
  if (def.kind !== 'function' && def.kind !== 'method') continue;
550
554
  if (!def.line) continue;
@@ -554,6 +558,9 @@ function collectNativeBulkRows(
554
558
  // of the native bulk-insert path for every TypeScript codebase (#846).
555
559
  if (!def.complexity) {
556
560
  if (def.name.includes('.') || !def.endLine || def.endLine <= def.line) continue;
561
+ // Languages without complexity rules will never have data — skip them
562
+ // rather than bailing out of the entire native bulk path.
563
+ if (!langSupported) continue;
557
564
  return null; // genuine function body missing complexity — needs JS fallback
558
565
  }
559
566
  const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
@@ -8,6 +8,7 @@
8
8
  import { openReadonlyOrFail } from '../db/index.js';
9
9
  import { CORE_SYMBOL_KINDS, findMatchingNodes } from '../domain/queries.js';
10
10
  import { isTestFile } from '../infrastructure/test-filter.js';
11
+ import { toSymbolRef } from '../shared/normalize.js';
11
12
  import { paginateResult } from '../shared/paginate.js';
12
13
  import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
13
14
 
@@ -175,7 +176,7 @@ function bfsCallees(
175
176
 
176
177
  visited.add(c.id);
177
178
  nextFrontier.push(c.id);
178
- const nodeInfo: NodeInfo = { name: c.name, kind: c.kind, file: c.file, line: c.line };
179
+ const nodeInfo: NodeInfo = toSymbolRef(c);
179
180
  levelNodes.push(nodeInfo);
180
181
  nodeDepths.set(c.id, d);
181
182
  idToNode.set(c.id, nodeInfo);
@@ -5,6 +5,7 @@ import { loadConfig } from '../infrastructure/config.js';
5
5
  import { debug } from '../infrastructure/logger.js';
6
6
  import { paginateResult } from '../shared/paginate.js';
7
7
  import type { BetterSqlite3Database, CodegraphConfig, ThresholdRule } from '../types.js';
8
+ import type { BoundaryViolation } from './boundaries.js';
8
9
  import { evaluateBoundaries } from './boundaries.js';
9
10
 
10
11
  // ─── Rule Definitions ─────────────────────────────────────────────────
@@ -416,7 +417,20 @@ function evaluateBoundaryRules(
416
417
  return;
417
418
  }
418
419
 
419
- const result = evaluateBoundaries(db, boundaryConfig, { noTests: opts.noTests || false });
420
+ let result: { violations: BoundaryViolation[]; violationCount: number };
421
+ try {
422
+ result = evaluateBoundaries(db, boundaryConfig, { noTests: opts.noTests || false });
423
+ } catch (e: unknown) {
424
+ debug(`boundary check failed: ${(e as Error).message}`);
425
+ ruleResults.push({
426
+ name: 'boundaries',
427
+ level: 'graph',
428
+ status: 'warn',
429
+ thresholds: effectiveThresholds,
430
+ violationCount: 0,
431
+ });
432
+ return;
433
+ }
420
434
  const hasBoundaryViolations = result.violationCount > 0;
421
435
 
422
436
  if (!hasBoundaryViolations) {
@@ -23,6 +23,7 @@ export const DEFAULTS = {
23
23
  incremental: true,
24
24
  dbPath: '.codegraph/graph.db',
25
25
  driftThreshold: 0.2,
26
+ smallFilesThreshold: 5,
26
27
  },
27
28
  query: {
28
29
  defaultDepth: 3,
@@ -18,11 +18,11 @@ interface UpdateCache {
18
18
 
19
19
  /**
20
20
  * Minimal semver comparison. Returns -1, 0, or 1.
21
- * Only handles numeric x.y.z (no pre-release tags).
21
+ * Strips pre-release suffixes (e.g. "3.9.3-dev.6" → "3.9.3") before comparing.
22
22
  */
23
23
  export function semverCompare(a: string, b: string): -1 | 0 | 1 {
24
- const pa = a.split('.').map(Number);
25
- const pb = b.split('.').map(Number);
24
+ const pa = a.replace(/-.*$/, '').split('.').map(Number);
25
+ const pb = b.replace(/-.*$/, '').split('.').map(Number);
26
26
  for (let i = 0; i < 3; i++) {
27
27
  const na = pa[i] || 0;
28
28
  const nb = pb[i] || 0;
@@ -36,6 +36,7 @@ export function batchQuery(
36
36
  const { command: defaultCommand = 'where', ...rest } = opts;
37
37
  const isMulti =
38
38
  targets.length > 0 &&
39
+ targets[0] !== null &&
39
40
  typeof targets[0] === 'object' &&
40
41
  !!(targets[0] as MultiBatchTarget).command;
41
42
 
@@ -75,7 +75,7 @@ export function formatHotspots(data: HotspotsResult): string {
75
75
 
76
76
  interface ModuleBoundaryEntry {
77
77
  directory: string;
78
- cohesion: number;
78
+ cohesion: number | null;
79
79
  fileCount: number;
80
80
  symbolCount: number;
81
81
  fanIn: number;
@@ -95,7 +95,7 @@ export function formatModuleBoundaries(data: ModuleBoundariesResult): string {
95
95
  const lines = [`\nModule boundaries (cohesion >= ${data.threshold}, ${data.count} modules):\n`];
96
96
  for (const m of data.modules) {
97
97
  lines.push(
98
- ` ${m.directory}/ cohesion=${m.cohesion.toFixed(2)} (${m.fileCount} files, ${m.symbolCount} symbols)`,
98
+ ` ${m.directory}/ cohesion=${m.cohesion !== null ? m.cohesion.toFixed(2) : 'n/a'} (${m.fileCount} files, ${m.symbolCount} symbols)`,
99
99
  );
100
100
  lines.push(` Incoming: ${m.fanIn} edges Outgoing: ${m.fanOut} edges`);
101
101
  if (m.files.length > 0) {
@@ -19,6 +19,16 @@ export function getFileHash(db: DbHandle, file: string): string | null {
19
19
  return row ? row.hash : null;
20
20
  }
21
21
 
22
+ /** Pick the 4-field symbol reference from any row that carries at least {name, kind, file, line}. */
23
+ export function toSymbolRef(row: { name: string; kind: string; file: string; line: number }): {
24
+ name: string;
25
+ kind: string;
26
+ file: string;
27
+ line: number;
28
+ } {
29
+ return { name: row.name, kind: row.kind, file: row.file, line: row.line };
30
+ }
31
+
22
32
  export function kindIcon(kind: string): string {
23
33
  switch (kind) {
24
34
  case 'function':
package/src/types.ts CHANGED
@@ -1085,6 +1085,7 @@ export interface CodegraphConfig {
1085
1085
  incremental: boolean;
1086
1086
  dbPath: string;
1087
1087
  driftThreshold: number;
1088
+ smallFilesThreshold: number;
1088
1089
  };
1089
1090
 
1090
1091
  query: {
@@ -1873,6 +1874,7 @@ export type StmtCache<TRow = unknown> = WeakMap<BetterSqlite3Database, SqliteSta
1873
1874
  export interface NativeAddon {
1874
1875
  parseFile(filePath: string, source: string, dataflow: boolean, ast: boolean): unknown;
1875
1876
  parseFiles(files: string[], rootDir: string, dataflow: boolean, ast: boolean): unknown[];
1877
+ parseFilesFull?(files: string[], rootDir: string): unknown[];
1876
1878
  resolveImport(fromFile: string, importSource: string, rootDir: string, aliases: unknown): string;
1877
1879
  resolveImports(
1878
1880
  items: Array<{ fromFile: string; importSource: string }>,