From 6686202be99c75d549e49225a6a14f4b5b84dff8 Mon Sep 17 00:00:00 2001 From: Sinabina Date: Tue, 21 Jul 2026 17:40:50 -0700 Subject: [PATCH] fix: persist indexed repo per provider and detect installs honestly search was running in cwd and missing the repo you indexed. Persist the indexed path per provider in the selection store and resolve it back so `search` reads the same graph `index` built. Graphify availability now checks `graphify --version` (installed = selectable) instead of "a graph already exists here", and the CLI keys Graphify sources on the repo path. Co-Authored-By: Claude Opus 4.8 (1M context) --- bin/gstack-code-intelligence | 7 ++++++- lib/code-intelligence/index.ts | 2 ++ lib/code-intelligence/picker.ts | 31 ++++++++++++++++++------------ lib/code-intelligence/selection.ts | 17 +++++++++++++++- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/bin/gstack-code-intelligence b/bin/gstack-code-intelligence index 9bcca08ef..e9ea163f4 100755 --- a/bin/gstack-code-intelligence +++ b/bin/gstack-code-intelligence @@ -29,6 +29,7 @@ import { resolveSelectedProvider, setConsent, setProvider, + setRoot, type CodeProviderId, } from "../lib/code-intelligence"; @@ -98,11 +99,15 @@ async function cmdIndex(pathArg: string | undefined): Promise { if (!provider!.local && !consented) { fail(`${provider!.label} would send this repo's content off the machine. Run \`gstack-code-intelligence consent ${repoPath}\` first.`); } - const repo = { id: basename(repoPath), path: repoPath }; + // Graphify keys sources on the repo path; GBrain/Sourcebot on a short id. + const sourceId = provider!.id === "graphify" ? repoPath : basename(repoPath); + const repo = { id: sourceId, path: repoPath }; try { const registered = await provider!.registerSource(repo, { consented }); out(`registered ${repo.id} with ${provider!.label} (${registered.state})`); const refreshed = await provider!.refresh({ id: registered.id }, { consented }); + // Remember which repo this provider indexed so `search` reads the same graph. + setRoot(provider!.id, repoPath); out(`indexed: ${refreshed.state}${refreshed.itemCount != null ? ` (${refreshed.itemCount} items)` : ""}`); } catch (err) { handleProviderError(err, provider!.label); diff --git a/lib/code-intelligence/index.ts b/lib/code-intelligence/index.ts index 0c69719c8..6b387ff8c 100644 --- a/lib/code-intelligence/index.ts +++ b/lib/code-intelligence/index.ts @@ -12,6 +12,8 @@ export { setProvider, setConsent, hasConsent, + setRoot, + getRoot, type Selection, } from "./selection"; export { diff --git a/lib/code-intelligence/picker.ts b/lib/code-intelligence/picker.ts index 0d31d1c28..56573bc91 100644 --- a/lib/code-intelligence/picker.ts +++ b/lib/code-intelligence/picker.ts @@ -15,9 +15,9 @@ import { localEngineStatus } from "../gbrain-local-status"; import { GbrainProvider } from "./gbrain-adapter"; -import { GraphifyProvider, type GraphifyOptions } from "./graphify-adapter"; +import { GraphifyProvider, graphifyInstalled, type GraphifyOptions } from "./graphify-adapter"; import { SourcebotProvider, type SourcebotOptions } from "./sourcebot-adapter"; -import { readSelection } from "./selection"; +import { readSelection, getRoot } from "./selection"; import type { CodeProvider, CodeProviderId } from "./contract"; /** Recommendation order — GBrain first. */ @@ -34,8 +34,12 @@ export function providerById(id: CodeProviderId, opts: PickerOptions = {}): Code switch (id) { case "gbrain": return new GbrainProvider(); - case "graphify": - return new GraphifyProvider({ env: opts.env, ...opts.graphify }); + case "graphify": { + // Default the graph root to the repo Graphify last indexed, so `search` + // reads the same graph `index` built (not whatever cwd happens to be). + const root = opts.graphify?.root ?? getRoot("graphify", opts.env); + return new GraphifyProvider({ env: opts.env, ...opts.graphify, ...(root ? { root } : {}) }); + } case "sourcebot": return new SourcebotProvider({ env: opts.env, ...opts.sourcebot }); } @@ -65,14 +69,17 @@ export async function detectAvailable(opts: PickerOptions = {}): Promise; + /** Provider id → the absolute repo path it last indexed (so search finds it). */ + roots: Record; } -const EMPTY: Selection = { provider: null, consents: {} }; +const EMPTY: Selection = { provider: null, consents: {}, roots: {} }; function storePath(env: NodeJS.ProcessEnv = process.env): string { const home = env.GSTACK_HOME || join(env.HOME || homedir(), ".gstack"); @@ -35,6 +37,7 @@ export function readSelection(env: NodeJS.ProcessEnv = process.env): Selection { return { provider: raw.provider ?? null, consents: raw.consents && typeof raw.consents === "object" ? raw.consents : {}, + roots: raw.roots && typeof raw.roots === "object" ? raw.roots : {}, }; } catch { return { ...EMPTY }; @@ -66,3 +69,15 @@ export function setConsent(repoPath: string, consented: boolean, env: NodeJS.Pro export function hasConsent(repoPath: string, env: NodeJS.ProcessEnv = process.env): boolean { return readSelection(env).consents[resolve(repoPath)] === true; } + +/** Record the repo path a provider last indexed, so search reads the same graph. */ +export function setRoot(provider: CodeProviderId, repoPath: string, env: NodeJS.ProcessEnv = process.env): Selection { + const current = readSelection(env); + const next: Selection = { ...current, roots: { ...current.roots, [provider]: resolve(repoPath) } }; + write(next, env); + return next; +} + +export function getRoot(provider: CodeProviderId, env: NodeJS.ProcessEnv = process.env): string | undefined { + return readSelection(env).roots[provider]; +}