Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

220 lines
12 KiB
Go

package semantic
import (
"context"
"errors"
"time"
"github.com/zzet/gortex/internal/graph"
)
// Provider enriches graph edges and nodes with semantic information
// from a language-aware analysis backend (SCIP, go/types, LSP, etc.).
type Provider interface {
// Name returns a human-readable identifier (e.g., "scip-go", "go-types", "gopls").
Name() string
// Languages returns the language codes this provider handles (e.g., ["go"]).
Languages() []string
// Available reports whether this provider can run. Checks for
// external tool availability (e.g., scip-go on PATH, go command present).
Available() bool
// Enrich performs a full enrichment pass over the graph for the given repo root.
// It upgrades edge confidence, adds missing edges, and fills Node.Meta fields.
// Called after tree-sitter indexing + resolver pass completes.
Enrich(g graph.Store, repoRoot string) (*EnrichResult, error)
// EnrichFile performs a targeted enrichment for a single file and its
// immediate dependents. Used in watch mode for incremental updates.
// Returns nil result if incremental enrichment is not supported.
EnrichFile(g graph.Store, repoRoot string, filePath string) (*EnrichResult, error)
// Close releases any resources held by the provider (daemon processes,
// temp files, connections).
Close() error
}
// RepoScopedProvider is an optional interface a Provider MAY implement to
// receive the repo prefix of the enrichment root alongside the root path.
// In a multi-repo daemon the shared graph holds file nodes from every
// tracked repo, and two repos can share a relative path; a provider that
// selects its work by walking graph file nodes needs the prefix to scope
// to the repo actually being enriched rather than guessing from disk
// existence (which a path collision defeats). The Manager calls EnrichRepo
// when the provider implements it, passing the repo's prefix (empty in
// single-repo mode); otherwise it falls back to Enrich.
type RepoScopedProvider interface {
EnrichRepo(g graph.Store, repoPrefix, repoRoot string) (*EnrichResult, error)
}
// EnrichDeadlinePolicy computes the per-repo enrichment context deadline
// from the post-filter unenriched candidate count — the symbols a prior
// pass has NOT already stamped, i.e. the work that actually remains. It
// lets a ContextEnricher size its own window AFTER candidate selection: a
// warm restart with few unstamped nodes lands a small budget, while a cold
// repo (nothing stamped) keeps the full size-scaled headroom. A non-positive
// return means "no deadline" — the pass runs unbounded. A nil policy is
// equivalent (the un-contexted Enrich / EnrichRepo entry points pass nil).
type EnrichDeadlinePolicy func(candidates int) time.Duration
// ContextEnricher is an optional interface a Provider MAY implement to
// receive a cancellation context for its per-repo pass. Providers that
// implement it are cancelled *cooperatively* at the Manager's per-repo
// deadline instead of being detached: the provider lands whatever work it
// has completed, marks the result Partial, and returns — so a deadline
// never discards finished enrichment and never leaks a goroutine that
// keeps mutating the graph after the pass was "abandoned".
//
// deadline (may be nil) sizes the pass's own context bound lazily, from the
// count of candidates left after already-stamped nodes are skipped — so the
// budget tracks the real remaining work rather than the whole-repo node
// count. The Manager keeps a generous outer ceiling on the context it passes
// in; the provider narrows it via deadline once selection is done.
type ContextEnricher interface {
EnrichRepoContext(ctx context.Context, g graph.Store, repoPrefix, repoRoot string, deadline EnrichDeadlinePolicy) (*EnrichResult, error)
}
// ReadinessProber is an optional interface a Provider MAY implement when its
// server answers `initialize` quickly but is not ready to serve semantic
// queries until a slower background load finishes — the Roslyn / MSBuild
// solution load a csharp-ls / OmniSharp pass sits behind. The Manager calls
// WaitReady BEFORE it starts the per-repo enrichment deadline, so that cold
// load does not consume the query budget (which otherwise elapses during the
// load, leaving the pass with zero useful edges). WaitReady must respect ctx
// (a bounded readiness budget) and should return promptly for a server that is
// already ready — providers whose servers serve queries immediately after
// initialize simply do not implement this interface. The returned error is
// best-effort: enrichment proceeds regardless, so a probe failure never blocks
// the pass.
type ReadinessProber interface {
WaitReady(ctx context.Context, repoRoot string) error
}
// ErrWorkspaceNotReady is returned by a ReadinessProber.WaitReady when the
// server never finished loading its workspace within the readiness budget. The
// Manager treats it specially: rather than run a futile sweep against a server
// that will answer every query empty (the "completed in 8s, 0 coverage"
// pathology), it records an honest not-ready state and skips the pass, leaving
// the repo un-enriched so a later cycle retries. Any other WaitReady error is
// best-effort and the pass proceeds.
var ErrWorkspaceNotReady = errors.New("semantic: workspace did not become ready within the readiness budget")
// EnrichResult contains statistics from an enrichment pass.
type EnrichResult struct {
Provider string `json:"provider"`
Language string `json:"language"`
EdgesConfirmed int `json:"edges_confirmed"`
EdgesRefuted int `json:"edges_refuted"`
EdgesAdded int `json:"edges_added"`
NodesEnriched int `json:"nodes_enriched"`
SymbolsCovered int `json:"symbols_covered"`
SymbolsTotal int `json:"symbols_total"`
CoveragePercent float64 `json:"coverage_percent"`
DurationMs int64 `json:"duration_ms"`
// HoverCandidates is the post-filter count of symbols this pass selected
// for hover enrichment — total symbols minus file/import nodes and minus
// the nodes a prior pass already stamped. Deadline budgeting scales the
// per-repo enrichment window on this number.
HoverCandidates int `json:"hover_candidates,omitempty"`
// BudgetSeconds is the per-repo enrichment deadline this pass derived
// lazily from HoverCandidates via the EnrichDeadlinePolicy, in seconds.
// The Manager surfaces it as the enrichment status deadline so the health
// surface reflects the actual (candidate-scaled) window, not the whole-repo
// estimate. 0 means the pass ran unbounded (nil policy / non-positive bound).
BudgetSeconds float64 `json:"budget_seconds,omitempty"`
// Partial reports that the pass was cut short (per-repo deadline /
// context cancellation) after landing some — but not all — of its
// work. The counters above reflect only what actually reached the
// graph. AbortReason carries the cause when Partial is true.
Partial bool `json:"partial,omitempty"`
AbortReason string `json:"abort_reason,omitempty"`
// BoundReason states why the add-phase stopped where it did, so a
// "completed" state that covered < 100% of its targets is never read as
// full coverage: "budget" (a deadline cut the pass), "cap" (the pass
// finished but some targets were skipped, e.g. no source position or an
// unservable file), or "completed_all" (every target visited).
BoundReason string `json:"bound_reason,omitempty"`
// ReferencesAddPass reports that the enrich pass added edges via
// textDocument/references (rather than call hierarchy) because the
// server advertised references but no call hierarchy — e.g. intelephense.
// Lets index_health distinguish this add mode from the hierarchy mode.
ReferencesAddPass bool `json:"references_add_pass,omitempty"`
// Degraded reports that the pass ran in a reduced mode — reference
// confirmation only, no hover / hierarchy sweep — because a server that
// needs a compilation database (clangd) found none. It is an intentional
// degradation, not a failure: the confirmed edges are trustworthy, but
// hover types and hierarchy edges are absent. DegradedReason carries why.
Degraded bool `json:"degraded,omitempty"`
DegradedReason string `json:"degraded_reason,omitempty"`
}
// Bounding reasons for the enrichment add-phase (EnrichResult.BoundReason /
// EnrichmentStatus.BoundReason).
const (
EnrichBoundBudget = "budget"
EnrichBoundCap = "cap"
EnrichBoundCompletedAll = "completed_all"
)
// Enrichment lifecycle states surfaced per (repo, provider) via
// Manager.EnrichmentStatuses — the health signal that lets an agent see
// an un-enriched or partially-enriched graph instead of assuming green.
const (
EnrichStateRunning = "running"
EnrichStateCompleted = "completed"
EnrichStatePartial = "partial" // deadline hit; completed work landed and is counted
EnrichStateAbandoned = "abandoned" // legacy provider detached at deadline; result discarded
EnrichStateFailed = "failed"
EnrichStateNotReady = "not_ready" // readiness prober timed out; sweep skipped, repo left for retry
)
// EnrichmentStatus reports the lifecycle state of one provider's per-repo
// enrichment pass. Exposed through index_health so consumers can tell a
// fully-enriched graph from one whose LSP pass was cut or abandoned.
type EnrichmentStatus struct {
Repo string `json:"repo"`
Provider string `json:"provider"`
Language string `json:"language,omitempty"`
State string `json:"state"`
// StartedAt is stamped when the pass enters EnrichStateRunning — the
// only state where "how long has this been going" is meaningful.
// Consumed by the daemon status surface to render a live elapsed
// time next to the per-repo deadline instead of a mute "in progress".
StartedAt time.Time `json:"started_at,omitempty"`
DeadlineSeconds float64 `json:"deadline_seconds,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
EdgesConfirmed int `json:"edges_confirmed"`
EdgesAdded int `json:"edges_added"`
NodesEnriched int `json:"nodes_enriched"`
// Add-phase coverage — the targets eligible for the hover/references
// pass, how many were visited, and why the pass stopped. Always emitted
// so a "completed" state that covered < 100% of targets is legible as a
// coverage sliver rather than trusted as full enrichment.
SymbolsTotal int `json:"symbols_total"`
SymbolsCovered int `json:"symbols_covered"`
CoveragePercent float64 `json:"coverage_percent"`
BoundReason string `json:"bound_reason,omitempty"`
// ReferencesAddPass marks that this provider added edges via
// textDocument/references (no call hierarchy) — the intelephense-style
// add mode. Distinguishes it from the call-hierarchy add mode.
ReferencesAddPass bool `json:"references_add_pass,omitempty"`
// Degraded marks that this provider ran in reference-confirmation-only
// mode because a needed compilation database was missing. Not a failure —
// State stays "completed" — but hover / hierarchy edges are absent, so
// index_health can flag it with the remediation. DegradedReason says why.
Degraded bool `json:"degraded,omitempty"`
DegradedReason string `json:"degraded_reason,omitempty"`
Detail string `json:"detail,omitempty"`
}
// ProviderStatus represents the current state of a semantic provider.
type ProviderStatus struct {
Name string `json:"name"`
Language string `json:"language"`
Status string `json:"status"` // "ready", "unavailable", "error"
CoveragePercent float64 `json:"coverage_percent,omitempty"`
LastResult *EnrichResult `json:"last_result,omitempty"`
}