diff --git a/scripts/benchmark.ts b/scripts/benchmark.ts index 642e2b1f5..65cad01a3 100644 --- a/scripts/benchmark.ts +++ b/scripts/benchmark.ts @@ -17,6 +17,7 @@ import { fileURLToPath } from 'node:url'; import Database from 'better-sqlite3'; import { resolveBenchmarkExcludes, resolveBenchmarkSource, srcImport } from './lib/bench-config.js'; import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js'; +import { median, round1, timeMedian } from './lib/bench-timing.js'; // ── Parent process: fork one child per engine, assemble final output ───── if (!isWorker()) { @@ -97,16 +98,6 @@ const QUERY_WARMUP_RUNS = 3; const PROBE_FILE = path.join(root, 'src', 'domain', 'queries.ts'); const BENCH_EXCLUDE = [...resolveBenchmarkExcludes()]; -function median(arr) { - const sorted = [...arr].sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; -} - -function round1(n) { - return Math.round(n * 10) / 10; -} - function selectTargets() { const db = new Database(dbPath, { readonly: true }); const rows = db @@ -158,13 +149,12 @@ try { for (let i = 0; i < WARMUP_RUNS; i++) { await buildGraph(root, { engine, incremental: true, exclude: BENCH_EXCLUDE }); } - const noopTimings = []; - for (let i = 0; i < INCREMENTAL_RUNS; i++) { - const start = performance.now(); - await buildGraph(root, { engine, incremental: true, exclude: BENCH_EXCLUDE }); - noopTimings.push(performance.now() - start); - } - noopRebuildMs = Math.round(median(noopTimings)); + noopRebuildMs = Math.round( + await timeMedian( + () => buildGraph(root, { engine, incremental: true, exclude: BENCH_EXCLUDE }), + INCREMENTAL_RUNS, + ), + ); } catch (err) { console.error(` [${engine}] No-op rebuild failed: ${(err as Error).message}`); } diff --git a/scripts/incremental-benchmark.ts b/scripts/incremental-benchmark.ts index 853d595da..b2f3b53d7 100644 --- a/scripts/incremental-benchmark.ts +++ b/scripts/incremental-benchmark.ts @@ -16,6 +16,7 @@ import { performance } from 'node:perf_hooks'; import { fileURLToPath } from 'node:url'; import { resolveBenchmarkExcludes, resolveBenchmarkSource, srcImport } from './lib/bench-config.js'; import { isWorker, workerEngine, forkEngines } from './lib/fork-engine.js'; +import { median, round1, timeMedian } from './lib/bench-timing.js'; // ── Parent process: fork one child per engine, assemble final output ───── if (!isWorker()) { @@ -51,12 +52,6 @@ if (!isWorker()) { // jitter and produces CI-amplified false regressions. const RUNS = 5; const WARMUP_RUNS = 2; - function median(arr) { - const sorted = [...arr].sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; - } - function round1(n) { return Math.round(n * 10) / 10; } function collectImportPairs() { const srcRoot = path.join(rootParent, 'src'); @@ -190,23 +185,15 @@ const WARMUP_RUNS = 2; // the same corpus. const BUILD_OPTS = { engine, exclude: [...resolveBenchmarkExcludes()] }; -function median(arr) { - const sorted = [...arr].sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; -} - console.error(`Benchmarking ${engine} engine...`); // Full build (delete DB first) -const fullTimings = []; -for (let i = 0; i < RUNS; i++) { - if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); - const start = performance.now(); - await buildGraph(root, { ...BUILD_OPTS, incremental: false }); - fullTimings.push(performance.now() - start); -} -const fullBuildMs = Math.round(median(fullTimings)); +const fullBuildMs = Math.round( + await timeMedian(async () => { + if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); + await buildGraph(root, { ...BUILD_OPTS, incremental: false }); + }, RUNS), +); // No-op rebuild (nothing changed) let noopRebuildMs = null; @@ -214,13 +201,9 @@ try { for (let i = 0; i < WARMUP_RUNS; i++) { await buildGraph(root, { ...BUILD_OPTS, incremental: true }); } - const noopTimings = []; - for (let i = 0; i < RUNS; i++) { - const start = performance.now(); - await buildGraph(root, { ...BUILD_OPTS, incremental: true }); - noopTimings.push(performance.now() - start); - } - noopRebuildMs = Math.round(median(noopTimings)); + noopRebuildMs = Math.round( + await timeMedian(() => buildGraph(root, { ...BUILD_OPTS, incremental: true }), RUNS), + ); } catch (err) { console.error(` [${engine}] No-op rebuild failed: ${(err as Error).message}`); } diff --git a/scripts/lib/bench-timing.ts b/scripts/lib/bench-timing.ts new file mode 100644 index 000000000..233fb33a7 --- /dev/null +++ b/scripts/lib/bench-timing.ts @@ -0,0 +1,53 @@ +/** + * Shared timing helpers for benchmark scripts. + * + * `median`/`round1` were independently duplicated (byte-for-byte, in most + * cases) across token-benchmark.ts, query-benchmark.ts, + * incremental-benchmark.ts (twice — once per process: parent and worker), + * and benchmark.ts. `timeMedian` wraps the "run N times, time each run, + * return the median" loop that recurred at every call site measuring a + * single scalar latency. + * + * Usage (in a benchmark script): + * + * import { median, round1, timeMedian } from './lib/bench-timing.js'; + * + * const fullBuildMs = Math.round( + * await timeMedian(() => buildGraph(root, { engine, incremental: false }), RUNS), + * ); + */ +import { performance } from 'node:perf_hooks'; + +/** + * Returns the median of `arr`. `arr` is not mutated (sorted on a copy). + * Returns 0 for an empty array. + */ +export function median(arr: number[]): number { + if (arr.length === 0) return 0; + const sorted = [...arr].sort((a, b) => a - b); + const mid = Math.floor(sorted.length / 2); + return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; +} + +/** + * Rounds `n` to 1 decimal place. + */ +export function round1(n: number): number { + return Math.round(n * 10) / 10; +} + +/** + * Runs `fn` `runs` times, recording the elapsed milliseconds per run, and + * returns the median duration. Awaits `fn()` each iteration, so both sync + * and async `fn` work — pass an async closure when `fn` itself needs to + * `await` (e.g. wrapping `buildGraph`). + */ +export async function timeMedian(fn: () => unknown, runs: number): Promise { + const timings: number[] = []; + for (let i = 0; i < runs; i++) { + const start = performance.now(); + await fn(); + timings.push(performance.now() - start); + } + return median(timings); +} diff --git a/scripts/query-benchmark.ts b/scripts/query-benchmark.ts index 0d53d75e4..d19aa8e2f 100644 --- a/scripts/query-benchmark.ts +++ b/scripts/query-benchmark.ts @@ -18,6 +18,7 @@ import { fileURLToPath } from 'node:url'; import Database from 'better-sqlite3'; import { resolveBenchmarkExcludes, resolveBenchmarkSource, srcImport } from './lib/bench-config.js'; import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js'; +import { median, round1 } from './lib/bench-timing.js'; // ── Parent process: fork one child per engine, assemble final output ───── if (!isWorker()) { @@ -117,16 +118,6 @@ const RUNS = 5; // before timing so the metric reflects warm-call latency, not cold-start. const WARMUP_RUNS = 3; -function median(arr) { - const sorted = [...arr].sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; -} - -function round1(n) { - return Math.round(n * 10) / 10; -} - // Pinned hub targets — stable function names that exist across versions. // Auto-selecting the most-connected node makes version-to-version comparison // meaningless when barrel/type files get added or removed. diff --git a/scripts/token-benchmark.ts b/scripts/token-benchmark.ts index d34268ead..7e66cb6b8 100644 --- a/scripts/token-benchmark.ts +++ b/scripts/token-benchmark.ts @@ -27,6 +27,7 @@ import { parseArgs } from 'node:util'; import { ISSUES, extractAgentOutput, validateResult } from './token-benchmark-issues.js'; import { getBenchmarkVersion } from './bench-version.js'; +import { median, round1, timeMedian } from './lib/bench-timing.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, '..'); @@ -76,13 +77,6 @@ const selectedIssues = selectedIssueIds.map((id) => { // ── Helpers ─────────────────────────────────────────────────────────────── -function median(arr) { - if (arr.length === 0) return 0; - const sorted = [...arr].sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; -} - function round2(n) { return Math.round(n * 100) / 100; } @@ -261,24 +255,6 @@ async function runSession(mode, issue, nextjsDir) { const PERF_RUNS = 3; -function round1(n) { - return Math.round(n * 10) / 10; -} - -/** - * Run `fn` `runs` times (default `PERF_RUNS`), recording the elapsed - * milliseconds per run, and return the median duration. - */ -async function timeMedian(fn, runs = PERF_RUNS) { - const timings = []; - for (let i = 0; i < runs; i++) { - const start = performance.now(); - await fn(); - timings.push(performance.now() - start); - } - return median(timings); -} - /** * Run build/query/stats benchmarks against the Next.js graph. * Reuses the same codegraph APIs as the existing benchmark scripts. @@ -324,13 +300,13 @@ async function runPerfBenchmarks(nextjsDir) { await timeMedian(async () => { if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath); await buildGraph(nextjsDir, { engine, incremental: false }); - }), + }, PERF_RUNS), ); // No-op rebuild console.error(` No-op rebuild (${engine})...`); const noopRebuildMs = Math.round( - await timeMedian(() => buildGraph(nextjsDir, { engine, incremental: true })), + await timeMedian(() => buildGraph(nextjsDir, { engine, incremental: true }), PERF_RUNS), ); buildResults[engine] = { fullBuildMs, noopRebuildMs }; @@ -379,12 +355,12 @@ async function runPerfBenchmarks(nextjsDir) { for (const depth of [1, 3, 5]) { // fnDeps queryResults[`fnDeps_depth${depth}Ms`] = round1( - await timeMedian(() => fnDepsData(hubName, dbPath, { depth, noTests: true })), + await timeMedian(() => fnDepsData(hubName, dbPath, { depth, noTests: true }), PERF_RUNS), ); // fnImpact queryResults[`fnImpact_depth${depth}Ms`] = round1( - await timeMedian(() => fnImpactData(hubName, dbPath, { depth, noTests: true })), + await timeMedian(() => fnImpactData(hubName, dbPath, { depth, noTests: true }), PERF_RUNS), ); } diff --git a/src/db/connection.ts b/src/db/connection.ts index eac517849..56f23978d 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { loadConfig } from '../infrastructure/config.js'; +import { DEFAULTS, loadConfig } from '../infrastructure/config.js'; import { debug, warn } from '../infrastructure/logger.js'; import { getNative, isNativeAvailable } from '../infrastructure/native.js'; import { DbError, toErrorMessage } from '../shared/errors.js'; @@ -158,7 +158,10 @@ function isSameDirectory(a: string, b: string): boolean { } } -export function openDb(dbPath: string): LockedDatabase { +export function openDb( + dbPath: string, + busyTimeoutMs: number = DEFAULTS.db.busyTimeoutMs, +): LockedDatabase { // Flush any deferred DB close from a previous build (avoids WAL contention) flushDeferredClose(); const dir = path.dirname(dbPath); @@ -167,7 +170,7 @@ export function openDb(dbPath: string): LockedDatabase { const Database = getDatabase(); const db = new Database(dbPath) as unknown as LockedDatabase; db.pragma('journal_mode = WAL'); - db.pragma('busy_timeout = 5000'); + db.pragma(`busy_timeout = ${busyTimeoutMs}`); db.__lockPath = `${dbPath}.lock`; return db; } @@ -327,7 +330,10 @@ export function findDbPath(customPath?: string): string { } /** Open a database in readonly mode, with a user-friendly error if the DB doesn't exist. */ -export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database { +export function openReadonlyOrFail( + customPath?: string, + busyTimeoutMs: number = DEFAULTS.db.busyTimeoutMs, +): BetterSqlite3Database { const dbPath = findDbPath(customPath); if (!fs.existsSync(dbPath)) { throw new DbError( @@ -337,7 +343,7 @@ export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database { } const Database = getDatabase(); const db = new Database(dbPath, { readonly: true }) as unknown as BetterSqlite3Database; - db.pragma('busy_timeout = 5000'); + db.pragma(`busy_timeout = ${busyTimeoutMs}`); warnOnVersionMismatch(() => { const row = db @@ -349,8 +355,15 @@ export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database { return db; } +/** Effective engine plus config-derived DB settings shared by openRepo() and openReadonlyWithNative(). */ +interface ResolvedDbSettings { + engine: 'native' | 'wasm' | 'auto'; + busyTimeoutMs: number; +} + /** - * Resolve the effective engine for DB access: explicit opts.engine > config.build.engine > 'auto'. + * Resolve the effective engine for DB access (explicit opts.engine > config.build.engine > + * 'auto') alongside config.db.busyTimeoutMs, in a single loadConfig() call. * Derives rootDir from the resolved DB path so loadConfig reads the right project config. * Shared by openRepo() and openReadonlyWithNative() so the two call sites can't drift. * @@ -358,18 +371,22 @@ export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database { * via resolveSecrets on a malformed llm.apiKeyCommand config), and an already-open * handle at that point would never be closed. */ -function resolveDbEngine( +function resolveDbSettings( customDbPath: string | undefined, engineOpt: 'native' | 'wasm' | 'auto' | undefined, -): 'native' | 'wasm' | 'auto' { +): ResolvedDbSettings { // Using findDbPath (not path.resolve(customDbPath)) ensures directory inputs like // --db /path/to/repo are normalised to .codegraph/graph.db before we strip two levels. // Convention: resolvedDbPath = /.codegraph/graph.db const resolvedDbPath = customDbPath ? findDbPath(customDbPath) : undefined; const rootDir = resolvedDbPath ? path.dirname(path.dirname(resolvedDbPath)) : undefined; + const config = loadConfig(rootDir); // config.build.engine is already populated from CODEGRAPH_ENGINE env by applyEnvOverrides, // so this covers both the env-var path and the .codegraphrc.json config-file path. - return engineOpt ?? loadConfig(rootDir).build.engine ?? 'auto'; + return { + engine: engineOpt ?? config.build.engine ?? 'auto', + busyTimeoutMs: config.db.busyTimeoutMs ?? DEFAULTS.db.busyTimeoutMs, + }; } /** Open a NativeRepository via rusqlite, throwing DbError if the DB file is missing. */ @@ -422,7 +439,7 @@ export function openRepo( // Respect explicit engine selection: opts.engine > config.build.engine > auto. // This ensures --engine wasm and benchmark workers bypass the native path. - const engine = resolveDbEngine(customDbPath, opts.engine); + const { engine, busyTimeoutMs } = resolveDbSettings(customDbPath, opts.engine); // Try native rusqlite path first (Phase 6.14) if (engine !== 'wasm' && isNativeAvailable()) { @@ -442,7 +459,7 @@ export function openRepo( } } - const db = openReadonlyOrFail(customDbPath); + const db = openReadonlyOrFail(customDbPath, busyTimeoutMs); return { repo: new SqliteRepository(db), close() { @@ -476,9 +493,9 @@ export function openReadonlyWithNative( // handle has been opened yet, so nothing is left leaked. (Previously this ran // AFTER openReadonlyOrFail(), so a config error here leaked the already-open // better-sqlite3 handle — see the phase-15 gauntlet finding.) - const engine = resolveDbEngine(customPath, opts.engine); + const { engine, busyTimeoutMs } = resolveDbSettings(customPath, opts.engine); - const db = openReadonlyOrFail(customPath); + const db = openReadonlyOrFail(customPath, busyTimeoutMs); let nativeDb: NativeDatabase | undefined; if (engine !== 'wasm' && isNativeAvailable()) { diff --git a/src/domain/graph/builder/call-resolver.ts b/src/domain/graph/builder/call-resolver.ts index 0312dee75..b39a158a7 100644 --- a/src/domain/graph/builder/call-resolver.ts +++ b/src/domain/graph/builder/call-resolver.ts @@ -35,6 +35,31 @@ export const RECEIVER_KINDS = new Set(['class', 'struct', 'interface', 'type', ' // continue to work without changes (build-edges.ts, etc.). export { isModuleScopedLanguage }; +/** + * Shared by both the full-build (build-edges.ts) and incremental (incremental.ts) + * same-class fallback strategies: derive the enclosing class name from the + * caller's qualified name (the segment immediately before the final dot, e.g. + * `Namespace.MyClass.method` → `MyClass`), then look up `ClassName.callName` + * as a method in the same file. + * + * Uses lastIndexOf (not indexOf) so deeply-qualified caller names extract the + * innermost class, not the outermost namespace. + */ +export function resolveSameClassQualifiedMethod( + callName: string, + callerName: string, + relPath: string, + lookup: CallNodeLookup, +): Array<{ id: number; file: string; kind?: string }> { + const lastDot = callerName.lastIndexOf('.'); + if (lastDot <= 0) return []; + const prevDot = callerName.lastIndexOf('.', lastDot - 1); + const className = callerName.slice(prevDot + 1, lastDot); + return lookup + .byNameAndFile(`${className}.${callName}`, relPath) + .filter((n) => n.kind === 'method'); +} + // ── Shared resolution functions ────────────────────────────────────────── /** diff --git a/src/domain/graph/builder/helpers.ts b/src/domain/graph/builder/helpers.ts index c9af890b9..16dda21f5 100644 --- a/src/domain/graph/builder/helpers.ts +++ b/src/domain/graph/builder/helpers.ts @@ -448,6 +448,31 @@ export function batchInsertEdges(db: BetterSqlite3Database, rows: unknown[][]): }); } +const exportStmtCache = new WeakMap>(); + +function getExportStmt(db: BetterSqlite3Database, chunkSize: number): SqliteStatement { + return getOrCreateBatchStmt(exportStmtCache, db, chunkSize, (n) => { + const conditions = Array.from( + { length: n }, + () => '(name = ? AND kind = ? AND file = ? AND line = ?)', + ).join(' OR '); + return `UPDATE nodes SET exported = 1 WHERE ${conditions}`; + }); +} + +/** + * Mark exported symbols as `exported = 1` in batches, keyed by + * `[name, kind, file, line]`. Shared by the JS-fallback definitions/exports + * insert (`insertDefinitionsAndExports`) and the native-orchestrator backfill + * (`insertBackfilledNodes`), which previously duplicated this exact + * chunked-UPDATE loop verbatim. + */ +export function markExportedSymbols(db: BetterSqlite3Database, exportKeys: unknown[][]): void { + runBatchInsert(db, exportKeys, getExportStmt, (k, vals) => { + vals.push(k[0], k[1], k[2], k[3]); + }); +} + /** Confidence assigned to CHA-expanded interface/abstract dispatch edges. */ export const CHA_DISPATCH_CONFIDENCE = 0.8; diff --git a/src/domain/graph/builder/incremental.ts b/src/domain/graph/builder/incremental.ts index 2c60b3909..863ef00f7 100644 --- a/src/domain/graph/builder/incremental.ts +++ b/src/domain/graph/builder/incremental.ts @@ -26,6 +26,7 @@ import { findCaller, resolveCallTargets, resolveReceiverEdge, + resolveSameClassQualifiedMethod, } from './call-resolver.js'; import { BUILTIN_RECEIVERS, readFileSafe } from './helpers.js'; @@ -556,32 +557,6 @@ function buildIncrementalTypeMap(symbols: ExtractorOutput): Map return typeMap; } -/** - * Strategy 1 — same-class `this.method()` fallback. - * Derives the enclosing class name from callerName by extracting the segment - * immediately before the final dot (e.g. `MyClass.method` → `MyClass`, - * `Namespace.MyClass.method` → `MyClass`), then retries with the qualified - * method name `MyClass.callName`. - * - * Uses lastIndexOf to match the full-build counterpart in resolveFallbackTargets - * (build-edges.ts) — indexOf would extract `Namespace` instead of `MyClass` for - * deeply-qualified caller names like `Namespace.MyClass.method`. - */ -function resolveThisSameClassTarget( - callName: string, - callerName: string, - relPath: string, - lookup: CallNodeLookup, -): Array<{ id: number; file: string; kind?: string }> { - const lastDot = callerName.lastIndexOf('.'); - if (lastDot <= 0) return []; - const prevDot = callerName.lastIndexOf('.', lastDot - 1); - const className = callerName.slice(prevDot + 1, lastDot); - return lookup - .byNameAndFile(`${className}.${callName}`, relPath) - .filter((n) => n.kind === 'method'); -} - /** * Strategy 2 — Object.defineProperty accessor fallback. * When a function is registered as a getter/setter via @@ -635,7 +610,7 @@ function applyThisReceiverFallbacks( // Strategy 1: same-class `this.method()` fallback. if (call.receiver === 'this' && callerName != null) { - const s1 = resolveThisSameClassTarget(call.name, callerName, relPath, lookup); + const s1 = resolveSameClassQualifiedMethod(call.name, callerName, relPath, lookup); if (s1.length > 0) return s1; } diff --git a/src/domain/graph/builder/pipeline.ts b/src/domain/graph/builder/pipeline.ts index 490c5fe07..28e4eb0df 100644 --- a/src/domain/graph/builder/pipeline.ts +++ b/src/domain/graph/builder/pipeline.ts @@ -193,7 +193,7 @@ function setupPipeline(ctx: PipelineContext): void { // cost entirely on no-op builds that exit before reaching the orchestrator. const dir = path.dirname(ctx.dbPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - ctx.db = openDb(ctx.dbPath); + ctx.db = openDb(ctx.dbPath, ctx.config.db.busyTimeoutMs); initSchema(ctx.db); // Merge caller-supplied excludes on top of the file-config excludes so // programmatic callers (e.g. benchmark scripts) can extend exclusion @@ -287,7 +287,7 @@ async function runPipelineStages(ctx: PipelineContext): Promise { // now-closed NativeDatabase. Replace it with a real better-sqlite3 // connection so the JS pipeline stages can operate normally. if (ctx.nativeFirstProxy) { - ctx.db = openDb(ctx.dbPath); + ctx.db = openDb(ctx.dbPath, ctx.config.db.busyTimeoutMs); ctx.nativeFirstProxy = false; } } diff --git a/src/domain/graph/builder/stages/build-edges.ts b/src/domain/graph/builder/stages/build-edges.ts index efeee79f6..011ce246b 100644 --- a/src/domain/graph/builder/stages/build-edges.ts +++ b/src/domain/graph/builder/stages/build-edges.ts @@ -36,6 +36,7 @@ import type { import { computeConfidence } from '../../resolve.js'; import type { PointsToMap } from '../../resolver/points-to.js'; import { buildPointsToMap, resolveViaPointsTo } from '../../resolver/points-to.js'; +import { unwrapTypeEntry } from '../../resolver/strategy.js'; import { enrichTypeMapWithTsc } from '../../resolver/ts-resolver.js'; import { type CallNodeLookup, @@ -43,6 +44,7 @@ import { isModuleScopedLanguage, resolveCallTargets, resolveReceiverEdge, + resolveSameClassQualifiedMethod, } from '../call-resolver.js'; import type { ChaContext } from '../cha.js'; import { buildChaContext, resolveChaTargets, resolveThisDispatch } from '../cha.js'; @@ -1086,27 +1088,6 @@ function resolveKotlinReflectionPreQualified( return []; } -/** - * Shared by both same-class fallback strategies below: derive the enclosing - * class name from the caller's qualified name (the segment immediately before - * the final dot, e.g. `Namespace.MyClass.method` → `MyClass`), then look up - * `ClassName.callName` as a method in the same file. - */ -function resolveSameClassQualifiedMethod( - callName: string, - callerName: string, - relPath: string, - lookup: CallNodeLookup, -): Array<{ id: number; file: string; kind?: string }> { - const lastDot = callerName.lastIndexOf('.'); - if (lastDot <= 0) return []; - const prevDot = callerName.lastIndexOf('.', lastDot - 1); - const className = callerName.slice(prevDot + 1, lastDot); - return lookup - .byNameAndFile(`${className}.${callName}`, relPath) - .filter((n) => n.kind === 'method'); -} - /** * Same-class `this.method()` fallback: when the call receiver is `this` and * resolveCallTargets found nothing, derive the enclosing class name from the @@ -1168,12 +1149,7 @@ function resolveReflectionKeyExprFallback( ) { return []; } - const typeEntry = typeMap.get(call.receiver); - const resolvedType = typeEntry - ? typeof typeEntry === 'string' - ? typeEntry - : (typeEntry as { type?: string }).type - : null; + const resolvedType = unwrapTypeEntry(typeMap.get(call.receiver)); if (resolvedType) { const qualified = lookup .byNameAndFile(`${resolvedType}.${call.keyExpr}`, relPath) @@ -1215,12 +1191,7 @@ function resolveDefinePropertyAccessorFallback( const receiverVarName = definePropertyReceivers.get(callerName); if (!receiverVarName) return []; - const typeEntry = typeMap.get(receiverVarName); - const typeName = typeEntry - ? typeof typeEntry === 'string' - ? typeEntry - : (typeEntry as { type?: string }).type - : null; + const typeName = unwrapTypeEntry(typeMap.get(receiverVarName)); if (typeName) { const qualified = lookup.byNameAndFile(`${typeName}.${call.name}`, relPath); if (qualified.length > 0) return [...qualified]; diff --git a/src/domain/graph/builder/stages/insert-nodes.ts b/src/domain/graph/builder/stages/insert-nodes.ts index 09aad25d8..c0459a2c5 100644 --- a/src/domain/graph/builder/stages/insert-nodes.ts +++ b/src/domain/graph/builder/stages/insert-nodes.ts @@ -27,6 +27,7 @@ import { batchInsertNodes, fileHash, fileStat, + markExportedSymbols, readFileSafe, } from '../helpers.js'; @@ -283,31 +284,7 @@ function insertDefinitionsAndExports( } } batchInsertNodes(db, phase1Rows); - - // Mark exported symbols in batches (cache prepared statements by chunk size) - if (exportKeys.length > 0) { - const EXPORT_CHUNK = 500; - const exportStmtCache = new Map(); - for (let i = 0; i < exportKeys.length; i += EXPORT_CHUNK) { - const end = Math.min(i + EXPORT_CHUNK, exportKeys.length); - const chunkSize = end - i; - let updateStmt = exportStmtCache.get(chunkSize); - if (!updateStmt) { - const conditions = Array.from( - { length: chunkSize }, - () => '(name = ? AND kind = ? AND file = ? AND line = ?)', - ).join(' OR '); - updateStmt = db.prepare(`UPDATE nodes SET exported = 1 WHERE ${conditions}`); - exportStmtCache.set(chunkSize, updateStmt); - } - const vals: unknown[] = []; - for (let j = i; j < end; j++) { - const k = exportKeys[j] as unknown[]; - vals.push(k[0], k[1], k[2], k[3]); - } - updateStmt.run(...vals); - } - } + markExportedSymbols(db, exportKeys); } // ── JS fallback: Phase 2+3 ────────────────────────────────────────── diff --git a/src/domain/graph/builder/stages/native-db-lifecycle.ts b/src/domain/graph/builder/stages/native-db-lifecycle.ts index ac9e2568f..7395c0d6d 100644 --- a/src/domain/graph/builder/stages/native-db-lifecycle.ts +++ b/src/domain/graph/builder/stages/native-db-lifecycle.ts @@ -70,5 +70,5 @@ export function refreshJsDb(ctx: PipelineContext): void { } catch (e) { debug(`refreshJsDb close failed: ${toErrorMessage(e)}`); } - ctx.db = openDb(ctx.dbPath); + ctx.db = openDb(ctx.dbPath, ctx.config.db.busyTimeoutMs); } diff --git a/src/domain/graph/builder/stages/native-orchestrator.ts b/src/domain/graph/builder/stages/native-orchestrator.ts index c0157cf95..b5d0c0467 100644 --- a/src/domain/graph/builder/stages/native-orchestrator.ts +++ b/src/domain/graph/builder/stages/native-orchestrator.ts @@ -34,7 +34,6 @@ import type { DataflowResult, Definition, ExtractorOutput, - SqliteStatement, } from '../../../../types.js'; import { classifyNativeDrops, @@ -57,6 +56,7 @@ import { collectFiles as collectFilesUtil, fileHash, fileStat, + markExportedSymbols, readFileSafe, } from '../helpers.js'; import { NativeDbProxy } from '../native-db-proxy.js'; @@ -136,7 +136,7 @@ function handoffWalAfterNativeBuild(ctx: PipelineContext): boolean { debug(`handoffWal JS db close failed: ${toErrorMessage(e)}`); } try { - ctx.db = openDb(ctx.dbPath); + ctx.db = openDb(ctx.dbPath, ctx.config.db.busyTimeoutMs); return true; } catch (reopenErr) { warn(`Failed to reopen DB after native build: ${(reopenErr as Error).message}`); @@ -1691,31 +1691,7 @@ function insertBackfilledNodes( } } batchInsertNodes(db, rows); - - // Mark exported symbols in batches — mirrors insertDefinitionsAndExports. - if (exportKeys.length > 0) { - const EXPORT_CHUNK = 500; - const exportStmtCache = new Map(); - for (let i = 0; i < exportKeys.length; i += EXPORT_CHUNK) { - const end = Math.min(i + EXPORT_CHUNK, exportKeys.length); - const chunkSize = end - i; - let updateStmt = exportStmtCache.get(chunkSize); - if (!updateStmt) { - const conditions = Array.from( - { length: chunkSize }, - () => '(name = ? AND kind = ? AND file = ? AND line = ?)', - ).join(' OR '); - updateStmt = db.prepare(`UPDATE nodes SET exported = 1 WHERE ${conditions}`); - exportStmtCache.set(chunkSize, updateStmt); - } - const vals: unknown[] = []; - for (let j = i; j < end; j++) { - const k = exportKeys[j] as unknown[]; - vals.push(k[0], k[1], k[2], k[3]); - } - updateStmt.run(...vals); - } - } + markExportedSymbols(db, exportKeys); } /** @@ -1789,7 +1765,7 @@ async function backfillNativeDroppedFiles( // for the INSERT path below). if (ctx.nativeFirstProxy) { closeNativeDb(ctx, 'pre-parity-backfill'); - ctx.db = openDb(ctx.dbPath); + ctx.db = openDb(ctx.dbPath, ctx.config.db.busyTimeoutMs); ctx.nativeFirstProxy = false; } @@ -1951,7 +1927,7 @@ function openNativeDatabase(ctx: PipelineContext): void { ctx.nativeFirstProxy = false; // defensive: reset in case future refactors move the assignment above throwing lines releaseAdvisoryLock(`${ctx.dbPath}.lock`); // Reopen better-sqlite3 for JS pipeline fallback - ctx.db = openDb(ctx.dbPath); + ctx.db = openDb(ctx.dbPath, ctx.config.db.busyTimeoutMs); } } @@ -2239,7 +2215,7 @@ class NativeOrchestrationSession { if (!needsStructure && !needsAnalysisFallback) return true; if (needsAnalysisFallback && this.ctx.nativeFirstProxy) { closeNativeDb(this.ctx, 'pre-analysis-fallback'); - this.ctx.db = openDb(this.ctx.dbPath); + this.ctx.db = openDb(this.ctx.dbPath, this.ctx.config.db.busyTimeoutMs); this.ctx.nativeFirstProxy = false; return true; } diff --git a/src/domain/graph/resolver/strategy.ts b/src/domain/graph/resolver/strategy.ts index 8bfe99cfb..02859cc4d 100644 --- a/src/domain/graph/resolver/strategy.ts +++ b/src/domain/graph/resolver/strategy.ts @@ -66,8 +66,11 @@ export function isModuleScopedLanguage(relPath: string): boolean { * shape `{ type?: string }` (some seeders attach extra metadata alongside the * target). This normalises both shapes to `string | null`, matching the * falsy-check semantics every call site previously duplicated inline. + * + * Exported: also used by build-edges.ts's reflection/defineProperty fallback + * helpers, which duplicated this exact ternary inline before being wired here. */ -function unwrapTypeEntry(entry: unknown): string | null { +export function unwrapTypeEntry(entry: unknown): string | null { if (!entry) return null; return typeof entry === 'string' ? entry : ((entry as { type?: string }).type ?? null); } diff --git a/src/domain/graph/watcher.ts b/src/domain/graph/watcher.ts index d922307e6..703a45247 100644 --- a/src/domain/graph/watcher.ts +++ b/src/domain/graph/watcher.ts @@ -186,7 +186,7 @@ function setupWatcher(rootDir: string, opts: { engine?: string; dbPath?: string const extraDirs = [...(config.ignoreDirs ?? []), ...(config.ignoreAdditionalDirs ?? [])]; const ignoreSet = buildIgnoreSet(extraDirs.length ? extraDirs : undefined); - const db = openDb(dbPath); + const db = openDb(dbPath, config.db.busyTimeoutMs); initSchema(db); const engineOpts: import('../../types.js').EngineOpts = { engine: (opts.engine || 'auto') as import('../../types.js').EngineMode, diff --git a/src/extractors/javascript.ts b/src/extractors/javascript.ts index 8c9931d22..4d189fa4c 100644 --- a/src/extractors/javascript.ts +++ b/src/extractors/javascript.ts @@ -178,18 +178,9 @@ function handleClassCapture( /** Handle method_definition capture. */ function handleMethodCapture(c: Record, definitions: Definition[]): void { const methNameNode = c.meth_name!; - let methName: string; - if (methNameNode.type === 'computed_property_name') { - // Extract the inner string literal from `['methodName']` or `["methodName"]`. - // Non-string computed keys (e.g. `[Symbol.iterator]`) cannot be resolved at - // dot-notation call sites, so skip them entirely. - const inner = methNameNode.child(1); // child(0)='[', child(1)=string, child(2)=']' - if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) return; - methName = inner.text.replace(/^['"]|['"]$/g, ''); - if (!methName) return; - } else { - methName = methNameNode.text; - } + // Non-string computed keys (e.g. `[Symbol.iterator]`) resolve to '' and are skipped. + const methName = resolveMethodDefinitionName(methNameNode); + if (!methName) return; const parentClass = findParentClass(c.meth_node!); const fullName = parentClass ? `${parentClass}.${methName}` : methName; const methChildren = extractParameters(c.meth_node!); @@ -960,18 +951,9 @@ function handleClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void { function handleMethodDef(node: TreeSitterNode, ctx: ExtractorOutput): void { const nameNode = node.childForFieldName('name'); if (nameNode) { - let methName: string; - if (nameNode.type === 'computed_property_name') { - // Extract the inner string literal from `['methodName']` or `["methodName"]`. - // Non-string computed keys (e.g. `[Symbol.iterator]`) cannot be resolved at - // dot-notation call sites, so skip them entirely. - const inner = nameNode.child(1); // child(0)='[', child(1)=string, child(2)=']' - if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) return; - methName = inner.text.replace(/^['"]|['"]$/g, ''); - if (!methName) return; - } else { - methName = nameNode.text; - } + // Non-string computed keys (e.g. `[Symbol.iterator]`) resolve to '' and are skipped. + const methName = resolveMethodDefinitionName(nameNode); + if (!methName) return; const parentClass = findParentClass(node); const fullName = parentClass ? `${parentClass}.${methName}` : methName; const methChildren = extractParameters(node); @@ -1287,17 +1269,9 @@ function extractObjectLiteralFunctions( } else if (child.type === 'method_definition') { const nameNode = child.childForFieldName('name'); if (nameNode) { - let methodName: string; - if (nameNode.type === 'computed_property_name') { - // Strip brackets+quotes from `['methodName']` to get a resolvable name. - // Skip non-string computed keys (e.g. [Symbol.iterator]). - const inner = nameNode.child(1); - if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) continue; - methodName = inner.text.replace(/^['"]|['"]$/g, ''); - if (!methodName) continue; - } else { - methodName = nameNode.text; - } + // Non-string computed keys (e.g. `[Symbol.iterator]`) resolve to '' and are skipped. + const methodName = resolveMethodDefinitionName(nameNode); + if (!methodName) continue; definitions.push({ name: `${varName}.${methodName}`, kind: 'function', diff --git a/src/features/communities.ts b/src/features/communities.ts index 0244c976c..baf5c10d1 100644 --- a/src/features/communities.ts +++ b/src/features/communities.ts @@ -156,6 +156,7 @@ type CommunitiesDataOpts = { maxLevels?: number; maxLocalPasses?: number; refinementTheta?: number; + capacityGrowthFactor?: number; limit?: number; offset?: number; repo?: Repository; @@ -197,11 +198,13 @@ export function communitiesData( const maxLevels = opts.maxLevels ?? config.community?.maxLevels; const maxLocalPasses = opts.maxLocalPasses ?? config.community?.maxLocalPasses; const refinementTheta = opts.refinementTheta ?? config.community?.refinementTheta; + const capacityGrowthFactor = opts.capacityGrowthFactor ?? config.community?.capacityGrowthFactor; const { assignments, modularity } = louvainCommunities(graph, { resolution, maxLevels, maxLocalPasses, refinementTheta, + capacityGrowthFactor, }); const { communities, communityDirs } = buildCommunityObjects(graph, assignments, opts); diff --git a/src/graph/algorithms/leiden/cpm.ts b/src/graph/algorithms/leiden/cpm.ts index 957a605fe..ee0e3bb7d 100644 --- a/src/graph/algorithms/leiden/cpm.ts +++ b/src/graph/algorithms/leiden/cpm.ts @@ -3,6 +3,8 @@ * Vendored from ngraph.leiden (MIT) — no external dependencies. */ +import { fget, iget } from './typed-array-helpers.js'; + /** * Minimal view of a partition needed by CPM quality functions. */ @@ -26,14 +28,6 @@ export interface GraphView { size: Float64Array; } -// Typed array safe-access helper (see adapter.ts for rationale) -function fget(a: Float64Array, i: number): number { - return a[i] as number; -} -function iget(a: Int32Array, i: number): number { - return a[i] as number; -} - export function diffCPM( part: PartitionView, g: GraphView, diff --git a/src/graph/algorithms/leiden/modularity.ts b/src/graph/algorithms/leiden/modularity.ts index 98a9a038b..d419d9fc6 100644 --- a/src/graph/algorithms/leiden/modularity.ts +++ b/src/graph/algorithms/leiden/modularity.ts @@ -3,6 +3,8 @@ * Vendored from ngraph.leiden (MIT) — no external dependencies. */ +import { fget, iget } from './typed-array-helpers.js'; + /** * Minimal view of a partition needed by modularity quality functions. */ @@ -30,14 +32,6 @@ export interface GraphView { selfLoop: Float64Array; } -// Typed array safe-access helper (see adapter.ts for rationale) -function fget(a: Float64Array, i: number): number { - return a[i] as number; -} -function iget(a: Int32Array, i: number): number { - return a[i] as number; -} - export function diffModularity( part: PartitionView, g: GraphView, diff --git a/src/graph/algorithms/leiden/optimiser.ts b/src/graph/algorithms/leiden/optimiser.ts index 5d6753c70..ed00d5442 100644 --- a/src/graph/algorithms/leiden/optimiser.ts +++ b/src/graph/algorithms/leiden/optimiser.ts @@ -9,7 +9,7 @@ import { makeGraphAdapter } from './adapter.js'; import { diffCPM } from './cpm.js'; import { diffModularity } from './modularity.js'; import type { Partition } from './partition.js'; -import { makePartition } from './partition.js'; +import { DEFAULT_CAPACITY_GROWTH_FACTOR, makePartition } from './partition.js'; import { createRng } from './rng.js'; // Mirrored in DEFAULTS.community (src/infrastructure/config.js) for user override @@ -51,6 +51,7 @@ export interface LeidenOptions { linkWeight?: GraphAdapterOptions['linkWeight']; nodeSize?: GraphAdapterOptions['nodeSize']; baseNodeIds?: string[]; + capacityGrowthFactor?: number; } export interface NormalizedOptions { @@ -67,6 +68,7 @@ export interface NormalizedOptions { maxCommunitySize: number; refinementTheta: number; fixedNodes: Set | string[] | undefined; + capacityGrowthFactor: number; } export interface LevelEntry { @@ -168,7 +170,9 @@ function runLevel( random: () => number, fixedNodeMask: Uint8Array | null, ): LevelOutcome { - const partition: Partition = makePartition(graphAdapter); + const partition: Partition = makePartition(graphAdapter, { + capacityGrowthFactor: options.capacityGrowthFactor, + }); partition.graph = graphAdapter; partition.initializeAggregates(); @@ -499,7 +503,7 @@ function refineWithinCoarseCommunities( opts: NormalizedOptions, fixedMask0: Uint8Array | null, ): Partition { - const p: Partition = makePartition(g); + const p: Partition = makePartition(g, { capacityGrowthFactor: opts.capacityGrowthFactor }); p.initializeAggregates(); p.graph = g; const macro: Int32Array = basePart.nodeCommunity; @@ -675,6 +679,10 @@ function normalizeOptions(options: LeidenOptions = {}): NormalizedOptions { : Infinity; const refinementTheta: number = typeof options.refinementTheta === 'number' ? options.refinementTheta : 1.0; + const capacityGrowthFactor: number = + typeof options.capacityGrowthFactor === 'number' + ? options.capacityGrowthFactor + : DEFAULT_CAPACITY_GROWTH_FACTOR; return { directed, randomSeed, @@ -689,6 +697,7 @@ function normalizeOptions(options: LeidenOptions = {}): NormalizedOptions { maxCommunitySize, refinementTheta, fixedNodes: options.fixedNodes, + capacityGrowthFactor, }; } diff --git a/src/graph/algorithms/leiden/partition.ts b/src/graph/algorithms/leiden/partition.ts index 8e76f8c50..ad2464f5c 100644 --- a/src/graph/algorithms/leiden/partition.ts +++ b/src/graph/algorithms/leiden/partition.ts @@ -68,8 +68,17 @@ interface PartitionState { outEdgeWeightToCommunity: Float64Array; inEdgeWeightFromCommunity: Float64Array; isCandidateCommunity: Uint8Array; + /** Growth multiplier applied by ensureCommCapacity when resizing typed arrays. */ + capacityGrowthFactor: number; } +/** + * Mirrored in DEFAULTS.community.capacityGrowthFactor (src/infrastructure/config.ts). + * Exported so other leiden modules (e.g. optimiser.ts) share this single fallback + * instead of keeping an independently-drifting copy. + */ +export const DEFAULT_CAPACITY_GROWTH_FACTOR = 1.5; + /* ------------------------------------------------------------------ */ /* Community-ID sort helper (used by compact) */ /* ------------------------------------------------------------------ */ @@ -128,7 +137,10 @@ function buildSortedCommunityIds( function ensureCommCapacity(s: PartitionState, newCount: number): void { if (newCount <= s.communityTotalSize.length) return; - const growTo: number = Math.max(newCount, Math.ceil(s.communityTotalSize.length * 1.5)); + const growTo: number = Math.max( + newCount, + Math.ceil(s.communityTotalSize.length * s.capacityGrowthFactor), + ); s.communityTotalSize = growFloat(s.communityTotalSize, growTo); s.communityNodeCount = growInt(s.communityNodeCount, growTo); s.communityInternalEdgeWeight = growFloat(s.communityInternalEdgeWeight, growTo); @@ -485,7 +497,11 @@ function compactIds(s: PartitionState, opts: CompactOptions = {}): void { /* Factory: thin wrapper that wires state to extracted functions */ /* ------------------------------------------------------------------ */ -export function makePartition(graph: GraphAdapter): Partition { +export interface MakePartitionOptions { + capacityGrowthFactor?: number; +} + +export function makePartition(graph: GraphAdapter, options: MakePartitionOptions = {}): Partition { const n: number = graph.n; const nodeCommunity = new Int32Array(n); for (let i = 0; i < n; i++) nodeCommunity[i] = i; @@ -507,6 +523,10 @@ export function makePartition(graph: GraphAdapter): Partition { outEdgeWeightToCommunity: new Float64Array(n), inEdgeWeightFromCommunity: new Float64Array(n), isCandidateCommunity: new Uint8Array(n), + capacityGrowthFactor: + typeof options.capacityGrowthFactor === 'number' + ? options.capacityGrowthFactor + : DEFAULT_CAPACITY_GROWTH_FACTOR, }; return { diff --git a/src/graph/algorithms/louvain.ts b/src/graph/algorithms/louvain.ts index 8adfdb560..60ef439ff 100644 --- a/src/graph/algorithms/louvain.ts +++ b/src/graph/algorithms/louvain.ts @@ -20,6 +20,7 @@ export interface LouvainOptions { maxLevels?: number; maxLocalPasses?: number; refinementTheta?: number; + capacityGrowthFactor?: number; } export interface LouvainResult { @@ -36,9 +37,14 @@ export function louvainCommunities(graph: CodeGraph, opts: LouvainOptions = {}): const native = loadNative(); if (native?.louvainCommunities) { - if (opts.maxLevels != null || opts.maxLocalPasses != null || opts.refinementTheta != null) { + if ( + opts.maxLevels != null || + opts.maxLocalPasses != null || + opts.refinementTheta != null || + opts.capacityGrowthFactor != null + ) { debug( - 'louvainCommunities: maxLevels/maxLocalPasses/refinementTheta are ignored by the native Rust path', + 'louvainCommunities: maxLevels/maxLocalPasses/refinementTheta/capacityGrowthFactor are ignored by the native Rust path', ); } const edges = graph.toEdgeArray(); @@ -63,6 +69,7 @@ function louvainJS(graph: CodeGraph, opts: LouvainOptions, resolution: number): ...(opts.maxLevels != null && { maxLevels: opts.maxLevels }), ...(opts.maxLocalPasses != null && { maxLocalPasses: opts.maxLocalPasses }), ...(opts.refinementTheta != null && { refinementTheta: opts.refinementTheta }), + ...(opts.capacityGrowthFactor != null && { capacityGrowthFactor: opts.capacityGrowthFactor }), }); const assignments = new Map(); diff --git a/src/infrastructure/config.ts b/src/infrastructure/config.ts index 6181d450e..ceba048f3 100644 --- a/src/infrastructure/config.ts +++ b/src/infrastructure/config.ts @@ -63,9 +63,12 @@ export const DEFAULTS = deepFreeze({ db: { /** * SQLite `busy_timeout` pragma (ms) applied to every opened connection. - * @reserved — currently not wired; `src/db/connection.ts` still sets the - * hardcoded literal `5000` directly via `db.pragma('busy_timeout = 5000')` - * in both `openDb` and `openReadonlyOrFail`. + * Wired as the default `busyTimeoutMs` parameter of `openDb()` and + * `openReadonlyOrFail()` in `src/db/connection.ts`. Build/watch call sites + * that already hold a resolved config (pipeline, native orchestrator, + * watcher, `openRepo`/`openReadonlyWithNative`) pass the user-configured + * value through explicitly; the remaining ad-hoc read-only query call + * sites fall back to this default. */ busyTimeoutMs: 5000, }, @@ -160,9 +163,10 @@ export const DEFAULTS = deepFreeze({ /** * Growth multiplier applied when a Leiden partition's per-community * typed arrays need to be resized to fit a larger community count. - * @reserved — currently not wired; `ensureCommCapacity()` in - * `src/graph/algorithms/leiden/partition.ts` still uses the hardcoded - * literal `1.5` directly. + * Threaded through `communitiesData()` -> `louvainCommunities()` -> + * `detectClusters()` -> `makePartition()` to `ensureCommCapacity()` in + * `src/graph/algorithms/leiden/partition.ts`. Ignored by the native Rust + * Louvain path (classic Louvain doesn't use this Leiden-specific resize). */ capacityGrowthFactor: 1.5, }, diff --git a/src/presentation/audit.ts b/src/presentation/audit.ts index a2d14fa15..9a350a6e7 100644 --- a/src/presentation/audit.ts +++ b/src/presentation/audit.ts @@ -108,7 +108,7 @@ export function audit( ): void { const data: AuditResult = auditData(target, customDbPath, opts); - if (outputResult(data, null, opts)) return; + if (outputResult(data as unknown as Record, null, opts)) return; if (data.functions.length === 0) { console.log(`No ${data.kind === 'file' ? 'file' : 'function/symbol'} matching "${target}"`); diff --git a/src/types.ts b/src/types.ts index 66b894f36..980713528 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1346,8 +1346,7 @@ export interface CodegraphConfig { db: { /** * SQLite `busy_timeout` pragma (ms) applied to every opened connection. - * @reserved — currently not wired; see `busyTimeoutMs` in - * `src/infrastructure/config.ts` for wiring status. + * See `busyTimeoutMs` in `src/infrastructure/config.ts` for wiring status. */ busyTimeoutMs: number; }; @@ -1472,8 +1471,7 @@ export interface CodegraphConfig { /** * Growth multiplier applied when a Leiden partition's per-community * typed arrays need to be resized to fit a larger community count. - * @reserved — currently not wired; see `capacityGrowthFactor` in - * `src/infrastructure/config.ts` for wiring status. + * See `capacityGrowthFactor` in `src/infrastructure/config.ts` for wiring status. */ capacityGrowthFactor: number; };