Skip to content

fix(scripts): resolve benchmark hub targets to a single, kind-filtered node#2039

Open
carlos-alm wants to merge 1 commit into
fix/issue-1901-engine-parity-native-js-extractor-emits-nofrom
fix/issue-1904-benchmark-hub-selection-queries-pick-non
Open

fix(scripts): resolve benchmark hub targets to a single, kind-filtered node#2039
carlos-alm wants to merge 1 commit into
fix/issue-1901-engine-parity-native-js-extractor-emits-nofrom
fix/issue-1904-benchmark-hub-selection-queries-pick-non

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

query-benchmark.ts and benchmark.ts each picked "hub"/"mid"/"leaf" call-graph targets by name via a raw SQL query with no kind filter and no deterministic tie-break:

  • The pinned-candidate query (PINNED_HUB_CANDIDATES = ['buildGraph', 'openDb', 'loadConfig']) could match a local const { buildGraph } = await import(...) binding (kind = 'constant') just as easily as the real function buildGraph definition.
  • benchDiffImpact(hubName) then ran a second, independently unfiltered SELECT file FROM nodes WHERE name = ? LIMIT 1 query with no test/spec/kind filter at all, so it could resolve to a different physical node/file than whatever selectTargets() had already picked in the same run — with no ORDER BY, which node won was unspecified SQLite row order.
  • scripts/benchmark.ts had its own independently-duplicated selectTargets() with the same unfiltered-kind query, feeding the same ambiguity into its fnDepsData/pathData benchmark calls.

Verified end-to-end against this repo's own graph (WASM engine, this branch's extractor code): buildGraph currently resolves to 8 same-named nodes — 6 constant-kind local bindings across scripts/*.ts, 1 parameter-kind, and the 1 real function definition at src/domain/graph/builder/pipeline.ts.

Fix

Extracted the selection logic into a single shared scripts/lib/hub-selection.ts:

  • HUB_CANDIDATE_KINDS = ['function', 'method'] — mirrors CALLABLE_SYMBOL_KINDS in src/shared/kinds.ts (Same-file bare-name lookup (resolveCallTargets) is unfiltered by kind and runs before type-aware dispatch, causing false calls-edges to unrelated classes/variables #1888), the same "same-name lookup with no other signal" hazard.
  • Both the pinned-candidate query and the most-connected fallback/ranking query now filter to these kinds and add an explicit ORDER BY id ASC tie-break, so selection is reproducible regardless of physical row insertion order.
  • selectHubTargets now also returns the resolved hub's file (hubFile) as part of the same query that resolved its name, so benchDiffImpact reuses that exact node instead of re-querying nodes by name a second time — eliminating the disagreement risk entirely.
  • query-benchmark.ts and benchmark.ts both now call the shared helper instead of maintaining their own near-duplicate selectTargets() implementations.

Test plan

  • Added tests/unit/hub-selection.test.ts — fixture DB with a same-named constant binding (higher raw edge count) alongside the real function/method definitions; verifies pinned-candidate selection, most-connected fallback, mid/leaf ranking, determinism across repeated calls, and the empty-graph error path
  • npx vitest run tests/unit/hub-selection.test.ts — 5 passed
  • npm run lint — clean (no new warnings/errors)
  • npm run build — clean
  • End-to-end smoke test: built this repo's own graph via the WASM engine (this branch's extractor source) and ran scripts/query-benchmark.ts directly — hub now resolves deterministically to src/domain/graph/builder/pipeline.ts (the real definition) across repeated runs, confirmed via direct selectHubTargets() calls against the built DB
  • npm test — pre-existing failures in this worktree are all native-engine tests failing on Cannot find module '@optave/codegraph-darwin-arm64' (no native addon installed in this fresh worktree); none touch the files changed here

Stacked on #1901 (base branch fix/issue-1901-engine-parity-native-js-extractor-emits-no) — only the scripts//tests/unit/ diff is this issue's change.

Fixes #1904

…d node

query-benchmark.ts and benchmark.ts each selected "hub"/"mid"/"leaf" call-graph
targets by name via a raw SQL query with no `kind` filter and no deterministic
tie-break. A hub-candidate name like `buildGraph` could match a local
`const { buildGraph } = await import(...)` binding (kind=constant) as easily
as the real function definition, and which node won depended on unspecified
SQLite row order. benchDiffImpact then ran a second, independently unfiltered
query to resolve the hub's file, which could disagree with the first
resolution and mutate/diff-impact the wrong file.

Extract the shared selection logic into scripts/lib/hub-selection.ts:
filter candidates to function/method kinds, add an explicit id-based
ORDER BY tie-break, and return the resolved hub's file alongside its name
so benchDiffImpact reuses that exact node instead of re-querying.

Fixes #1904
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes non-deterministic hub/mid/leaf selection in the benchmark scripts by extracting a shared scripts/lib/hub-selection.ts that kind-filters candidates to ['function', 'method'] and adds an explicit ORDER BY id ASC tie-break, preventing a constant-kind local binding from beating the real function definition purely due to SQLite row order.

  • Kind filter + deterministic tie-break: Both the pinned-candidate and most-connected-fallback queries now filter to callable kinds (function/method) and use ORDER BY id ASC, so buildGraph resolves to its actual definition rather than a destructured import binding.
  • Eliminated double-query disagreement: benchDiffImpact previously re-queried nodes by name with no kind filter; it now reuses hubFile already resolved by selectHubTargets, so both lookups agree on the same physical node.
  • Test coverage: A fixture DB seeded with a same-named constant binding (higher raw edge count) alongside real function/method nodes verifies that the kind filter holds in all selection paths.

Confidence Score: 4/5

Safe to merge — the core non-determinism bug is correctly fixed in both scripts and is well-tested.

The kind-filter and ORDER BY tie-break correctly address the root cause, and the double-query disagreement in benchDiffImpact is cleanly resolved by reusing hubFile. The remaining gap is that benchmark.ts still auto-selects the most-connected node without pinned candidates, so its hub identity can shift across graph versions — the same pre-existing drift, just no longer caused by wrong kind matching. The mid field is also computed and tested but never actually fed into a benchmark query.

scripts/benchmark.ts — hub target can still drift across versions without pinned candidates; scripts/lib/hub-selection.ts — mid field is exported and tested but not consumed by any benchmark query.

Important Files Changed

Filename Overview
scripts/lib/hub-selection.ts New shared helper that correctly kind-filters hub/mid/leaf selection to function/method nodes and adds ORDER BY id ASC for deterministic tie-breaking; mid is computed but unused by callers.
scripts/query-benchmark.ts Replaces duplicate selectTargets() with selectHubTargets() (passing PINNED_HUB_CANDIDATES), and passes the already-resolved hubFile to benchDiffImpact eliminating the double-query disagreement risk.
scripts/benchmark.ts Removes the old unfiltered selectTargets() and calls selectHubTargets() with no pinned candidates, fixing the kind-filter bug but leaving hub identity free to drift across graph versions.
tests/unit/hub-selection.test.ts Adds a fixture-DB unit test covering pinned-candidate preference, kind-filter exclusion of constant bindings, mid/leaf ordering, determinism, and the empty-graph error path — good regression coverage for #1904.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant P as Parent process
    participant W1 as Worker 1 (wasm)
    participant W2 as Worker 2 (native)
    participant DB as graph.db
    participant HS as hub-selection.ts

    P->>W1: fork (no TARGETS env)
    W1->>DB: buildGraph()
    W1->>HS: selectHubTargets(dbPath, PINNED_HUB_CANDIDATES)
    HS->>DB: SELECT name,file WHERE kind IN ('function','method') ORDER BY id ASC [pinned]
    HS->>DB: SELECT name,file,cnt WHERE kind IN ('function','method') ORDER BY cnt DESC,id ASC [ranked]
    HS-->>W1: "{hub, hubFile, mid, leaf}"
    W1->>DB: benchDiffImpact(targets.hubFile) — no second query
    W1-->>P: "JSON {targets, ...}"
    P->>W2: "fork (TARGETS=JSON.stringify(firstResult.targets))"
    W2->>HS: workerTargets() → reuse wasm targets
    W2->>DB: benchmark queries using targets.hub / targets.leaf
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant P as Parent process
    participant W1 as Worker 1 (wasm)
    participant W2 as Worker 2 (native)
    participant DB as graph.db
    participant HS as hub-selection.ts

    P->>W1: fork (no TARGETS env)
    W1->>DB: buildGraph()
    W1->>HS: selectHubTargets(dbPath, PINNED_HUB_CANDIDATES)
    HS->>DB: SELECT name,file WHERE kind IN ('function','method') ORDER BY id ASC [pinned]
    HS->>DB: SELECT name,file,cnt WHERE kind IN ('function','method') ORDER BY cnt DESC,id ASC [ranked]
    HS-->>W1: "{hub, hubFile, mid, leaf}"
    W1->>DB: benchDiffImpact(targets.hubFile) — no second query
    W1-->>P: "JSON {targets, ...}"
    P->>W2: "fork (TARGETS=JSON.stringify(firstResult.targets))"
    W2->>HS: workerTargets() → reuse wasm targets
    W2->>DB: benchmark queries using targets.hub / targets.leaf
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(scripts): resolve benchmark hub targ..." | Re-trigger Greptile

Comment thread scripts/benchmark.ts
// ── Query benchmarks ────────────────────────────────────────────────
console.error(` [${engine}] Benchmarking queries...`);
const targets = workerTargets() || selectTargets();
const targets = workerTargets() || selectHubTargets(dbPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Benchmark hub selection still drifts without pinned candidates

selectHubTargets(dbPath) is called with no second argument, so benchmark.ts always falls back to the most-connected qualifying node. The kind-filter bug is fixed, but the hub identity can silently shift between versions whenever the most-connected function changes (e.g. a new heavily-called utility is added). query-benchmark.ts avoids this with PINNED_HUB_CANDIDATES, but that list was never ported to benchmark.ts. The output JSON records targets, so the drift is visible post-hoc — but back-to-back benchmark runs for version-to-version comparisons may measure different nodes and produce misleading deltas.

Fix in Claude Code

Comment on lines +109 to +110
const mid = rows[Math.floor(rows.length / 2)].name;
const leaf = rows[rows.length - 1].name;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 mid is computed and exported but consumed nowhere

mid is included in HubTargets and tested, but neither query-benchmark.ts nor benchmark.ts ever passes targets.mid to a benchDepths/benchQuery call. It flows into the output JSON blob (as part of targets) but drives no actual measurement. If the intent is to benchmark medium-connectivity nodes, the corresponding benchDepths(fnDepsData, targets.mid, ...) calls are missing; if it's purely informational, the HubTargets interface and tests create the impression of an active selection that does not actually feed any benchmark.

Fix in Claude Code

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

9 functions changed3 callers affected across 2 files

  • HubTargets.hub in scripts/lib/hub-selection.ts:34 (0 transitive callers)
  • HubTargets.hubFile in scripts/lib/hub-selection.ts:35 (0 transitive callers)
  • HubTargets.mid in scripts/lib/hub-selection.ts:36 (0 transitive callers)
  • HubTargets.leaf in scripts/lib/hub-selection.ts:37 (0 transitive callers)
  • NodeRow.name in scripts/lib/hub-selection.ts:41 (0 transitive callers)
  • NodeRow.file in scripts/lib/hub-selection.ts:42 (0 transitive callers)
  • RankedNodeRow.cnt in scripts/lib/hub-selection.ts:46 (0 transitive callers)
  • selectHubTargets in scripts/lib/hub-selection.ts:63 (3 transitive callers)
  • benchDiffImpact in scripts/query-benchmark.ts:154 (1 transitive callers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant