Files
codegraph/__tests__/explore-pinned-allocation.test.ts
T
Colby McHenry 238dbc5cec fix(explore): accurately resolve query file paths and find camelCase symbols
Previously, `codegraph_explore` queries explicitly naming files by path (e.g., `src/routes/m/projects/[id]/runs/[runId]/+page.svelte`) were shredded. Bracketed path segments exploded into "named symbol" seeds, and FTS on fragments like `page` or `runs` admitted every sibling file, starving the user's intended target.

This change introduces:
- **Query path pinning:** File paths named in a query are now resolved against the index, "pinned," and stripped from the query. Pinned files are guaranteed inclusion, top ranking, and fair allocation. Unresolvable path-like spans are reported.
- **Segment vocabulary supplement:** Natural language query terms (e.g., "auto-scroll to bottom") can now reach camelCase identifiers (e.g., `pinFeedIfNearBottom`, `feedAtBottom`) by matching against their constituent segments.
- **Variable seeding:** `variable` and `constant` node kinds are now included in identifier seeding, improving recall for `$state`-style variables common in frameworks like Svelte.
2026-08-20 09:10:07 -07:00

84 lines
3.4 KiB
TypeScript

/**
* Pinned files in `allocateExploreBudget` (see query-paths.ts): a file the
* query named by PATH must survive every allocation guard. Its score is
* whatever the path-stripped query happened to match — for a pure-path query,
* nearly nothing — so without the pinned floor the proportional split would
* fund the one file the agent explicitly asked for worst of all, and the
* cliff would zero it outright.
*/
import { describe, it, expect } from 'vitest';
import { allocateExploreBudget, getExploreOutputBudget, EXPLORE_ALLOCATION } from '../src/mcp/tools';
import type { ExploreAllocationCandidate } from '../src/mcp/tools';
const cand = (
path: string,
score: number,
extra: Partial<ExploreAllocationCandidate> = {},
): ExploreAllocationCandidate => ({ path, score, worth: 1, spine: false, ...extra });
const budget = getExploreOutputBudget(1000);
describe('allocateExploreBudget — pinned files', () => {
it('never cliffs a pinned file, however low it scores', () => {
const { allowances, cliffed } = allocateExploreBudget(
[
cand('pinned.svelte', 0.1, { pinned: true }),
cand('hub.ts', 200),
cand('noise.ts', 0.1),
],
budget,
8,
);
expect(cliffed).toContain('noise.ts');
expect(cliffed).not.toContain('pinned.svelte');
expect(allowances.has('pinned.svelte')).toBe(true);
});
it('funds a pinned file at least as well as the strongest candidate', () => {
const { allowances } = allocateExploreBudget(
[
cand('pinned.svelte', 0.5, { pinned: true }),
cand('hub.ts', 300),
cand('helper.ts', 40),
],
budget,
8,
);
expect(allowances.get('pinned.svelte')!).toBeGreaterThanOrEqual(allowances.get('hub.ts')!);
expect(allowances.get('pinned.svelte')!).toBeGreaterThan(allowances.get('helper.ts')!);
});
it('keeps pinned files through the affordability trim', () => {
// Smallest tier: affordable = floor(13000 / (MIN_CHARS + FILE_OVERHEAD)) = 14
// slots. 18 equal-weight candidates admitted → the trim must cut 4. The
// pinned file sits last with a TIED weight (the pinned floor lifts it to
// the top weight), so the stable by-weight sort would slice it off — only
// the explicit spine/pinned keep saves it.
const tiny = getExploreOutputBudget(10);
const fleet = Array.from({ length: 17 }, (_, i) => cand(`f${i}.ts`, 100));
fleet.push(cand('pinned.svelte', 0.1, { pinned: true }));
const { allowances, cliffed } = allocateExploreBudget(fleet, tiny, 18);
expect(allowances.has('pinned.svelte')).toBe(true);
expect(cliffed).not.toContain('pinned.svelte');
expect(allowances.size).toBeLessThan(18);
});
it('an all-pinned zero-score call still allocates (pure-path query)', () => {
const { allowances, pool } = allocateExploreBudget(
[cand('a.svelte', 0, { pinned: true }), cand('b.svelte', 0, { pinned: true })],
budget,
8,
);
expect(pool).toBeGreaterThan(0);
expect(allowances.get('a.svelte')!).toBeGreaterThanOrEqual(EXPLORE_ALLOCATION.MIN_CHARS);
expect(allowances.get('b.svelte')!).toBeGreaterThanOrEqual(EXPLORE_ALLOCATION.MIN_CHARS);
});
it('unpinned behavior is unchanged when no candidate is pinned', () => {
const before = allocateExploreBudget(
[cand('a.ts', 40), cand('b.ts', 10)], budget, 8,
);
expect(before.allowances.get('a.ts')!).toBeGreaterThan(before.allowances.get('b.ts')!);
});
});