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.
This commit is contained in:
Colby McHenry
2026-08-20 09:10:07 -07:00
parent c6aaa20358
commit 238dbc5cec
15 changed files with 905 additions and 31 deletions
@@ -0,0 +1,5 @@
{
"name": "explore-path-pinning-fixture",
"version": "1.0.0",
"private": true
}
@@ -0,0 +1,31 @@
/** In-memory registry of task runs, keyed by run id. */
export interface Scope {
projectId: string;
label: string;
}
export const runId = 'run-000';
const runs = new Map<string, { id: string; scope: Scope; status: string }>();
export function registerRun(id: string, scope: Scope): void {
runs.set(id, { id, scope, status: 'queued' });
}
export function getRun(id: string): { id: string; scope: Scope; status: string } | null {
return runs.get(id) ?? null;
}
export function listRuns(scope: Scope): string[] {
return [...runs.values()]
.filter((r) => r.scope.projectId === scope.projectId)
.map((r) => r.id);
}
export function stopRun(id: string): boolean {
const run = runs.get(id);
if (!run) return false;
run.status = 'cancelled';
return true;
}
@@ -0,0 +1,28 @@
/** Detached chat window page — session presence + streaming state. */
let chatAtBottom = true;
let isStreaming = false;
let messages: string[] = [];
export function handleMessagesScroll(distance: number): void {
chatAtBottom = distance < 50;
}
export function sendMessage(text: string): void {
messages = [...messages, text];
isStreaming = true;
}
export function stopResponse(): void {
isStreaming = false;
}
export function redock(): void {
messages = [];
isStreaming = false;
chatAtBottom = true;
}
export function chatSnapshot(): { messages: string[]; streaming: boolean } {
return { messages: [...messages], streaming: isStreaming };
}
@@ -0,0 +1,67 @@
/** Mobile run feed — event stream + scroll pinning for the run page. */
export interface FeedEvent {
id: string;
kind: 'output' | 'tool' | 'error';
content: string;
}
const EVENT_CAP = 300;
let events: FeedEvent[] = [];
let workingLine: string | null = null;
/** Whether the reader is at the tail of the feed (within 50px). */
let feedAtBottom = true;
interface FeedElement {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}
let feedEl: FeedElement | null = null;
export function bindFeedElement(el: FeedElement | null): void {
feedEl = el;
}
/** Track the reader's position; called from the feed's scroll listener. */
export function handleFeedScroll(): void {
const el = feedEl;
if (!el) return;
feedAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 50;
}
/** Re-pin after async content growth (image loads), only when at the tail. */
export function pinFeedIfNearBottom(): void {
const el = feedEl;
if (!el) return;
if (feedAtBottom) {
el.scrollTop = el.scrollHeight;
}
}
export function appendEvent(event: FeedEvent): void {
events = events.length >= EVENT_CAP
? [...events.slice(-(EVENT_CAP - 1)), event]
: [...events, event];
if (feedAtBottom) {
pinFeedIfNearBottom();
}
}
export function setWorkingLine(line: string | null): void {
workingLine = line;
pinFeedIfNearBottom();
}
export function resetFeed(): void {
events = [];
workingLine = null;
feedAtBottom = true;
}
export function feedSnapshot(): { events: FeedEvent[]; workingLine: string | null } {
return { events: [...events], workingLine };
}