@optave/codegraph 3.11.2 → 3.13.0
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.
- package/README.md +73 -37
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +2 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +1 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +272 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/triage.js +1 -1
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/options.d.ts +2 -1
- package/dist/cli/shared/options.d.ts.map +1 -1
- package/dist/cli/shared/options.js +11 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +8 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts +2 -0
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +24 -2
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +16 -10
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +251 -34
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/cha.d.ts +69 -0
- package/dist/domain/graph/builder/cha.d.ts.map +1 -0
- package/dist/domain/graph/builder/cha.js +158 -0
- package/dist/domain/graph/builder/cha.js.map +1 -0
- package/dist/domain/graph/builder/context.d.ts +3 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +2 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +25 -1
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +178 -5
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +74 -2
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +37 -2
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +704 -34
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +3 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +4 -0
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.js +783 -37
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts +1 -0
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +10 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/journal.js +1 -1
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/resolver/points-to.d.ts +53 -0
- package/dist/domain/graph/resolver/points-to.d.ts.map +1 -0
- package/dist/domain/graph/resolver/points-to.js +213 -0
- package/dist/domain/graph/resolver/points-to.js.map +1 -0
- package/dist/domain/graph/resolver/ts-resolver.d.ts +9 -0
- package/dist/domain/graph/resolver/ts-resolver.d.ts.map +1 -0
- package/dist/domain/graph/resolver/ts-resolver.js +476 -0
- package/dist/domain/graph/resolver/ts-resolver.js.map +1 -0
- package/dist/domain/parser.d.ts +12 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +83 -20
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +35 -2
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
- package/dist/domain/wasm-worker-pool.js +34 -0
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +15 -1
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/c.js +3 -3
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/clojure.js +1 -1
- package/dist/extractors/clojure.js.map +1 -1
- package/dist/extractors/cpp.d.ts.map +1 -1
- package/dist/extractors/cpp.js +45 -4
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/csharp.d.ts.map +1 -1
- package/dist/extractors/csharp.js +37 -8
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/cuda.d.ts.map +1 -1
- package/dist/extractors/cuda.js +45 -4
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +6 -6
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/fsharp.js +1 -1
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/go.js +5 -5
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/haskell.js +1 -1
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/helpers.d.ts +11 -0
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +10 -9
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts +2 -0
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +1812 -71
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +5 -5
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/lua.js +1 -1
- package/dist/extractors/lua.js.map +1 -1
- package/dist/extractors/objc.js +3 -3
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/ocaml.js +1 -1
- package/dist/extractors/ocaml.js.map +1 -1
- package/dist/extractors/php.js +2 -2
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.js +7 -7
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/ruby.js +2 -2
- package/dist/extractors/ruby.js.map +1 -1
- package/dist/extractors/scala.js +1 -1
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.js +1 -1
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +4 -4
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +4 -4
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/structure-query.d.ts +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +6 -6
- package/dist/features/structure-query.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts +85 -2
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +408 -19
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/native.d.ts +11 -0
- package/dist/infrastructure/native.d.ts.map +1 -1
- package/dist/infrastructure/native.js +78 -5
- package/dist/infrastructure/native.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +27 -0
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +59 -1
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +5 -0
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/structure.d.ts +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js +2 -2
- package/dist/presentation/structure.js.map +1 -1
- package/dist/types.d.ts +221 -0
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +7 -8
- package/src/cli/commands/audit.ts +2 -1
- package/src/cli/commands/batch.ts +1 -0
- package/src/cli/commands/build.ts +6 -1
- package/src/cli/commands/config.ts +353 -0
- package/src/cli/commands/triage.ts +1 -1
- package/src/cli/index.ts +10 -0
- package/src/cli/shared/options.ts +11 -1
- package/src/cli/types.ts +2 -0
- package/src/db/migrations.ts +8 -1
- package/src/domain/analysis/module-map.ts +29 -1
- package/src/domain/graph/builder/call-resolver.ts +263 -35
- package/src/domain/graph/builder/cha.ts +192 -0
- package/src/domain/graph/builder/context.ts +3 -0
- package/src/domain/graph/builder/helpers.ts +195 -5
- package/src/domain/graph/builder/incremental.ts +80 -1
- package/src/domain/graph/builder/pipeline.ts +49 -2
- package/src/domain/graph/builder/stages/build-edges.ts +867 -32
- package/src/domain/graph/builder/stages/detect-changes.ts +4 -2
- package/src/domain/graph/builder/stages/finalize.ts +4 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +910 -43
- package/src/domain/graph/builder/stages/resolve-imports.ts +15 -1
- package/src/domain/graph/journal.ts +1 -1
- package/src/domain/graph/resolver/points-to.ts +254 -0
- package/src/domain/graph/resolver/ts-resolver.ts +536 -0
- package/src/domain/parser.ts +86 -17
- package/src/domain/wasm-worker-entry.ts +35 -2
- package/src/domain/wasm-worker-pool.ts +22 -0
- package/src/domain/wasm-worker-protocol.ts +15 -0
- package/src/extractors/c.ts +3 -3
- package/src/extractors/clojure.ts +1 -1
- package/src/extractors/cpp.ts +47 -4
- package/src/extractors/csharp.ts +33 -9
- package/src/extractors/cuda.ts +47 -4
- package/src/extractors/elixir.ts +6 -6
- package/src/extractors/fsharp.ts +1 -1
- package/src/extractors/go.ts +5 -5
- package/src/extractors/haskell.ts +1 -1
- package/src/extractors/helpers.ts +43 -0
- package/src/extractors/java.ts +10 -9
- package/src/extractors/javascript.ts +1929 -72
- package/src/extractors/kotlin.ts +5 -5
- package/src/extractors/lua.ts +1 -1
- package/src/extractors/objc.ts +3 -3
- package/src/extractors/ocaml.ts +1 -1
- package/src/extractors/php.ts +2 -2
- package/src/extractors/python.ts +7 -7
- package/src/extractors/ruby.ts +2 -2
- package/src/extractors/scala.ts +1 -1
- package/src/extractors/solidity.ts +1 -1
- package/src/extractors/swift.ts +4 -4
- package/src/extractors/zig.ts +4 -4
- package/src/features/structure-query.ts +7 -7
- package/src/index.ts +5 -1
- package/src/infrastructure/config.ts +494 -20
- package/src/infrastructure/native.ts +87 -5
- package/src/infrastructure/registry.ts +82 -1
- package/src/presentation/queries-cli/overview.ts +15 -1
- package/src/presentation/structure.ts +3 -3
- package/src/types.ts +235 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
|
@@ -7,7 +7,7 @@ import { createHash } from 'node:crypto';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { purgeFilesData } from '../../../db/index.js';
|
|
10
|
-
import { warn } from '../../../infrastructure/logger.js';
|
|
10
|
+
import { debug, warn } from '../../../infrastructure/logger.js';
|
|
11
11
|
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from '../../../shared/constants.js';
|
|
12
12
|
import { compileGlobs, matchesAny } from '../../../shared/globs.js';
|
|
13
13
|
import type {
|
|
@@ -48,6 +48,13 @@ export const BUILTIN_RECEIVERS: Set<string> = new Set([
|
|
|
48
48
|
'require',
|
|
49
49
|
]);
|
|
50
50
|
|
|
51
|
+
/** Phase 8.6: confidence penalty applied to CHA-dispatch edges. */
|
|
52
|
+
export const CHA_DISPATCH_PENALTY = 0.1;
|
|
53
|
+
/** Phase 8.6: fixed confidence for typed-receiver (interface/CHA) dispatch edges.
|
|
54
|
+
* File proximity is not meaningful for virtual dispatch — all three engine paths
|
|
55
|
+
* (WASM inline, WASM post-pass, native post-pass) must agree on this value. */
|
|
56
|
+
export const CHA_TYPED_DISPATCH_CONFIDENCE = 0.8;
|
|
57
|
+
|
|
51
58
|
/** Check if a directory entry should be skipped (ignored dirs, dotfiles). */
|
|
52
59
|
function shouldSkipEntry(entry: fs.Dirent, extraIgnore: Set<string> | null): boolean {
|
|
53
60
|
if (entry.name.startsWith('.') && entry.name !== '.') {
|
|
@@ -313,9 +320,9 @@ function getEdgeStmt(db: BetterSqlite3Database, chunkSize: number): SqliteStatem
|
|
|
313
320
|
}
|
|
314
321
|
let stmt = cache.get(chunkSize);
|
|
315
322
|
if (!stmt) {
|
|
316
|
-
const ph = '(
|
|
323
|
+
const ph = '(?,?,?,?,?,?)';
|
|
317
324
|
stmt = db.prepare(
|
|
318
|
-
'INSERT INTO edges (source_id,target_id,kind,confidence,dynamic) VALUES ' +
|
|
325
|
+
'INSERT INTO edges (source_id,target_id,kind,confidence,dynamic,technique) VALUES ' +
|
|
319
326
|
Array.from({ length: chunkSize }, () => ph).join(','),
|
|
320
327
|
);
|
|
321
328
|
cache.set(chunkSize, stmt);
|
|
@@ -344,7 +351,7 @@ export function batchInsertNodes(db: BetterSqlite3Database, rows: unknown[][]):
|
|
|
344
351
|
|
|
345
352
|
/**
|
|
346
353
|
* Batch-insert edge rows via multi-value INSERT statements.
|
|
347
|
-
* Each row: [source_id, target_id, kind, confidence, dynamic]
|
|
354
|
+
* Each row: [source_id, target_id, kind, confidence, dynamic, technique]
|
|
348
355
|
*/
|
|
349
356
|
export function batchInsertEdges(db: BetterSqlite3Database, rows: unknown[][]): void {
|
|
350
357
|
if (!rows.length) return;
|
|
@@ -355,8 +362,191 @@ export function batchInsertEdges(db: BetterSqlite3Database, rows: unknown[][]):
|
|
|
355
362
|
const vals: unknown[] = [];
|
|
356
363
|
for (let j = i; j < end; j++) {
|
|
357
364
|
const r = rows[j] as unknown[];
|
|
358
|
-
vals.push(r[0], r[1], r[2], r[3], r[4]);
|
|
365
|
+
vals.push(r[0], r[1], r[2], r[3], r[4], r[5] ?? null);
|
|
359
366
|
}
|
|
360
367
|
stmt.run(...vals);
|
|
361
368
|
}
|
|
362
369
|
}
|
|
370
|
+
|
|
371
|
+
/** Confidence assigned to CHA-expanded interface/abstract dispatch edges. */
|
|
372
|
+
export const CHA_DISPATCH_CONFIDENCE = 0.8;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* CHA (Class Hierarchy Analysis) post-pass.
|
|
376
|
+
*
|
|
377
|
+
* Expands virtual-dispatch call edges for class hierarchies and interface
|
|
378
|
+
* implementations already present in the DB:
|
|
379
|
+
*
|
|
380
|
+
* 1. Build implementors map: parent/interface → [child/implementing class] from
|
|
381
|
+
* `extends` and `implements` edges.
|
|
382
|
+
* 2. Collect RTA evidence: class nodes that appear as `calls` targets (new X()).
|
|
383
|
+
* 3. Find all `calls` edges to qualified method nodes (name contains '.').
|
|
384
|
+
* 4. For each such call, expand to concrete overrides via the implementors map,
|
|
385
|
+
* filtered by RTA when evidence exists.
|
|
386
|
+
*
|
|
387
|
+
* Used by both the native orchestrator post-pass and the WASM build-edges pass.
|
|
388
|
+
*/
|
|
389
|
+
export function runChaPostPass(db: BetterSqlite3Database): number {
|
|
390
|
+
const hasHierarchy = db
|
|
391
|
+
.prepare(`SELECT 1 FROM edges WHERE kind IN ('extends', 'implements') LIMIT 1`)
|
|
392
|
+
.get();
|
|
393
|
+
if (!hasHierarchy) return 0;
|
|
394
|
+
|
|
395
|
+
const hierarchyRows = db
|
|
396
|
+
.prepare(
|
|
397
|
+
`SELECT src.name AS child_name, tgt.name AS parent_name
|
|
398
|
+
FROM edges e
|
|
399
|
+
JOIN nodes src ON e.source_id = src.id
|
|
400
|
+
JOIN nodes tgt ON e.target_id = tgt.id
|
|
401
|
+
WHERE e.kind IN ('extends', 'implements')`,
|
|
402
|
+
)
|
|
403
|
+
.all() as Array<{ child_name: string; parent_name: string }>;
|
|
404
|
+
|
|
405
|
+
const implementorSets = new Map<string, Set<string>>();
|
|
406
|
+
for (const row of hierarchyRows) {
|
|
407
|
+
let set = implementorSets.get(row.parent_name);
|
|
408
|
+
if (!set) {
|
|
409
|
+
set = new Set<string>();
|
|
410
|
+
implementorSets.set(row.parent_name, set);
|
|
411
|
+
}
|
|
412
|
+
set.add(row.child_name);
|
|
413
|
+
}
|
|
414
|
+
if (implementorSets.size === 0) return 0;
|
|
415
|
+
// Convert to arrays for iteration compatibility with the rest of the function
|
|
416
|
+
const implementors = new Map([...implementorSets.entries()].map(([k, v]) => [k, [...v]]));
|
|
417
|
+
|
|
418
|
+
// RTA: collect class names instantiated via constructor calls (`new X()`).
|
|
419
|
+
let rtaRows = db
|
|
420
|
+
.prepare(
|
|
421
|
+
`SELECT DISTINCT tgt.name
|
|
422
|
+
FROM edges e
|
|
423
|
+
JOIN nodes tgt ON e.target_id = tgt.id
|
|
424
|
+
WHERE e.kind = 'calls' AND tgt.kind = 'class'`,
|
|
425
|
+
)
|
|
426
|
+
.all() as Array<{ name: string }>;
|
|
427
|
+
if (rtaRows.length === 0) {
|
|
428
|
+
// Fallback: some languages (e.g. TypeScript via WASM) record constructor calls as
|
|
429
|
+
// 'function' or 'constructor' kind rather than 'class'. Restrict to names that are
|
|
430
|
+
// actually known class names to avoid treating unrelated function calls like `logger()`
|
|
431
|
+
// as class-instantiation evidence.
|
|
432
|
+
// Include both parent/interface names AND implementor (child) names so that
|
|
433
|
+
// `new UserRepository()` (a child class) is correctly detected as RTA evidence.
|
|
434
|
+
const knownClassNames = [
|
|
435
|
+
...new Set([
|
|
436
|
+
...implementorSets.keys(),
|
|
437
|
+
...[...implementorSets.values()].flatMap((s) => [...s]),
|
|
438
|
+
]),
|
|
439
|
+
];
|
|
440
|
+
if (knownClassNames.length > 0) {
|
|
441
|
+
// Chunk to stay within SQLite SQLITE_MAX_VARIABLE_NUMBER (999 in many builds).
|
|
442
|
+
const CHUNK = 999;
|
|
443
|
+
for (let i = 0; i < knownClassNames.length; i += CHUNK) {
|
|
444
|
+
const chunk = knownClassNames.slice(i, i + CHUNK);
|
|
445
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
446
|
+
const chunkRows = db
|
|
447
|
+
.prepare(
|
|
448
|
+
`SELECT DISTINCT tgt.name
|
|
449
|
+
FROM edges e
|
|
450
|
+
JOIN nodes tgt ON e.target_id = tgt.id
|
|
451
|
+
WHERE e.kind = 'calls' AND tgt.kind IN ('constructor', 'function')
|
|
452
|
+
AND tgt.name IN (${placeholders})`,
|
|
453
|
+
)
|
|
454
|
+
.all(...chunk) as Array<{ name: string }>;
|
|
455
|
+
rtaRows = rtaRows.concat(chunkRows);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const instantiated = new Set(rtaRows.map((r) => r.name));
|
|
460
|
+
const noRtaEvidence = instantiated.size === 0;
|
|
461
|
+
if (noRtaEvidence) {
|
|
462
|
+
debug('runChaPostPass: no constructor-call evidence — proceeding without RTA filter');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const callToMethods = db
|
|
466
|
+
.prepare(
|
|
467
|
+
`SELECT e.source_id, src.name AS caller_name, tgt.name AS method_name
|
|
468
|
+
FROM edges e
|
|
469
|
+
JOIN nodes tgt ON e.target_id = tgt.id
|
|
470
|
+
JOIN nodes src ON e.source_id = src.id
|
|
471
|
+
WHERE e.kind = 'calls' AND tgt.kind = 'method'
|
|
472
|
+
AND INSTR(tgt.name, '.') > 0
|
|
473
|
+
AND (e.technique IS NULL OR e.technique != 'cha-expanded')`,
|
|
474
|
+
)
|
|
475
|
+
.all() as Array<{ source_id: number; caller_name: string; method_name: string }>;
|
|
476
|
+
|
|
477
|
+
const seen = new Set<string>();
|
|
478
|
+
// Scope deduplication to only the source_ids we are about to expand, avoiding
|
|
479
|
+
// a full-table scan. CHA only inserts edges FROM callers that already call a
|
|
480
|
+
// qualified method (the source_ids in callToMethods), so we only need to
|
|
481
|
+
// check existing edges for those specific callers.
|
|
482
|
+
const callerIds = [...new Set(callToMethods.map((r) => r.source_id))];
|
|
483
|
+
if (callerIds.length > 0) {
|
|
484
|
+
// Chunk to stay within SQLite SQLITE_MAX_VARIABLE_NUMBER (999 in many builds).
|
|
485
|
+
const CHUNK = 999;
|
|
486
|
+
for (let i = 0; i < callerIds.length; i += CHUNK) {
|
|
487
|
+
const chunk = callerIds.slice(i, i + CHUNK);
|
|
488
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
489
|
+
const existingPairs = db
|
|
490
|
+
.prepare(
|
|
491
|
+
`SELECT source_id, target_id FROM edges WHERE kind = 'calls' AND source_id IN (${placeholders})`,
|
|
492
|
+
)
|
|
493
|
+
.all(...chunk) as Array<{ source_id: number; target_id: number }>;
|
|
494
|
+
for (const e of existingPairs) seen.add(`${e.source_id}|${e.target_id}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// No LIMIT: multiple files can define the same qualified name in a monorepo.
|
|
499
|
+
const findMethodStmt = db.prepare(`SELECT id FROM nodes WHERE name = ? AND kind = 'method'`);
|
|
500
|
+
const newEdges: Array<[number, number, string, number, number, string]> = [];
|
|
501
|
+
|
|
502
|
+
for (const { source_id, method_name } of callToMethods) {
|
|
503
|
+
const dotIdx = method_name.indexOf('.');
|
|
504
|
+
if (dotIdx === -1) continue;
|
|
505
|
+
const typeName = method_name.slice(0, dotIdx);
|
|
506
|
+
const methodSuffix = method_name.slice(dotIdx + 1);
|
|
507
|
+
|
|
508
|
+
// BFS over the implementors map — handles multi-level hierarchies where
|
|
509
|
+
// abstract/non-instantiated classes sit between the call-site type and
|
|
510
|
+
// the concrete leaf implementations (matches runPostNativeCha, issue #1311).
|
|
511
|
+
const bfsQueue: string[] = [typeName];
|
|
512
|
+
const bfsVisited = new Set<string>([typeName]);
|
|
513
|
+
while (bfsQueue.length > 0) {
|
|
514
|
+
const current = bfsQueue.shift()!;
|
|
515
|
+
const children = implementors.get(current);
|
|
516
|
+
if (!children?.length) continue;
|
|
517
|
+
|
|
518
|
+
for (const cls of children) {
|
|
519
|
+
if (bfsVisited.has(cls)) continue;
|
|
520
|
+
bfsVisited.add(cls);
|
|
521
|
+
|
|
522
|
+
if (noRtaEvidence || instantiated.has(cls)) {
|
|
523
|
+
const qualifiedName = `${cls}.${methodSuffix}`;
|
|
524
|
+
const methodNodes = findMethodStmt.all(qualifiedName) as Array<{ id: number }>;
|
|
525
|
+
for (const methodNode of methodNodes) {
|
|
526
|
+
if (methodNode.id === source_id) continue; // skip self-loops
|
|
527
|
+
const key = `${source_id}|${methodNode.id}`;
|
|
528
|
+
if (seen.has(key)) continue;
|
|
529
|
+
seen.add(key);
|
|
530
|
+
newEdges.push([
|
|
531
|
+
source_id,
|
|
532
|
+
methodNode.id,
|
|
533
|
+
'calls',
|
|
534
|
+
CHA_TYPED_DISPATCH_CONFIDENCE,
|
|
535
|
+
0,
|
|
536
|
+
'cha-expanded',
|
|
537
|
+
]);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Always traverse children — non-instantiated classes may have instantiated subclasses.
|
|
542
|
+
bfsQueue.push(cls);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (newEdges.length > 0) {
|
|
548
|
+
db.transaction(() => batchInsertEdges(db, newEdges))();
|
|
549
|
+
debug(`runChaPostPass: inserted ${newEdges.length} CHA dispatch edge(s)`);
|
|
550
|
+
}
|
|
551
|
+
return newEdges.length;
|
|
552
|
+
}
|
|
@@ -502,6 +502,34 @@ function buildCallEdges(
|
|
|
502
502
|
]),
|
|
503
503
|
)
|
|
504
504
|
: new Map();
|
|
505
|
+
|
|
506
|
+
// Phase 8.3f: seed typeMap[callee::restName] = { type: argName } from
|
|
507
|
+
// objectRestParamBindings × paramBindings, mirroring buildObjectRestParamPostPass.
|
|
508
|
+
// Scoped keys prevent same-name rest-param collisions when two functions in
|
|
509
|
+
// the same file both use `...rest` (#1358). The unscoped key is also seeded
|
|
510
|
+
// when only one callee uses a given rest name, preserving resolution when
|
|
511
|
+
// callerName is null (findCaller couldn't identify the enclosing function).
|
|
512
|
+
if (symbols.objectRestParamBindings?.length && symbols.paramBindings?.length) {
|
|
513
|
+
const restNameCallees = new Map<string, Set<string>>();
|
|
514
|
+
for (const orpb of symbols.objectRestParamBindings) {
|
|
515
|
+
if (!restNameCallees.has(orpb.restName)) restNameCallees.set(orpb.restName, new Set());
|
|
516
|
+
restNameCallees.get(orpb.restName)!.add(orpb.callee);
|
|
517
|
+
}
|
|
518
|
+
for (const orpb of symbols.objectRestParamBindings) {
|
|
519
|
+
for (const pb of symbols.paramBindings) {
|
|
520
|
+
if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
|
|
521
|
+
const scopedKey = `${orpb.callee}::${orpb.restName}`;
|
|
522
|
+
if (!typeMap.has(scopedKey)) {
|
|
523
|
+
typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
|
|
524
|
+
if (restNameCallees.get(orpb.restName)!.size === 1 && !typeMap.has(orpb.restName)) {
|
|
525
|
+
typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
505
533
|
const seenCallEdges = new Set<string>();
|
|
506
534
|
const lookup = makeIncrementalLookup(db, stmts);
|
|
507
535
|
let edgesAdded = 0;
|
|
@@ -510,13 +538,63 @@ function buildCallEdges(
|
|
|
510
538
|
if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
|
|
511
539
|
|
|
512
540
|
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
513
|
-
const { targets, importedFrom } = resolveCallTargets(
|
|
541
|
+
const { targets: initialTargets, importedFrom } = resolveCallTargets(
|
|
514
542
|
lookup,
|
|
515
543
|
call,
|
|
516
544
|
relPath,
|
|
517
545
|
importedNames,
|
|
518
546
|
typeMap,
|
|
547
|
+
caller.callerName,
|
|
519
548
|
);
|
|
549
|
+
let targets = initialTargets;
|
|
550
|
+
|
|
551
|
+
if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
|
|
552
|
+
const dotIdx = caller.callerName.indexOf('.');
|
|
553
|
+
if (dotIdx > 0) {
|
|
554
|
+
const className = caller.callerName.slice(0, dotIdx);
|
|
555
|
+
const qualifiedName = `${className}.${call.name}`;
|
|
556
|
+
const qualified = lookup
|
|
557
|
+
.byNameAndFile(qualifiedName, relPath)
|
|
558
|
+
.filter((n) => n.kind === 'method');
|
|
559
|
+
if (qualified.length > 0) {
|
|
560
|
+
targets = qualified;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (
|
|
566
|
+
targets.length === 0 &&
|
|
567
|
+
call.receiver === 'this' &&
|
|
568
|
+
caller.callerName != null &&
|
|
569
|
+
symbols.definePropertyReceivers
|
|
570
|
+
) {
|
|
571
|
+
const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
|
|
572
|
+
if (receiverVarName) {
|
|
573
|
+
const typeEntry = typeMap.get(receiverVarName);
|
|
574
|
+
const typeName = typeEntry
|
|
575
|
+
? typeof typeEntry === 'string'
|
|
576
|
+
? typeEntry
|
|
577
|
+
: (typeEntry as { type?: string }).type
|
|
578
|
+
: null;
|
|
579
|
+
if (typeName) {
|
|
580
|
+
const qualifiedName = `${typeName}.${call.name}`;
|
|
581
|
+
const qualified = lookup.byNameAndFile(qualifiedName, relPath);
|
|
582
|
+
if (qualified.length > 0) {
|
|
583
|
+
targets = [...qualified];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (targets.length === 0) {
|
|
587
|
+
// Narrow to function/method kinds only to avoid matching unrelated
|
|
588
|
+
// variables or classes that share a name in the same file.
|
|
589
|
+
const sameFile = lookup
|
|
590
|
+
.byNameAndFile(call.name, relPath)
|
|
591
|
+
.filter((n) => n.kind === 'function' || n.kind === 'method');
|
|
592
|
+
if (sameFile.length > 0) {
|
|
593
|
+
targets = [...sameFile];
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
520
598
|
|
|
521
599
|
for (const t of targets) {
|
|
522
600
|
const edgeKey = `${caller.id}|${t.id}`;
|
|
@@ -542,6 +620,7 @@ function buildCallEdges(
|
|
|
542
620
|
relPath,
|
|
543
621
|
typeMap,
|
|
544
622
|
seenCallEdges,
|
|
623
|
+
importedNames,
|
|
545
624
|
);
|
|
546
625
|
if (recv) {
|
|
547
626
|
stmts.insertEdge.run(recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0);
|
|
@@ -15,7 +15,14 @@ import {
|
|
|
15
15
|
MIGRATIONS,
|
|
16
16
|
openDb,
|
|
17
17
|
} from '../../../db/index.js';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
computeConfigHash,
|
|
20
|
+
detectWorkspaces,
|
|
21
|
+
getLastAppliedGlobalConfig,
|
|
22
|
+
getLastAppliedGlobalPath,
|
|
23
|
+
loadConfig,
|
|
24
|
+
promptForConsentIfNeeded,
|
|
25
|
+
} from '../../../infrastructure/config.js';
|
|
19
26
|
import { debug, info, warn } from '../../../infrastructure/logger.js';
|
|
20
27
|
import { loadNative } from '../../../infrastructure/native.js';
|
|
21
28
|
import { toErrorMessage } from '../../../shared/errors.js';
|
|
@@ -114,6 +121,16 @@ function checkEngineSchemaMismatch(ctx: PipelineContext): void {
|
|
|
114
121
|
);
|
|
115
122
|
ctx.forceFullRebuild = true;
|
|
116
123
|
}
|
|
124
|
+
|
|
125
|
+
// Config hash — promotes to full rebuild when build-relevant config changes
|
|
126
|
+
// (include/exclude/ignoreDirs/extensions/aliases/build.*).
|
|
127
|
+
// This closes the pre-existing config-change gap and covers the new global-config layer.
|
|
128
|
+
const currentConfigHash = computeConfigHash(ctx.config);
|
|
129
|
+
const prevConfigHash = meta('config_hash');
|
|
130
|
+
if (prevConfigHash && prevConfigHash !== currentConfigHash) {
|
|
131
|
+
info('Build-relevant config changed, promoting to full rebuild.');
|
|
132
|
+
ctx.forceFullRebuild = true;
|
|
133
|
+
}
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
function warnOnEmbeddingsWipe(ctx: PipelineContext): void {
|
|
@@ -172,7 +189,7 @@ function setupPipeline(ctx: PipelineContext): void {
|
|
|
172
189
|
ctx.db = openDb(ctx.dbPath);
|
|
173
190
|
initSchema(ctx.db);
|
|
174
191
|
|
|
175
|
-
ctx.config = loadConfig(ctx.rootDir);
|
|
192
|
+
ctx.config = loadConfig(ctx.rootDir, { userConfig: ctx.opts.userConfig });
|
|
176
193
|
// Merge caller-supplied excludes on top of the file-config excludes so
|
|
177
194
|
// programmatic callers (e.g. benchmark scripts) can extend exclusion
|
|
178
195
|
// without mutating .codegraphrc.json. Native orchestrator picks this up
|
|
@@ -186,6 +203,30 @@ function setupPipeline(ctx: PipelineContext): void {
|
|
|
186
203
|
ctx.incremental =
|
|
187
204
|
ctx.opts.incremental !== false && ctx.config.build && ctx.config.build.incremental !== false;
|
|
188
205
|
|
|
206
|
+
// ── Build-time global-config notice ──────────────────────────────
|
|
207
|
+
// Use the already-parsed and sanitized global config cached by loadConfig —
|
|
208
|
+
// avoids a second disk read and the TOCTOU window between loadConfig and here.
|
|
209
|
+
const appliedGlobalPath = getLastAppliedGlobalPath();
|
|
210
|
+
if (appliedGlobalPath) {
|
|
211
|
+
const buildAffectingKeys = [
|
|
212
|
+
'include',
|
|
213
|
+
'exclude',
|
|
214
|
+
'ignoreDirs',
|
|
215
|
+
'extensions',
|
|
216
|
+
'aliases',
|
|
217
|
+
'build',
|
|
218
|
+
];
|
|
219
|
+
const globalData = getLastAppliedGlobalConfig();
|
|
220
|
+
if (globalData) {
|
|
221
|
+
const injectedKeys = buildAffectingKeys.filter((k) => k in globalData);
|
|
222
|
+
if (injectedKeys.length > 0) {
|
|
223
|
+
process.stderr.write(
|
|
224
|
+
`ℹ global config applied (${appliedGlobalPath}) — injecting: ${injectedKeys.join(', ')} · --no-user-config to ignore\n`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
189
230
|
initializeEngine(ctx);
|
|
190
231
|
checkEngineSchemaMismatch(ctx);
|
|
191
232
|
warnOnEmbeddingsWipe(ctx);
|
|
@@ -348,6 +389,12 @@ export async function buildGraph(
|
|
|
348
389
|
ctx.rootDir = rootDir;
|
|
349
390
|
|
|
350
391
|
try {
|
|
392
|
+
// Interactive consent prompt — only fires when the caller opts in (build
|
|
393
|
+
// command with TTY), a global file exists, and the repo is undecided.
|
|
394
|
+
if (opts.promptForConsent) {
|
|
395
|
+
await promptForConsentIfNeeded(rootDir);
|
|
396
|
+
}
|
|
397
|
+
|
|
351
398
|
setupPipeline(ctx);
|
|
352
399
|
|
|
353
400
|
// ── JS-side fast-skip for native incremental (#1054) ──────────────
|