refactor(core): GraphRepository — complete RULE-ARCH-003 repository abstraction - #165
Conversation
…hase 1) The storage + query operations as one interface (RULE-ARCH-003), connection held by the future adapter — no GraphDbConnection in any signature. ForeignGraphReader is a separate read-only port for foreign-workspace DB access (the switcher), since those reads open arbitrary DBs by path and are driven by the extension. Lens selectors stay out (in-memory graphology, not storage). Pure addition — no implementation, no callers yet. core 266/266, typecheck + lint clean. Refs: #164
… fns (phase 2) Implements GraphRepository over an open DatabaseHandle, supplying the connection to each existing storage/query function so callers never pass one. Stateless DuckDbForeignGraphReader for the read-only foreign-workspace functions (they open by path themselves). Thin relocation behind the interface — zero behavior change. Adapter tests prove delegation against a real in-memory DuckDB (write/read round-trip, clearWorkspace/clearFile parity, coverage/edge-kinds, dispose closes the handle). Two test expectations corrected to real behavior: replaceFileGraph synthesizes a DEFINES edge per file (deletedEdges=2), and clearFile matches the absolute stored path (what the watcher passes), not the relative path. No caller migration yet — shippable on its own. core 271/271, typecheck + lint clean. Refs: #164
…dle (phase 3) DuckTreeIndexer now constructs a DuckDbGraphRepository over the opened handle and calls this.repo.X() everywhere — the 15 requireDatabaseHandle()/database.connection sites are gone, replaced by a single requireRepo(). initialize() builds the repo then drives initializeSchema/applyMigrations through it; dispose() calls repo.dispose() (which closes the handle). index.ts no longer references a connection at all — the DIP boundary of RULE-ARCH-003 is achieved; the composition root still names openDatabase + DuckDbGraphRepository, which is correct. Import surface collapsed from ~19 storage/query functions to openDatabase + DuckDbGraphRepository (the query re-exports are independent export-from lines). Public Indexer contract unchanged: core 271/271, extension 640/640 with no consumer edits. Refs: #164
…free public API (phase 4) The extension's two foreign-workspace read sites (extension.ts switch, cache/ workspaceRegistry list) now use DuckDbForeignGraphReader instead of the bare readWorkspaceGraph/readWorkspaceIndexSummary functions. Core's public barrel (index.ts) drops the connection-first re-exports (getPresentEdgeKinds/ getCoverageReport/neighborhood) and the bare foreign-read fns, exporting the GraphRepository/ForeignGraphReader interfaces + DuckDbForeignGraphReader adapter instead. The internal storage barrel stops re-exporting connection-first functions too. Result: no GraphDbConnection-taking signature appears anywhere in core's public API — the only supported entry to storage is the repository interface + its adapter (RULE-ARCH-003). The connection-first functions remain as internal implementation the adapter delegates to (not deleted — that would mean inlining all SQL into the adapter for no behavioral gain). Extension test mock updated to mock the DuckDbForeignGraphReader class (via vi.hoisted). core 271/271, extension 640/640, typecheck + lint clean. Refs: #164
…e (phase 5) In-memory GraphRepository implementation (per-file symbol storage, real clears, cache round-trip; analytics reads return valid empty shapes). The type checker enforces it satisfies the whole interface, and the test consumes it only through the GraphRepository type — demonstrating substitutability (RULE-ARCH-003) without a DuckDB instance. The indexer still constructs its DuckDbGraphRepository internally; wiring a constructor seam to inject an arbitrary repository would change the public createIndexer contract, so that's left as a future enhancement — the fake proves the contract is substitutable at the interface level. core 274/274. Refs: #164
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughSummaryUser-visible or reviewer-relevant changes:
Risks:
Deferred or not validated:
Package boundaries & schema:
WalkthroughThis PR completes RULE-ARCH-003 by abstracting storage and query operations behind a ChangesGraph Repository Abstraction
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
Comment |
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
#167) * feat(core): persisted precise (pass-2) resolution write path Adds the core half of persisted precise call resolution (the unbuilt 'pass-2 LSP fills them in' the edge schema references). New GraphRepository methods: - getUnresolvedCallSites(workspaceRoot): CALLS edges not yet precise, each with the source symbol's NodeLocation (file + start line/col) for the LSP query - findSymbolIdAt(filePath, line): map an LSP result location back to a stored target symbol id (file + start line; null on no/ambiguous match) - persistPreciseEdges(resolutions): set target_id + stamp metadata.resolution='precise' (confidence 1.0) in one transaction Implemented in storage/preciseResolution.ts (behind the adapter) + the in-memory FakeGraphRepository (with a seedCallSite test helper). The existing stampResolutionTier already guards <> 'precise', so precise edges are never downgraded — proven by test. Real-DuckDB tests cover list/match/persist, idempotency, and no-downgrade. core 279/279. Refs: #166 * feat: persist precise (LSP) call resolution via opt-in command Wires the pass-2 precise resolution end to end. New Indexer.resolvePreciseEdges (workspaceRoot, resolver, {onProgress, isCancelled}) orchestrates the core loop: work-list (getUnresolvedCallSites) → host resolver per site → match each result back to a target (findSymbolIdAt) → batch persist (persistPreciseEdges). The host owns only the LSP resolver (RULE-ARCH-005); all DB access stays behind the repository. Extension: createResolvePreciseEdgesCommand runs the pass under a cancellable withProgress notification (reports N/M sites · K upgraded) and refreshes the graph on finish. Registered as dextree.resolvePreciseEdges / 'Resolve Precise Edges (LSP)' in activate + package.json. Behavior-preserving + opt-in: nothing runs unless invoked; idempotent (precise edges drop out of the work-list); graceful (cold/empty resolver leaves edges heuristic); cancellable (persists partial). Integration tests against a real in-memory DB cover upgrade, idempotency, cold resolver, and cancellation; command tests cover the UI shell. Indexer mocks across extension tests gained the method. core 282 / exporters 161 / extension 643. Refs: #166
Summary
Completes
RULE-ARCH-003(repository pattern). The backend-stabilization PR confined the DuckDB driver to one adapter; this puts the storage + query operations behind aGraphRepositoryinterface — the connection is held by the adapter and never appears in a method signature, so no domain code touches a connection. A narrowForeignGraphReadercovers the extension's read-only foreign-workspace access.DuckTreeIndexernow depends on the interface instead of aDatabaseHandle.Behavior-preserving throughout; the public contract is unchanged. Full suite green at every phase (core 274 / exporters 161 / extension 640).
Linked spec / issue
openspec/changes/graph-repository/(OpenSpec change; openspec/ is gitignored)Slice classification
refactor— internal architecture change, no behavior delta (one additive test fake)Affected surfaces
packages/core(interfaces, adapter, indexer, fake)packages/extension(foreign-reader call sites + one test mock)Phases (commit-by-commit)
db52387interfaces —GraphRepository+ForeignGraphReader, noGraphDbConnectionin any signaturee5208f0adapter —DuckDbGraphRepositorydelegating to the existing storage/query fns; delegation tests vs real in-memory DuckDBd080618indexer swap —DuckTreeIndexerholdsthis.repo; the 15requireDatabaseHandle()/database.connectionsites becomethis.repo.X();index.tsis now connection-free594839aforeign reads + connection-free public API — extension's two foreign-read sites useDuckDbForeignGraphReader; core's public barrel drops the connection-first re-exportsc7d95fftest fake —FakeGraphRepositoryproves substitutability through the interfaceScope decisions (where the plan met reality)
GraphDbConnection-taking signature is exported anymore.FakeGraphRepositoryis consumed through theGraphRepositorytype. Wiring it into the indexer via constructor injection would change the publiccreateIndexercontract, so that's left as a future enhancement.Test plan
pnpm checkgreen across all packages (forced/uncached): core 274, exporters 161, extension 640GraphDbConnectionappears in anyGraphRepositorymethod signatureindex.tshas zerodatabase.connectionreferences — the DIP boundary is realIndexercontract unchanged — extension suite passes with no consumer edits beyond the two foreign-reader sitesFakeGraphRepositoryproves substitutabilitypnpm dedupe --checkcleanChecklist
Closes:references the tracking issueanywithout justificationpackages/coreNotes for reviewers
ForeignGraphReadersplit is deliberate (design D2): foreign reads open arbitrary DBs by path and are driven by the extension, so they don't belong on the connection-owning repository.🤖 Generated with Claude Code