Skip to content

Commit d299cf9

Browse files
committed
fix: port PINNED_HUB_CANDIDATES to benchmark.ts for stable hub identity (#2039)
benchmark.ts called selectHubTargets(dbPath) with no pinned-candidate list, so it always fell back to the most-connected qualifying node. query-benchmark.ts already solved this drift with PINNED_HUB_CANDIDATES, but the list was never ported over, so benchmark.ts's hub identity could still silently shift between versions whenever the most-connected function changed. Move PINNED_HUB_CANDIDATES into scripts/lib/hub-selection.ts as a shared export (single source of truth instead of a second copy that could drift), and have both benchmark.ts and query-benchmark.ts import and pass it to selectHubTargets().
1 parent c43ab69 commit d299cf9

4 files changed

Lines changed: 25 additions & 9 deletions

File tree

scripts/benchmark.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { fileURLToPath } from 'node:url';
1717
import { resolveBenchmarkExcludes, resolveBenchmarkSource, srcImport } from './lib/bench-config.js';
1818
import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js';
1919
import { round1, timeMedian, timeMedianWithValue } from './lib/bench-timing.js';
20-
import { selectHubTargets } from './lib/hub-selection.js';
20+
import { PINNED_HUB_CANDIDATES, selectHubTargets } from './lib/hub-selection.js';
2121

2222
// ── Parent process: fork one child per engine, assemble final output ─────
2323
if (!isWorker()) {
@@ -173,7 +173,7 @@ try {
173173

174174
// ── Query benchmarks ────────────────────────────────────────────────
175175
console.error(` [${engine}] Benchmarking queries...`);
176-
const targets = workerTargets() || selectHubTargets(dbPath);
176+
const targets = workerTargets() || selectHubTargets(dbPath, PINNED_HUB_CANDIDATES);
177177
console.error(` hub=${targets.hub}, leaf=${targets.leaf}`);
178178

179179
async function benchQuery(fn, ...args) {

scripts/lib/hub-selection.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ import Database from 'better-sqlite3';
3030
// a candidate's name — mirrors CALLABLE_SYMBOL_KINDS in src/shared/kinds.ts.
3131
export const HUB_CANDIDATE_KINDS: readonly string[] = ['function', 'method'];
3232

33+
// Pinned hub targets — stable function names expected to exist across
34+
// versions. Auto-selecting the most-connected node makes version-to-version
35+
// comparison meaningless whenever the most-connected function changes (e.g.
36+
// a barrel/type file gets added or removed, or a new heavily-called utility
37+
// shifts the ranking) — pinning keeps "the hub" identity stable so
38+
// back-to-back benchmark runs measure the same node. Shared by both
39+
// query-benchmark.ts and benchmark.ts so the two scripts can't drift apart
40+
// on which names they consider stable.
41+
export const PINNED_HUB_CANDIDATES: readonly string[] = ['buildGraph', 'openDb', 'loadConfig'];
42+
3343
export interface HubTargets {
3444
hub: string;
3545
hubFile: string;

scripts/query-benchmark.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'node:url';
1818
import { resolveBenchmarkExcludes, resolveBenchmarkSource, srcImport } from './lib/bench-config.js';
1919
import { isWorker, workerEngine, workerTargets, forkEngines } from './lib/fork-engine.js';
2020
import { round1, timeMedian } from './lib/bench-timing.js';
21-
import { selectHubTargets, type HubTargets } from './lib/hub-selection.js';
21+
import { PINNED_HUB_CANDIDATES, selectHubTargets, type HubTargets } from './lib/hub-selection.js';
2222

2323
// ── Parent process: fork one child per engine, assemble final output ─────
2424
if (!isWorker()) {
@@ -118,11 +118,6 @@ const RUNS = 5;
118118
// before timing so the metric reflects warm-call latency, not cold-start.
119119
const WARMUP_RUNS = 3;
120120

121-
// Pinned hub targets — stable function names that exist across versions.
122-
// Auto-selecting the most-connected node makes version-to-version comparison
123-
// meaningless when barrel/type files get added or removed.
124-
const PINNED_HUB_CANDIDATES = ['buildGraph', 'openDb', 'loadConfig'];
125-
126121
async function benchDepths(fn, name, depths) {
127122
const result = {};
128123
for (const depth of depths) {

tests/unit/hub-selection.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import os from 'node:os';
1313
import path from 'node:path';
1414
import Database from 'better-sqlite3';
1515
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
16-
import { selectHubTargets } from '../../scripts/lib/hub-selection.js';
16+
import { PINNED_HUB_CANDIDATES, selectHubTargets } from '../../scripts/lib/hub-selection.js';
1717
import { initSchema } from '../../src/db/index.js';
1818

1919
function insertNode(db, name, kind, file, line) {
@@ -82,6 +82,17 @@ describe('selectHubTargets', () => {
8282
expect(targets.hubFile).toBe('src/domain/graph/builder.ts');
8383
});
8484

85+
it('resolves via the shared PINNED_HUB_CANDIDATES list used by both benchmark scripts', () => {
86+
// query-benchmark.ts and benchmark.ts both pass this exact export to
87+
// selectHubTargets — exercise it directly (not just a single-item
88+
// ['buildGraph'] stand-in) so a typo or ordering change in the shared
89+
// list is caught here rather than only at benchmark run time.
90+
expect(PINNED_HUB_CANDIDATES.length).toBeGreaterThan(0);
91+
const targets = selectHubTargets(dbPath, PINNED_HUB_CANDIDATES);
92+
expect(targets.hub).toBe('buildGraph');
93+
expect(targets.hubFile).toBe('src/domain/graph/builder.ts');
94+
});
95+
8596
it('excludes a constant-kind node from the most-connected fallback even with more raw edges', () => {
8697
// No pinned candidates supplied — falls back to the most-connected
8798
// qualifying (function/method) node. constBuildGraph has 3 edges (more

0 commit comments

Comments
 (0)