@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
@@ -2,7 +2,7 @@ import { findFileNodes, type Repository } from '../../db/index.js';
2
2
  import { cachedStmt } from '../../db/repository/cached-stmt.js';
3
3
  import { isTestFile } from '../../infrastructure/test-filter.js';
4
4
  import { resolveMethodViaHierarchy } from '../../shared/hierarchy.js';
5
- import { normalizeSymbol } from '../../shared/normalize.js';
5
+ import { normalizeSymbol, toSymbolRef } from '../../shared/normalize.js';
6
6
  import { paginateResult } from '../../shared/paginate.js';
7
7
  import type {
8
8
  BetterSqlite3Database,
@@ -143,12 +143,8 @@ function buildNodeDepsResult(
143
143
 
144
144
  return {
145
145
  ...normalizeSymbol(node, repo, hc),
146
- callees: filteredCallees.map((c) => ({
147
- name: c.name,
148
- kind: c.kind,
149
- file: c.file,
150
- line: c.line,
151
- })),
146
+ callees: filteredCallees.map(toSymbolRef),
147
+ // Not using toSymbolRef — callers include the extra viaHierarchy field
152
148
  callers: callers.map((c) => ({
153
149
  name: c.name,
154
150
  kind: c.kind,
@@ -245,20 +241,14 @@ function resolveEndpoints(
245
241
  to,
246
242
  found: false,
247
243
  error: `No symbol matching "${to}"`,
248
- fromCandidates: fromNodes
249
- .slice(0, 5)
250
- .map((n) => ({ name: n.name, kind: n.kind, file: n.file, line: n.line })),
244
+ fromCandidates: fromNodes.slice(0, 5).map(toSymbolRef),
251
245
  toCandidates: [],
252
246
  },
253
247
  };
254
248
  }
255
249
 
256
- const fromCandidates = fromNodes
257
- .slice(0, 5)
258
- .map((n) => ({ name: n.name, kind: n.kind, file: n.file, line: n.line }));
259
- const toCandidates = toNodes
260
- .slice(0, 5)
261
- .map((n) => ({ name: n.name, kind: n.kind, file: n.file, line: n.line }));
250
+ const fromCandidates = fromNodes.slice(0, 5).map(toSymbolRef);
251
+ const toCandidates = toNodes.slice(0, 5).map(toSymbolRef);
262
252
 
263
253
  return {
264
254
  sourceNode: fromNodes[0],
@@ -307,6 +307,33 @@ export function diffImpactData(
307
307
  }
308
308
 
309
309
  const affectedFunctions = findAffectedFunctions(db, changedRanges, noTests);
310
+
311
+ // Short-circuit: when no function-level changes detected, skip expensive
312
+ // lookups (BFS, co-change, ownership). Boundary checks are preserved
313
+ // because they are file-scoped and can surface real violations even when
314
+ // no function bodies were touched (e.g. import or type-alias changes).
315
+ if (affectedFunctions.length === 0) {
316
+ const { boundaryViolations, boundaryViolationCount } = checkBoundaryViolations(
317
+ db,
318
+ changedRanges,
319
+ noTests,
320
+ { ...opts, config },
321
+ repoRoot,
322
+ );
323
+ const base = {
324
+ changedFiles: changedRanges.size,
325
+ newFiles: [...newFiles],
326
+ affectedFunctions: [] as unknown[],
327
+ affectedFiles: [] as string[],
328
+ historicallyCoupled: [] as unknown[],
329
+ ownership: null,
330
+ boundaryViolations,
331
+ boundaryViolationCount,
332
+ summary: null as null,
333
+ };
334
+ return paginateResult(base, 'affectedFunctions', { limit: opts.limit, offset: opts.offset });
335
+ }
336
+
310
337
  const includeImplementors = opts.includeImplementors !== false;
311
338
  const { functionResults, allAffected } = buildFunctionImpactResults(
312
339
  db,
@@ -325,7 +352,7 @@ export function diffImpactData(
325
352
  db,
326
353
  changedRanges,
327
354
  noTests,
328
- opts,
355
+ { ...opts, config },
329
356
  repoRoot,
330
357
  );
331
358
 
@@ -1,6 +1,6 @@
1
1
  import { Repository, SqliteRepository } from '../../db/index.js';
2
2
  import { isTestFile } from '../../infrastructure/test-filter.js';
3
- import { normalizeSymbol } from '../../shared/normalize.js';
3
+ import { normalizeSymbol, toSymbolRef } from '../../shared/normalize.js';
4
4
  import { paginateResult } from '../../shared/paginate.js';
5
5
  import type { BetterSqlite3Database, NodeRow, RelatedNodeRow } from '../../types.js';
6
6
  import { resolveAnalysisOpts, withRepo } from './query-helpers.js';
@@ -125,7 +125,7 @@ export function bfsTransitiveCallers(
125
125
  visited.add(c.id);
126
126
  nextFrontier.push(c.id);
127
127
  if (!levels[d]) levels[d] = [];
128
- levels[d]!.push({ name: c.name, kind: c.kind, file: c.file, line: c.line });
128
+ levels[d]!.push(toSymbolRef(c));
129
129
  if (onVisit) onVisit(c, fid, d);
130
130
  }
131
131
  if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
@@ -1,6 +1,6 @@
1
1
  import { isTestFile } from '../../infrastructure/test-filter.js';
2
2
  import { CORE_SYMBOL_KINDS } from '../../shared/kinds.js';
3
- import { normalizeSymbol } from '../../shared/normalize.js';
3
+ import { normalizeSymbol, toSymbolRef } from '../../shared/normalize.js';
4
4
  import { paginateResult } from '../../shared/paginate.js';
5
5
  import type { RelatedNodeRow } from '../../types.js';
6
6
  import { withRepo } from './query-helpers.js';
@@ -34,12 +34,7 @@ export function implementationsData(
34
34
 
35
35
  return {
36
36
  ...normalizeSymbol(node, repo, hc),
37
- implementors: implementors.map((impl) => ({
38
- name: impl.name,
39
- kind: impl.kind,
40
- file: impl.file,
41
- line: impl.line,
42
- })),
37
+ implementors: implementors.map(toSymbolRef),
43
38
  };
44
39
  });
45
40
 
@@ -76,12 +71,7 @@ export function interfacesData(
76
71
 
77
72
  return {
78
73
  ...normalizeSymbol(node, repo, hc),
79
- interfaces: interfaces.map((iface) => ({
80
- name: iface.name,
81
- kind: iface.kind,
82
- file: iface.file,
83
- line: iface.line,
84
- })),
74
+ interfaces: interfaces.map(toSymbolRef),
85
75
  };
86
76
  });
87
77
 
@@ -33,6 +33,10 @@ export class PipelineContext {
33
33
  forceFullRebuild: boolean = false;
34
34
  schemaVersion!: number;
35
35
  nativeDb?: NativeDatabase;
36
+ /** Whether native engine is available (deferred — DB opened only when needed). */
37
+ nativeAvailable: boolean = false;
38
+ /** True when ctx.db is a NativeDbProxy — single rusqlite connection for the entire pipeline. */
39
+ nativeFirstProxy: boolean = false;
36
40
 
37
41
  // ── File collection (set by collectFiles stage) ────────────────────
38
42
  allFiles!: string[];
@@ -0,0 +1,104 @@
1
+ /**
2
+ * NativeDbProxy — wraps a NativeDatabase (rusqlite via napi-rs) to satisfy the
3
+ * BetterSqlite3Database interface. When the native addon is available, the
4
+ * build pipeline uses this proxy as `ctx.db` so that every stage operates on a
5
+ * single rusqlite connection — no dual-connection WAL corruption, no
6
+ * open/close/reopen dance.
7
+ *
8
+ * When native is unavailable, the pipeline falls back to real better-sqlite3.
9
+ */
10
+
11
+ import type { BetterSqlite3Database, NativeDatabase, SqliteStatement } from '../../../types.js';
12
+
13
+ /** Sanitize params for napi-rs: better-sqlite3 treats `undefined` as NULL,
14
+ * but serde_json cannot represent `undefined`. Replace with `null`. */
15
+ function sanitize(params: unknown[]): Array<string | number | null> {
16
+ return params.map((p) => (p === undefined ? null : p)) as Array<string | number | null>;
17
+ }
18
+
19
+ export class NativeDbProxy implements BetterSqlite3Database {
20
+ readonly #ndb: NativeDatabase;
21
+ /** Advisory lock path — set by the pipeline so closeDb() can release it. */
22
+ __lockPath?: string;
23
+
24
+ constructor(nativeDb: NativeDatabase) {
25
+ this.#ndb = nativeDb;
26
+ }
27
+
28
+ prepare<TRow = unknown>(sql: string): SqliteStatement<TRow> {
29
+ const ndb = this.#ndb;
30
+ // Only INSERT statements need last_insert_rowid — skip the extra napi
31
+ // call for UPDATE/DELETE/other DML to halve per-statement overhead.
32
+ const isInsert = sql.trimStart().substring(0, 6).toUpperCase() === 'INSERT';
33
+ const stmt: SqliteStatement<TRow> = {
34
+ all(...params: unknown[]): TRow[] {
35
+ return ndb.queryAll(sql, sanitize(params)) as TRow[];
36
+ },
37
+ get(...params: unknown[]): TRow | undefined {
38
+ return (ndb.queryGet(sql, sanitize(params)) ?? undefined) as TRow | undefined;
39
+ },
40
+ run(...params: unknown[]): { changes: number; lastInsertRowid: number | bigint } {
41
+ ndb.queryAll(sql, sanitize(params));
42
+ if (isInsert) {
43
+ const row = ndb.queryGet('SELECT last_insert_rowid() AS rid', []) as {
44
+ rid: number;
45
+ } | null;
46
+ return { changes: 0, lastInsertRowid: row?.rid ?? 0 };
47
+ }
48
+ return { changes: 0, lastInsertRowid: 0 };
49
+ },
50
+ iterate(): IterableIterator<TRow> {
51
+ throw new Error('iterate() is not supported via NativeDbProxy');
52
+ },
53
+ raw(): SqliteStatement<TRow> {
54
+ return stmt; // no-op — .raw() is not used in the build pipeline
55
+ },
56
+ };
57
+ return stmt;
58
+ }
59
+
60
+ exec(sql: string): this {
61
+ this.#ndb.exec(sql);
62
+ return this;
63
+ }
64
+
65
+ pragma(sql: string): unknown {
66
+ return this.#ndb.pragma(sql);
67
+ }
68
+
69
+ close(): void {
70
+ // No-op: the pipeline manages the NativeDatabase lifecycle directly.
71
+ // closeDbPair() calls nativeDb.close() separately.
72
+ }
73
+
74
+ get open(): boolean {
75
+ return this.#ndb.isOpen;
76
+ }
77
+
78
+ get name(): string {
79
+ return this.#ndb.dbPath;
80
+ }
81
+
82
+ transaction<F extends (...args: any[]) => any>(
83
+ fn: F,
84
+ ): (...args: F extends (...a: infer A) => unknown ? A : never) => ReturnType<F> {
85
+ const ndb = this.#ndb;
86
+ return ((...args: unknown[]) => {
87
+ // NOTE: nested transactions (savepoints) are not supported — ensure callers
88
+ // do not invoke a transaction() wrapper from within an existing transaction.
89
+ ndb.exec('BEGIN');
90
+ try {
91
+ const result = fn(...args);
92
+ ndb.exec('COMMIT');
93
+ return result;
94
+ } catch (e) {
95
+ try {
96
+ ndb.exec('ROLLBACK');
97
+ } catch {
98
+ // Ignore rollback errors — the original error is more important
99
+ }
100
+ throw e;
101
+ }
102
+ }) as any;
103
+ }
104
+ }