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
220 lines
12 KiB
Go
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"`
|
|
}
|