Skip to content

Commit 90f40a2

Browse files
fix(storage): purge stale lbug/DuckDB refs after ADR 0019; fix 2 latent bugs (#247)
## Summary The single-file SQLite migration (ADR 0019, #245) left stale references to the removed `graph.lbug` + `temporal.duckdb` backends across source comments, docs, agent-facing MCP tool descriptions, and user-facing error strings. This sweeps and corrects them, and fixes **two real bugs** the migration left behind that its own tests did not catch. ## Bugs fixed (behavioral) **1. `describeArtifacts()` pointed at a file that no longer exists.** `paths.ts` still returned `graphFile="graph.lbug"` / `temporalFile="temporal.duckdb"`, but `openStore()` writes `store.sqlite`. That value is not cosmetic: it feeds the `is-indexed` existence probe (`cli/src/lib/is-indexed.ts`) and the user-facing path in the MCP "store unreadable" error (`mcp/src/tools/shared.ts`). So the error told users to check a `.codehub/graph.lbug` that is gone. Now returns `store.sqlite` for both views. `paths.test.ts` had pinned the wrong values, so it was green while asserting broken behavior — updated. **2. `bm25CorpusHasSummaries()` queried `information_schema.tables`.** That is a DuckDB/Postgres catalog `node:sqlite` does not expose. The query threw, was swallowed by the surrounding `try/catch`, and the probe was silently always-false in production. Switched to `sqlite_master`; updated the test mock that pinned the old string. ## Also: the `sql` MCP tool contract The `sql` tool now advertises `nodes`/`edges`/`embeddings` as directly SQL-queryable (they are real tables in `store.sqlite`) instead of claiming the graph is "NOT SQL-queryable, reachable only through Cypher". The `cypher:` arg returns a clear "use `sql:` instead" envelope against the default SQLite backend (`execCypher` is unimplemented per ADR 0019, reserved for community forks). `sql.test.ts` updated to assert the inverted contract. ## Scope 47 files. Most are comment/doc corrections; the 4 above are behavioral. The DuckDB→SQLite comment pass was context-aware (historical-fact mentions like "replaced the lbug + **DuckDB** pair" intentionally kept). ## Verification - typecheck clean: core-types, storage, mcp, cli - storage suite: 81 pass / 0 fail - mcp suite: 234 pass / 0 fail - cli suite: 346 pass / 0 fail - banned-strings + commitlint pre-commit gates pass - pre-push test hook passed on push
1 parent c72c84f commit 90f40a2

48 files changed

Lines changed: 238 additions & 213 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
0 Bytes
Binary file not shown.

packages/analysis/src/test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* `traverseDescendants`, `traverse`, plus the ITemporalStore-compat noops.
1212
*
1313
* Per-test fixtures populate the store via `addNode` / `addEdge`; the test
14-
* then exercises the production code through the same finders the DuckDb
14+
* then exercises the production code through the same finders the SQLite store
1515
* and GraphDb adapters expose. No raw SQL crosses the test boundary.
1616
*/
1717

packages/analysis/src/verdict.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* Contributors) never crashes the verdict; it simply drops the missing
1616
* signal.
1717
* - **Zero `any`**: the only loose type surface is `Record<string,unknown>`
18-
* for raw DuckDB rows, each of which we narrow with explicit casts.
18+
* for raw SQLite rows, each of which we narrow with explicit casts.
1919
*/
2020

2121
import { execFile } from "node:child_process";

packages/cli/src/commands/analyze.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export interface AnalyzeOptions {
5656
readonly force?: boolean;
5757
/**
5858
* When true, the embeddings phase embeds every callable/declaration symbol
59-
* and the result is upserted into the DuckDB `embeddings` table. Requires
59+
* and the result is upserted into the `embeddings` table. Requires
6060
* `codehub setup --embeddings` to have installed weights; if weights are
6161
* missing the phase logs a warning and skips — analyze never aborts.
6262
*/
@@ -222,7 +222,7 @@ export async function runAnalyze(path: string, opts: AnalyzeOptions = {}): Promi
222222

223223
// Load a prior graph projection for the incremental-scope phase when the
224224
// CLI was not invoked with --force. The projection is a thin wrapper
225-
// around the prior DuckDB index (File nodes + IMPORTS / EXTENDS /
225+
// around the prior SQLite index (File nodes + IMPORTS / EXTENDS /
226226
// IMPLEMENTS edges). `loadPreviousGraph` silently returns undefined if
227227
// the store does not exist or cannot be opened; incremental-scope then
228228
// reports mode="full" with reason="no-prior-graph".
@@ -333,10 +333,9 @@ export async function runAnalyze(path: string, opts: AnalyzeOptions = {}): Promi
333333
);
334334
}
335335

336-
// Persist to the composed graph + temporal store. Storage is always
337-
// graph.lbug (graph-tier) + temporal.duckdb sidecar (cochanges, summary
338-
// cache); the temporal-tier writes (`bulkLoadCochanges`,
339-
// `bulkLoadSymbolSummaries`) route through `store.temporal`.
336+
// Persist to the composed graph + temporal store. Post-ADR 0019 both views
337+
// are one `store.sqlite`; the temporal-tier writes (`bulkLoadCochanges`,
338+
// `bulkLoadSymbolSummaries`) still route through `store.temporal`.
340339
await mkdir(resolveRepoMetaDir(repoPath), { recursive: true });
341340
const dbPath = resolveGraphPath(repoPath);
342341
const store: Store = await openStore({ path: dbPath });
@@ -423,7 +422,7 @@ export async function runAnalyze(path: string, opts: AnalyzeOptions = {}): Promi
423422

424423
// Persist the scan-state sidecar so the next analyze invocation can feed
425424
// the incremental-scope phase via loadPreviousGraph(). We write this
426-
// alongside the DuckDB file under `<repo>/.codehub` so a clean of the
425+
// alongside the store.sqlite file under `<repo>/.codehub` so a clean of the
427426
// meta dir invalidates both the index and the incremental state together.
428427
if (result.scan !== undefined) {
429428
await writeScanState(
@@ -434,7 +433,7 @@ export async function runAnalyze(path: string, opts: AnalyzeOptions = {}): Promi
434433

435434
// Opt-in skill generation. Walk Community nodes just persisted above and
436435
// emit one SKILL.md per cluster under `<repo>/.codehub/skills/`. Runs
437-
// against the still-open DuckDB handle so there's no re-open cost, and
436+
// against the still-open SQLite handle so there's no re-open cost, and
438437
// any per-skill failure (read-only dir, permission denied, disk full)
439438
// logs-and-continues — analyze never aborts because of a skill write.
440439
if (opts.skills === true) {
@@ -579,7 +578,7 @@ export async function runAnalyze(path: string, opts: AnalyzeOptions = {}): Promi
579578

580579
/**
581580
* Build the {@link pipeline.PreviousGraph} projection expected by the
582-
* incremental-scope phase from the prior DuckDB index + scan-state sidecar.
581+
* incremental-scope phase from the prior SQLite index + scan-state sidecar.
583582
*
584583
* The projection carries:
585584
* - file paths + scan-time content hashes, read from
@@ -827,7 +826,7 @@ export async function resolveCoverageEnabled(
827826
* compute that before the pipeline runs (LSP phases haven't yielded
828827
* yet), so we use the prior run's stored counts when available:
829828
*
830-
* - If a DuckDB store is readable at the expected path, count nodes
829+
* - If a SQLite store is readable at the expected path, count nodes
831830
* whose kind is Function/Method/Class. That count is the best proxy
832831
* for "SCIP-confirmed callables" we can get before the parse phase.
833832
* - If no prior store exists (fresh clone, first analyze), fall back
@@ -863,7 +862,7 @@ export async function resolveMaxSummariesCap(
863862

864863
/**
865864
* Count callable symbols (Function / Method / Class) recorded by the
866-
* prior run. Returns `undefined` when no prior DuckDB index exists or
865+
* prior run. Returns `undefined` when no prior SQLite index exists or
867866
* the count query fails — callers treat that as "no prior run" and fall
868867
* back to the first-run heuristic.
869868
*/
@@ -893,7 +892,7 @@ async function countPriorCallableSymbols(repoPath: string): Promise<number | und
893892
}
894893

895894
/**
896-
* Open a read-only DuckDB store scoped to the `symbol_summaries` cache
895+
* Open a read-only SQLite store scoped to the `symbol_summaries` cache
897896
* probe. The returned object carries a cache adapter the `summarize`
898897
* phase uses to short-circuit candidates whose content hash already has
899898
* a row on disk, plus a `close()` the caller invokes to release the
@@ -927,7 +926,7 @@ async function openSummaryCacheAdapter(
927926
}
928927

929928
/**
930-
* Open a read-only DuckDB store scoped to the `embeddings` content-hash
929+
* Open a read-only SQLite store scoped to the `embeddings` content-hash
931930
* probe. The returned adapter's `list()` loads every prior
932931
* `(granularity, nodeId, chunkIndex) → content_hash` row in a single
933932
* round-trip so the embeddings phase can skip chunks whose source text is
@@ -992,7 +991,7 @@ function fileFromNodeId(id: string): string | undefined {
992991
// returns rehydrated `GraphNode` objects, so the constant is no longer
993992
// load-bearing here. The `rowToGraphNode` / `rowToCodeRelation` adapters
994993
// below remain exported for external consumers that hand-roll over the
995-
// DuckDB wide-column shape.
994+
// SQLite wide-column shape.
996995

997996
const NODE_KIND_SET: ReadonlySet<string> = new Set<string>(NODE_KINDS);
998997
const RELATION_TYPE_SET: ReadonlySet<string> = new Set<string>(RELATION_TYPES);
@@ -1015,7 +1014,7 @@ function boolField(r: Record<string, unknown>, col: string): boolean | undefined
10151014
}
10161015

10171016
function stringArrayField(r: Record<string, unknown>, col: string): readonly string[] | undefined {
1018-
// Preserve `[]` distinct from absent. The DuckDB TEXT[] binder returns
1017+
// Preserve `[]` distinct from absent. The SQLite TEXT[] binder returns
10191018
// a 0-length JS array for an empty SQL array literal and `null` for
10201019
// SQL NULL; mirror the storage adapter's `setStringArrayField` and
10211020
// return the array verbatim so a Community / Route node written as

packages/cli/src/commands/code-pack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface CodePackArgs {
6161
readonly engine?: "pack" | "repomix";
6262
/**
6363
* Test seam — inject a custom `generatePack` so unit tests don't need
64-
* to load native DuckDB bindings. Production callers leave this
64+
* to load native storage bindings. Production callers leave this
6565
* unset.
6666
*/
6767
readonly _generatePack?: typeof generatePack;

packages/cli/src/commands/ingest-sarif.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Flow:
66
* 1. Read + parse + validate the SARIF file via `@opencodehub/sarif`.
77
* 2. Resolve the target repo (either `--repo <name>` or CWD).
8-
* 3. Open the DuckDB store and pull a per-file, line-sorted symbol
8+
* 3. Open the SQLite store and pull a per-file, line-sorted symbol
99
* index over the SARIF's referenced URIs (used to resolve Finding
1010
* → Symbol edges).
1111
* 4. For every Result across every Run, build a Finding node keyed by
@@ -15,7 +15,7 @@
1515
* enclosing symbol at `(uri, startLine)` when the graph contains
1616
* one. A scanner-provided `opencodehub.symbolId` hint wins over the
1717
* enclosing lookup when set.
18-
* 5. UPSERT into DuckDB via `store.bulkLoad({ mode: "upsert" })`.
18+
* 5. UPSERT into the SQLite store via `store.bulkLoad({ mode: "upsert" })`.
1919
*
2020
* The command is idempotent — re-running with the same SARIF produces
2121
* the same nodes and edges. Results without a parsable location (no
@@ -140,7 +140,7 @@ interface BuildSummary {
140140

141141
/**
142142
* Pure builder over SARIF runs. Exposed for unit tests so we can exercise
143-
* the node/edge emission logic without touching DuckDB.
143+
* the node/edge emission logic without touching SQLite.
144144
*
145145
* `nodesByFile` is the per-file, line-sorted symbol index (produced by
146146
* {@link indexNodesByFile}) used to resolve each SARIF result back to the

packages/cli/src/commands/list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ type Health = "ok" | "path-missing" | "graph-missing";
3434

3535
function classifyHealth(entry: RepoEntry): Health {
3636
if (!existsSync(entry.path)) return "path-missing";
37-
// Indexed probe: presence of `meta.json` / `graph.lbug` under `.codehub/`
38-
// counts as "indexed" (lbug is the only graph backend post-ADR 0016).
37+
// Indexed probe: presence of `meta.json` / `store.sqlite` under `.codehub/`
38+
// counts as "indexed" (the single-file store is the only backend, ADR 0019).
3939
if (!codehubIsIndexed(entry.path)) return "graph-missing";
4040
return "ok";
4141
}

packages/cli/src/commands/open-store.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* Returns the canonical {@link Store} envelope from `@opencodehub/storage`
77
* so callers can route graph-tier queries through `store.graph` and
88
* temporal-tier queries (cochanges, summaries, `--sql` escape hatch)
9-
* through `store.temporal`. Storage is always graph.lbug + temporal.duckdb;
10-
* the legacy backend selector was removed when the DuckDB graph backend
11-
* was ripped out (see ADR 0016).
9+
* through `store.temporal`. Post-ADR 0019 both views are one `SqliteStore`
10+
* over a single `<repo>/.codehub/store.sqlite`; the legacy backend selector
11+
* was removed when the lbug + DuckDB pair was replaced (see ADR 0019).
1212
*/
1313

1414
import { resolve } from "node:path";
@@ -33,7 +33,7 @@ export async function openStoreForCommand(opts: OpenStoreOptions): Promise<OpenS
3333
path: dbPath,
3434
readOnly: opts.readOnly ?? true,
3535
});
36-
// The legacy CLI entry point opened the DuckDB connection eagerly and
36+
// The legacy CLI entry point opened the store connection eagerly and
3737
// every command consumed an already-open store. The `openStore` factory
3838
// only constructs adapters; opening is the lifecycle owner's job. Keep
3939
// that contract by opening both views here so command handlers stay a

packages/cli/src/commands/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const INCLUDE_CONTENT_CHAR_CAP = 2000;
5050
const SUMMARY_COLUMN_CHAR_CAP = 120;
5151

5252
/**
53-
* Hook for tests to inject a pre-built store without touching DuckDB. The
53+
* Hook for tests to inject a pre-built store without touching SQLite. The
5454
* default implementation delegates to {@link openStoreForCommand}. Kept
5555
* separate from the public `QueryOptions` interface so end-user CLI callers
5656
* aren't tempted to pass an in-process store.

packages/cli/src/commands/status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface StatusOptions {
3131
/**
3232
* Test seam: open a read-only store and return its retrieval state. Defaults
3333
* to opening the real composed store. Tests inject a stub so they don't need
34-
* a live graph.lbug on disk.
34+
* a live store.sqlite on disk.
3535
*/
3636
readonly probeRetrieval?: (repoPath: string) => Promise<RetrievalState | undefined>;
3737
}

0 commit comments

Comments
 (0)