Documentation
¶
Index ¶
- Variables
- func BuildEdges(symbols []core.SymbolRecord) []core.Edge
- func BuildEdgesDelta(prevEdges []core.Edge, prevSymbols, symbols []core.SymbolRecord, ...) []core.Edge
- func DetectConflicts(a, b core.IsolatedChangeRegion) core.ConflictResult
- func DiffSymbols(before, after []core.SymbolRecord) core.GraphDiff
- type ChangeImpactResult
- type CodeGraph
- func (g *CodeGraph) AffectedTests(files []string) []core.SymbolRecord
- func (g *CodeGraph) BaselineRef() ([]core.SymbolRecord, []core.Edge)
- func (g *CodeGraph) ChangeImpact(query string) (*ChangeImpactResult, error)
- func (g *CodeGraph) ComputeICR(intent string) core.IsolatedChangeRegion
- func (g *CodeGraph) DeadCode(extraRoots []string) *DeadCodeResult
- func (g *CodeGraph) Deps(filePath string) []core.Edge
- func (g *CodeGraph) EdgesSnapshot() []core.Edge
- func (g *CodeGraph) FileSymbols(filePath string) []core.SymbolRecord
- func (g *CodeGraph) Impact(query string, maxDepth int) []core.SymbolRecord
- func (g *CodeGraph) ImpactWithPolicy(query string, maxDepth int, policy TraversalPolicy) []core.SymbolRecord
- func (g *CodeGraph) MissingImplementations(query string) (*MissingImplementationsResult, error)
- func (g *CodeGraph) Neighbors(query, direction string, kinds map[core.EdgeType]bool) []Neighbor
- func (g *CodeGraph) RenamePlan(query, newName string) (*RenamePlanResult, error)
- func (g *CodeGraph) Replace(symbols []core.SymbolRecord, filesIndexed int)
- func (g *CodeGraph) ReplaceWithBaseEdges(symbols []core.SymbolRecord, base, extraEdges []core.Edge, filesIndexed int)
- func (g *CodeGraph) ReplaceWithEdges(symbols []core.SymbolRecord, extraEdges []core.Edge, filesIndexed int)
- func (g *CodeGraph) ReplaceWithStoredEdges(symbols []core.SymbolRecord, edges []core.Edge, filesIndexed int)
- func (g *CodeGraph) Search(query string, limit int) []core.SymbolRecord
- func (g *CodeGraph) SemanticSearch(query string, limit int) []embeddings.Scored
- func (g *CodeGraph) Snapshot() ([]core.SymbolRecord, []core.Edge)
- func (g *CodeGraph) Status() core.Status
- func (g *CodeGraph) TestsFor(query string) []core.SymbolRecord
- func (g *CodeGraph) TestsForSymbol(id string, policy TraversalPolicy) []core.SymbolRecord
- func (g *CodeGraph) TestsForWithStats(query string) ([]core.SymbolRecord, PolicySkips)
- func (g *CodeGraph) UntestedSurface(query string) (*UntestedSurfaceResult, error)
- type CoverageSite
- type DeadCodeResult
- type DeltaMeta
- type MissingImplementationsResult
- type Neighbor
- type PolicySkips
- type RenameEdit
- type RenamePlanResult
- type TraversalPolicy
- type UntestedSurfaceResult
Constants ¶
This section is empty.
Variables ¶
var ( // PolicyDiagnostic walks every edge — debugging / full blast radius. PolicyDiagnostic = TraversalPolicy{Name: "diagnostic"} // PolicyTests: evidence-backed edges only. The 0.7 floor drops type-use // (0.5) and ambiguous name-matched calls (0.6); the reason exclusion drops // the regex body-scan fallback even at same-file confidence (0.85). PolicyTests = TraversalPolicy{Name: "tests", MinConfidence: minTestTraversalConfidence, ExcludeReason: map[core.EdgeReason]bool{core.ReasonRegexFallbck: true}} // PolicyImpact: blast radius keeps dynamic dispatch (real edges) but drops // the regex fallback and the weakest type-use guesses (0.5). PolicyImpact = TraversalPolicy{Name: "impact", MinConfidence: 0.6, ExcludeReason: map[core.EdgeReason]bool{core.ReasonRegexFallbck: true}} // PolicyCertification backs guarantees: only AST-exact / structural / // native edges (≥0.9). The 0.9 floor already drops heuristic dispatch // (0.7) and constructor/inheritance guesses (0.85); the reason exclusions // state the intent. PolicyCertification = TraversalPolicy{Name: "certification", MinConfidence: 0.9, ExcludeReason: map[core.EdgeReason]bool{ core.ReasonDispatch: true, core.ReasonRegexFallbck: true, core.ReasonInheritance: true, core.ReasonConstructor: true, }} )
Functions ¶
func BuildEdges ¶
func BuildEdges(symbols []core.SymbolRecord) []core.Edge
BuildEdges constructs all 8 edge types from the symbol set.
Edge construction order (matches Implementation Plan §3.1):
- defines (file → symbol) confidence 1.0
- contains (parent → child) confidence 1.0
- imports (file → import:path) confidence 0.9
- extends (subtype → supertype) confidence 0.85
- implements (concrete → interface/trait) confidence 0.85
- uses-type (symbol → referenced type) confidence 0.5
- calls (caller → callee) confidence 0.85 same-file, 0.6 cross-file
- tests (test sym → tested sym) confidence 0.8
"calls" and "uses-type" are scoped to same-file + imported-file symbols per the non-negotiable accuracy rule in the plan.
func BuildEdgesDelta ¶ added in v0.24.0
func BuildEdgesDelta(prevEdges []core.Edge, prevSymbols, symbols []core.SymbolRecord, changedFiles map[string]bool, nativeAnalyzedDirs map[string]bool) []core.Edge
BuildEdgesDelta is the []core.Edge-only form of BuildEdgesDeltaMeta.
func DetectConflicts ¶
func DetectConflicts(a, b core.IsolatedChangeRegion) core.ConflictResult
DetectConflicts checks whether two ICRs have overlapping exclusive symbols or files.
func DiffSymbols ¶ added in v0.6.0
func DiffSymbols(before, after []core.SymbolRecord) core.GraphDiff
DiffSymbols computes the structural delta between two symbol snapshots (typically: the graph before and after a merge or reindex).
Symbols are matched by stable identity — file path + qualified name + kind — not by symbol ID: IDs embed the file content SHA, so any edit changes every ID in the file and an ID-based diff would report whole-file churn for a one-line change. A symbol whose span moved but whose signature and body are unchanged is not reported at all; that is what makes the diff usable as a drift signal ("the ground shifted under you") rather than a line-number echo.
Same-key collisions (e.g. C++ overloads sharing a qualified name) are paired positionally in document order; surplus entries on either side surface as added/removed.
Types ¶
type ChangeImpactResult ¶ added in v0.14.1
type ChangeImpactResult struct {
Query string // the query as given
Declarations []core.SymbolRecord // resolved declaration(s) on the named type
Supers []core.SymbolRecord // same-member declarations on other contracts (supertypes of the seed OR of any family member — a sibling interface satisfied by the same implementations breaks under the change exactly like the seed contract)
Family []core.SymbolRecord // overrides/implementations in the subtype closure (excluding Declarations)
Callers []core.SymbolRecord // methods with call edges into Declarations or Family (excluding both)
// DeclaringTypes: type declarations whose bodies contain a change-set
// member signature that is not indexed as its own symbol (Go and TS
// interface members). For those declarations the innermost enclosing
// symbol in the file IS the type, so the type's declaration block is
// the change site a diff, a scorer, or a reviewer names. Empty for
// languages whose member declarations are real symbols (Java, Python).
DeclaringTypes []core.SymbolRecord
// ExternalSupers lists supertype names declared in the hierarchy's
// extends/implements clauses that resolve to no indexed type (JDK or
// dependency types). Informational: the clause is in project source even
// when the type is not.
ExternalSupers []string
// OverridesExternal is non-empty when the queried method is a member of
// an external supertype's contract ("java.util.Iterator#next"): changing
// its signature breaks that contract, and the change-set below is the
// project-local dispatch closure, not a complete must-change set (calls
// through receivers typed as the external supertype are not indexed).
OverridesExternal []string
// Completeness is "closed" when the override family is fully rooted in
// indexed types, "project-local" when it is bounded by an external
// contract (OverridesExternal non-empty, or the query named an external
// type directly).
Completeness string
}
ChangeImpactResult is the full, deterministic change-set for a method signature change: the declaration, every override/implementation in the subtype closure, and every method with a resolved call edge into any of them. This is the task-shaped answer to "what must change if X changes" — computed in the engine so no agent has to orchestrate the traversal over primitives (references → overrides → callers → dedup).
func (*ChangeImpactResult) Sites ¶ added in v0.14.1
func (r *ChangeImpactResult) Sites() []core.SymbolRecord
Sites returns every METHOD in the change-set — declarations, family, callers, and supers — as one deduplicated, file-ordered list. Supers are included because a same-member declaration on another contract (a sibling interface satisfied by the same implementations, or a supertype when the query seeded on an implementation) must change exactly like the seed: omitting it silently dropped the interface's own member from rename-plan and undercounted untested-surface. DeclaringTypes is deliberately NOT unioned here — those are TYPE declarations, not methods; rename-plan consumes them separately (they are edit sites) and untested-surface must not (a type declaration has no test).
type CodeGraph ¶
type CodeGraph struct {
// contains filtered or unexported fields
}
func (*CodeGraph) AffectedTests ¶ added in v0.20.0
func (g *CodeGraph) AffectedTests(files []string) []core.SymbolRecord
AffectedTests returns the covering tests for every symbol defined in the given repo-relative files — the file-diff form of TestsFor. It seeds the inbound test-coverage closure with all symbols in the changed files at once (one traversal), so a CI "run only the affected tests" step maps a `git diff --name-only` straight to the set of test symbols to run. Uses the same evidence-backed policy as TestsFor (low-confidence edges excluded, so a weak bare-name match cannot sweep in unrelated tests across a monorepo).
func (*CodeGraph) BaselineRef ¶ added in v0.24.0
func (g *CodeGraph) BaselineRef() ([]core.SymbolRecord, []core.Edge)
BaselineRef returns the live symbol and edge slices WITHOUT copying, for read-only baseline use by incremental edge construction (deep-copying a 6M-edge monorepo graph doubles peak heap and its GC cost dwarfs the delta itself). The caller must treat both slices as immutable and drop them as soon as the delta completes. Symbols are materialized from the map (order is irrelevant to the delta path, which indexes by ID and file).
func (*CodeGraph) ChangeImpact ¶ added in v0.14.1
func (g *CodeGraph) ChangeImpact(query string) (*ChangeImpactResult, error)
ChangeImpact resolves a "Type.method" or "Type.method(ParamType, ...)" query to the exact change-set for that method's signature. Unlike Impact (name-substring seeded, type-erased BFS), seeding here is type-resolved: the named type's declaration is found via contains edges, the family via the extends/implements subtype closure filtered by signature compatibility, and callers via inbound call edges to those exact symbol IDs — never to same-named methods on unrelated types.
func (*CodeGraph) ComputeICR ¶
func (g *CodeGraph) ComputeICR(intent string) core.IsolatedChangeRegion
ComputeICR computes an Isolated Change Region for the given intent string. When no symbol matches the intent, the region is empty with floor confidence and no lock keys: an arbitrary fallback region (the previous behaviour seeded from the first 20 symbols alphabetically) would make two unrelated no-match intents lock and conflict on the same random files.
func (*CodeGraph) DeadCode ¶ added in v0.15.0
func (g *CodeGraph) DeadCode(extraRoots []string) *DeadCodeResult
DeadCode computes forward reachability from every entry point — main/init functions, test symbols, exported symbols, plus extraRoots by name — over call, test, uses-type, and override edges, and reports the production functions/methods nothing reaches.
func (*CodeGraph) Deps ¶
Deps returns all edges that touch the given file path. Uses exact-prefix matching: edges from "file:<path>" or whose node ID begins with "<path>::" (symbol IDs in that file).
func (*CodeGraph) EdgesSnapshot ¶ added in v0.17.1
EdgesSnapshot returns a copy of the edge list only — the store write path needs no symbols, and deep-copying 100k symbol records to discard them dominated the write phase on monorepos.
func (*CodeGraph) FileSymbols ¶ added in v0.6.1
func (g *CodeGraph) FileSymbols(filePath string) []core.SymbolRecord
FileSymbols returns deep copies of the symbols defined in filePath (slash-separated, repo-relative), ordered by span. Cheap relative to Snapshot: only the one file's symbols are copied.
func (*CodeGraph) Impact ¶
func (g *CodeGraph) Impact(query string, maxDepth int) []core.SymbolRecord
Impact returns all symbols reachable from the seed (identified by query) by traversing inbound edges up to maxDepth. "Inbound" means: things that call, test, or contain the seed symbol — i.e., the blast radius if the seed changes.
func (*CodeGraph) ImpactWithPolicy ¶ added in v0.11.0
func (g *CodeGraph) ImpactWithPolicy(query string, maxDepth int, policy TraversalPolicy) []core.SymbolRecord
ImpactWithPolicy is Impact with an explicit traversal policy — e.g. PolicyCertification for a blast radius that only follows guarantee-grade edges.
func (*CodeGraph) MissingImplementations ¶ added in v0.15.0
func (g *CodeGraph) MissingImplementations(query string) (*MissingImplementationsResult, error)
MissingImplementations resolves a "Type.method" or "Type.method(ParamType, ...)" query to every type in the subtype closure that fails to implement the member. Seeding is type-resolved exactly as in ChangeImpact; nominal closure only, so for Go the structural caveat is the same as ChangeImpact's.
func (*CodeGraph) Neighbors ¶ added in v0.13.0
Neighbors returns the seed symbol's direct typed neighbors — one graph hop, edge types preserved (unlike Impact, which flattens the blast radius). This is what lets a caller ask precisely for "what does X call" (direction "out", kind calls), "who calls X" (direction "in", kind calls), or "what tests X" (direction "in", kind tests) instead of a relevance-ranked blob.
direction is "out", "in", or "both"/"". kinds filters by edge type; an empty set returns every kind. Seeds are matched by exact name / qualified name / ID.
func (*CodeGraph) RenamePlan ¶ added in v0.16.0
func (g *CodeGraph) RenamePlan(query, newName string) (*RenamePlanResult, error)
RenamePlan computes ChangeImpact(query) and converts it to line edits renaming the method to newName.
func (*CodeGraph) Replace ¶
func (g *CodeGraph) Replace(symbols []core.SymbolRecord, filesIndexed int)
func (*CodeGraph) ReplaceWithBaseEdges ¶ added in v0.24.0
func (g *CodeGraph) ReplaceWithBaseEdges(symbols []core.SymbolRecord, base, extraEdges []core.Edge, filesIndexed int)
ReplaceWithBaseEdges installs a precomputed BASE edge set (a BuildEdges/BuildEdgesDelta output) merged with native analyzer edges — the incremental counterpart of ReplaceWithEdges, which computes the base itself.
func (*CodeGraph) ReplaceWithEdges ¶ added in v0.5.0
func (*CodeGraph) ReplaceWithStoredEdges ¶ added in v0.6.0
func (g *CodeGraph) ReplaceWithStoredEdges(symbols []core.SymbolRecord, edges []core.Edge, filesIndexed int)
ReplaceWithStoredEdges installs a previously-computed edge set verbatim — the edges persisted by the last index are already the merged (baseline + native) set, so rehydration must not pay the BuildEdges cost again. Databases written before edges were persisted (symbols but no edges) fall back to a full rebuild.
func (*CodeGraph) Search ¶
func (g *CodeGraph) Search(query string, limit int) []core.SymbolRecord
Search returns symbols matching the query (case-insensitive), ranked by match quality: exact name > exact qualified name > name prefix > name substring > qualified-name substring > path/signature substring. Ranking matters because results are truncated at limit — with the previous alphabetical-by-path ordering, the exact-name match for a common query could be cut off by substring hits in files that happened to sort earlier.
func (*CodeGraph) SemanticSearch ¶
func (g *CodeGraph) SemanticSearch(query string, limit int) []embeddings.Scored
SemanticSearch ranks symbols against a free-text intent using the configured embedding backend (Model2Vec by default; TF-IDF if GROVE_EMBEDDINGS=tfidf). Documents are constructed from (name + qualifiedName + signature + docstring + parent). The engine is built lazily and cached until the next Replace().
func (*CodeGraph) TestsForSymbol ¶ added in v0.12.0
func (g *CodeGraph) TestsForSymbol(id string, policy TraversalPolicy) []core.SymbolRecord
TestsForSymbol returns the covering tests for one symbol ID under the given policy — the ID-seeded form of TestsFor, used to benchmark the closure (and to compare policies) without the name-matching phase.
func (*CodeGraph) TestsForWithStats ¶ added in v0.11.0
func (g *CodeGraph) TestsForWithStats(query string) ([]core.SymbolRecord, PolicySkips)
TestsForWithStats is TestsFor plus the per-reason counts of edges the policy excluded from the closure — the evidence Grove chose not to trust, for certification-style "included vs excluded" reporting.
func (*CodeGraph) UntestedSurface ¶ added in v0.15.0
func (g *CodeGraph) UntestedSurface(query string) (*UntestedSurfaceResult, error)
UntestedSurface computes the change-set for a "Type.method" or "Type.method(ParamType, ...)" query (exactly as ChangeImpact does) and partitions it by covering-test evidence under PolicyTests — evidence-backed edges only, so "covered" is never asserted off a regex-fallback edge, and depth-bounded so "covered" means a test within coverageDepth caller hops.
type CoverageSite ¶ added in v0.15.0
type CoverageSite struct {
Symbol core.SymbolRecord
TestCount int
// Tests is capped (TestCount carries the truth) to keep the payload
// agent-sized: the agent needs "is it covered, and where do I look",
// not every transitive test.
Tests []core.SymbolRecord
}
CoverageSite pairs a change-set site with the tests that reach it (directly or through the inbound dependency closure).
type DeadCodeResult ¶ added in v0.15.0
type DeadCodeResult struct {
RootCount int `json:"rootCount"` // entry points seeded (mains, inits, tests, exported, extra)
ReachableCount int `json:"reachableCount"` // symbols reached from the roots
Considered int `json:"considered"` // production functions/methods examined
// Dead: unreachable, non-exported, name unreferenced anywhere in live
// code. The deletion-candidate list.
Dead []core.SymbolRecord `json:"dead"`
// ExportedUnreferenced: exported symbols with no in-project reference at
// all (no inbound edge, name unreferenced). Dead only if nothing outside
// this project links against it — a decision the graph cannot make.
ExportedUnreferenced []core.SymbolRecord `json:"exportedUnreferenced"`
// Caveats state what static reachability cannot see. They are part of
// the result, not documentation: an agent relaying this answer must
// relay them.
Caveats []string `json:"caveats"`
}
DeadCodeResult reports production functions/methods that nothing reaches. Precision-first: a symbol appears in Dead only when it is unreachable from every root, is not exported, and its name occurs nowhere in live code text (so callback/value references that produce no call edge still keep a symbol alive). Anything static analysis cannot decide is bucketed separately or excluded, never reported as dead.
type DeltaMeta ¶ added in v0.24.0
type DeltaMeta struct {
// AffectedOwners: symbol IDs (in unchanged files) whose out-edges were
// recomputed — their stored rows must be deleted and reinserted.
AffectedOwners map[string]bool
// AffectedTests: test symbol IDs whose tests edges were recomputed.
AffectedTests map[string]bool
// ChangedFiles / NativeAnalyzedDirs: as passed in (splice needs them to
// select insert rows and native-row deletions).
ChangedFiles map[string]bool
NativeAnalyzedDirs map[string]bool
}
BuildEdgesDelta computes the new edge set from the previous edge set (a prior base output, or the merged-with-native set when nativeAnalyzedDirs names every natively re-analyzed package dir), the previous and current full symbol slices, and the set of changed (added/modified/removed) repo-relative file paths. Falls back to BuildEdges(symbols) whenever soundness of the scoping cannot be guaranteed cheaply.
Identity remap: symbol IDs embed the file blob SHA, so EVERY edit turns over every ID in the file. Symbols whose resolution-relevant content is unchanged (identityKey) keep their edges via an oldID→newID rewrite instead of caller re-resolution — a comment-level edit re-resolves almost nothing. Only semantically added/removed/changed symbols contribute to the name delta and the affected set. DeltaMeta describes the write-set of an incremental rebuild — everything a store splice needs to bring the persisted table to the new state without a full-table diff. Nil when the delta fell back to a full rebuild.
type MissingImplementationsResult ¶ added in v0.15.0
type MissingImplementationsResult struct {
Query string // the query as given
Contract []core.SymbolRecord // the method's declaration(s) on the named type
// Missing lists concrete types (class/struct/enum) in the subtype
// closure with no signature-compatible implementation of their own and
// none inherited through their class-extends chain. Each is a compile
// error once the member is required.
Missing []core.SymbolRecord
// AbstractMissing lists abstract classes in the closure without an
// implementation. Not compile-broken themselves — their concrete
// subtypes appear in Missing — but listed because adding the
// implementation there often fixes a whole subtree.
AbstractMissing []core.SymbolRecord
// Unverifiable lists closure types without a visible implementation
// whose class-extends chain leaves the index (an external superclass may
// provide the member). Deliberately separated from Missing: flagging a
// working type as broken sends an agent to "fix" correct code.
Unverifiable []core.SymbolRecord
// ImplementedCount is the number of closure types that do carry a
// compatible implementation (own or inherited) — evidence of coverage
// without inflating the payload; ChangeImpact returns the full family.
ImplementedCount int
// DefaultProvided is true when the contract itself supplies a body every
// subtype inherits (a Java default method, or a concrete method on a
// class/abstract-class seed). No type is then compile-broken today, and
// Missing reads as "inherits the default — breaks if the member becomes
// abstract/required", which is the interface-evolution question that
// motivates querying a defaulted member.
DefaultProvided bool
// Same contract-boundary reporting as ChangeImpactResult.
ExternalSupers []string
OverridesExternal []string
Completeness string // "closed" | "project-local"
}
MissingImplementationsResult is the deterministic answer to "I added (or am adding) Type.method — which types claiming that contract do not implement it?": the companion operation to ChangeImpact for interface evolution. ChangeImpact returns the sites that must change when a signature changes; MissingImplementations returns the types the compiler will reject once the member is required. Computed in the engine so no agent has to orchestrate the traversal (closure → per-type member check → inheritance walk) over primitives.
type Neighbor ¶ added in v0.13.0
type Neighbor struct {
Symbol core.SymbolRecord
EdgeType core.EdgeType
Direction string // "out" = seed→symbol (callee, uses-type); "in" = symbol→seed (caller, test)
Confidence float64
}
Neighbor is a symbol reached from a seed by exactly one typed edge.
type PolicySkips ¶ added in v0.11.0
type PolicySkips map[core.EdgeReason]int
PolicySkips counts, per resolver reason, the edges a policy excluded during a traversal — so consumers (certification) can cite the evidence they did not trust.
type RenameEdit ¶ added in v0.16.0
type RenameEdit struct {
FilePath string
Line int // 1-based
Before string // source line as indexed
After string // with the rename applied
SiteID string // containing symbol ID
Site string // "relpath:name" for relay
}
RenameEdit is one suggested line edit in a rename plan.
type RenamePlanResult ¶ added in v0.16.0
type RenamePlanResult struct {
Query string
NewName string
Edits []RenameEdit // confirmed: apply as-is
Ambiguous []RenameEdit // same-named non-family callee also in scope: verify receiver type first
// Unresolved lists change-set sites for which no line edit could be
// derived (empty indexed text, or the name not found in call position
// on the recorded lines) — the agent must handle these manually.
Unresolved []string
SitesTotal int // sites in the underlying change-impact set
ExternalSupers []string
OverridesExternal []string
Completeness string
}
RenamePlanResult is the change-set of ChangeImpact converted into concrete line edits: declaration/override name lines plus family-resolved call lines, each with a suggested substitution. Precision-first: lines the graph cannot attribute to the family with certainty (the containing method also calls a same-named non-family method) are bucketed Ambiguous, never silently included or dropped. The agent's job becomes review-and-apply.
type TraversalPolicy ¶ added in v0.11.0
type TraversalPolicy struct {
Name string
MinConfidence float64
ExcludeReason map[core.EdgeReason]bool
}
TraversalPolicy decides which edges a consumer closure may walk, by confidence floor and resolver reason. Profiles let tests / impact / certification / diagnostic consumers opt into different strictness instead of one hard-coded threshold, and make the choice explainable (every excluded edge has a reason). See roadmap Wave 4 / Sweep #4.
type UntestedSurfaceResult ¶ added in v0.15.0
type UntestedSurfaceResult struct {
Query string
// Untested: change-set sites (declaration, override family, callers)
// with no covering test in their inbound dependency closure. These are
// the sites a signature change can break silently.
Untested []core.SymbolRecord
// Covered: sites with at least one covering test.
Covered []CoverageSite
TotalSites int
// Same contract-boundary reporting as ChangeImpact — the change-set this
// partition is computed over is subject to the same bound.
ExternalSupers []string
OverridesExternal []string
Completeness string // "closed" | "project-local"
}
UntestedSurfaceResult partitions a method's change-set by test coverage: the answer to "before I change Type.method, what in its blast radius has no test pinning it?". The natural pipeline is change_impact → untested_surface(same query) → write tests for exactly the Untested list.