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

1103 lines
52 KiB
Go

package graph
type EdgeKind string
const (
EdgeImports EdgeKind = "imports"
// EdgeReExports links a file to a binding it forwards from another
// module — the barrel-file pattern `export { x } from "mod"` /
// `export * from "mod"` / `export * as ns from "mod"`. Direction:
// file → unresolved::import::<path>[::<original>]. Distinct from
// EdgeImports so a dependency walk can tell "this file re-exports
// mod's surface" (a transitive forwarding hop) from "this file
// consumes mod". The renamed name rides on Edge.Alias.
EdgeReExports EdgeKind = "re_exports"
// EdgeContains links a file node to its non-symbol children — import
// nodes today, and a natural home for future side-band kinds
// (todos, fixtures) that "belong to" a file without being defined
// by it. EdgeDefines is the wrong fit for these because the file
// does not semantically *define* an import; it *contains* the
// import statement. Splitting the kinds lets walkers that want
// "real definitions" follow EdgeDefines and walkers that want the
// full file neighbourhood union both. The disk-backed
// GetFileSubGraph relies on this union to fetch every file
// neighbour in one pass.
EdgeContains EdgeKind = "contains"
EdgeDefines EdgeKind = "defines"
EdgeCalls EdgeKind = "calls"
EdgeInstantiates EdgeKind = "instantiates"
EdgeImplements EdgeKind = "implements"
EdgeExtends EdgeKind = "extends"
EdgeReferences EdgeKind = "references"
// EdgeMotivates links knowledge — a KindRationale, KindArtifact, or
// content KindDoc — to the code symbol it explains or justifies: the
// causal "why this exists" relation, distinct from the structural
// mention EdgeReferences. Source: rationale projection and the
// doc->code linking pass.
EdgeMotivates EdgeKind = "motivates"
EdgeMemberOf EdgeKind = "member_of"
EdgeProvides EdgeKind = "provides"
EdgeConsumes EdgeKind = "consumes"
// EdgeMatches links a consumer contract node to the provider contract
// node it resolves to (e.g. consumer http:GET:/v1/tucks → provider
// http:GET:/v1/tucks, across repos). Traversals bridge service
// boundaries by hopping Consumer → EdgeConsumes⁻¹ → consumer-contract
// → EdgeMatches → provider-contract → EdgeProvides⁻¹ → handler.
EdgeMatches EdgeKind = "matches"
// EdgeBridges links a KindContractBridge group node to one of the
// KindContract nodes participating in the group. Direction:
// bridge → contract. Meta["side"] ∈ provider | consumer | both
// ("both" when the provider and consumer contracts share one ID
// and therefore collapse into a single contract node in the
// graph). Emitted by the contract-bridge materialisation pass in
// ReconcileContractEdges alongside EdgeMatches; the whole bridge
// generation is evicted and re-derived on every reconcile so the
// edges never outlive the contracts they group.
EdgeBridges EdgeKind = "bridges"
// EdgeAnnotated links a symbol to a synthetic annotation node
// representing a decorator / annotation / attribute applied to it
// (e.g. @Component, @Test, @Deprecated, #[derive(Debug)],
// [Authorize], @app.route("/x"), @Published). The annotation node's
// ID follows the convention "annotation::<lang>::<name>"; the edge's
// Meta["args"] carries the verbatim argument text (truncated) when
// the annotation has parentheses.
//
// Framework dispatch (NestJS @Get, Laravel middleware, Symfony
// AsEventListener, Spring @Bean, FastAPI @app.route, …) continues
// to flow through the contracts/dispatch layer with
// EdgeProvides/EdgeConsumes — EdgeAnnotated runs in parallel as a
// queryable record of the raw decorator. This lets agents answer
// "find all @Deprecated" / "find all controllers" with one graph
// hop without duplicating contract logic.
EdgeAnnotated EdgeKind = "annotated"
// EdgeTests links a test function/method to a non-test symbol it
// exercises. Computed at index time as a post-extraction pass:
// every call edge whose source is a test function (Meta["is_test"]
// = true) and whose target is non-test produces an EdgeTests pair
// alongside the existing EdgeCalls. Lets agents answer
// "which tests cover X" with a single reverse-edge walk and lets
// `get_untested_symbols` filter public symbols whose inverse-EdgeTests
// set is empty.
//
// Test detection is by file naming convention plus per-language
// fn-name conventions (Test*/Benchmark*/Fuzz* in Go, test_* /
// Test* in Python, *_test.dart, etc.). Override per-repo via
// .gortex.yaml::test_patterns when the project uses an unusual
// layout — false positives are an acknowledged tradeoff for
// keeping the heuristic dependency-free.
EdgeTests EdgeKind = "tests"
// EdgeReads / EdgeWrites split EdgeReferences for value-side uses
// of variables and fields. LHS of an assignment / op= / ++ / --
// emits EdgeWrites; every other identifier or selector use emits
// EdgeReads. EdgeReferences is reserved for type references
// (`var x SomeType` references the type SomeType) so the resolver
// can keep distinguishing the two by target node kind.
//
// Together with KindField, these let agents ask "which functions
// write to this field" — impossible with the previous "any use is
// a reference" model. Implemented per-language as the Go/TS/
// Python (priority wave) and Rust/Java (second wave) extractors
// learn to walk assignment AST nodes.
EdgeReads EdgeKind = "reads"
EdgeWrites EdgeKind = "writes"
// EdgeThrows links a function/method to an error or exception
// type that can propagate from it. Per language:
//
// go function returns an error type → edge to that type
// (custom *MyError type or external::error sentinel for
// the built-in error interface).
// python `raise <Exception>` AST nodes inside the body.
// java method `throws` clause.
// swift `throws` / `rethrows` keyword on the function decl.
// rust return type contains Result<_, E> → edge to E.
//
// Lets agents ask "what error types can propagate from here" with
// a single forward walk and lets `analyze kind: "error_surface"`
// summarise every public function's error contract without
// re-deriving it from source.
EdgeThrows EdgeKind = "throws"
// Coverage edges: each is produced only when the relevant
// index.coverage.<domain>.enabled gate is set; the registry is
// permissive (DefaultOriginFor handles unknown kinds via the
// confidence-score fallback).
// EdgeParamOf links a KindParam node to its owning function or
// method. Distinct from EdgeMemberOf (which is for fields of
// types). Always ast_resolved by construction.
EdgeParamOf EdgeKind = "param_of"
// EdgeReturns links a function/method to a type it returns. Multi-
// return Go functions emit one edge per result. Confidence reflects
// the resolver: ast_inferred when the type is named in source,
// promoted to ast_resolved / lsp_resolved by the semantic layer.
EdgeReturns EdgeKind = "returns"
// EdgeTypedAs binds a variable, parameter, field, or constant to
// its declared type. Lets traversals answer "find all values of
// type T". Distinct from EdgeReferences, which is broader.
EdgeTypedAs EdgeKind = "typed_as"
// EdgeCaptures links a closure node to an outer binding it closes
// over.
EdgeCaptures EdgeKind = "captures"
// EdgeSpawns links a caller to a function it launches
// asynchronously (goroutine, async/await, Promise, worker pool).
// Emitted in addition to the corresponding EdgeCalls so synchronous
// reachability queries can scope by edge kind. Meta["mode"] ∈
// goroutine|async|promise|worker_pool.
EdgeSpawns EdgeKind = "spawns"
// EdgeSends / EdgeRecvs link a function to a channel-typed
// variable for channel I/O. The channel's element type is reachable
// via the variable's EdgeTypedAs edge.
EdgeSends EdgeKind = "sends"
EdgeRecvs EdgeKind = "recvs"
// EdgeQueries links a function to a database table it queries
// against. Default origin text_matched from string-literal SQL;
// promoted to ast_resolved when an ORM mapping is recognized.
EdgeQueries EdgeKind = "queries"
// EdgeReadsCol / EdgeWritesCol provide column-level resolution
// when the SQL parser can extract it. Falls back to table-level
// EdgeQueries when columns can't be resolved.
EdgeReadsCol EdgeKind = "reads_col"
EdgeWritesCol EdgeKind = "writes_col"
// EdgeReadsConfig / EdgeWritesConfig link a function to a config
// key it reads or writes (env var, viper key, k8s configmap entry,
// struct-tag binding).
EdgeReadsConfig EdgeKind = "reads_config"
EdgeWritesConfig EdgeKind = "writes_config"
// Capability edges — first-class, traversable typed edges for the
// supply-chain / least-privilege questions "what reads the
// environment", "what shells out", "what touches this field". They
// are synthesised after resolution (see synthesizeCapabilityEdges)
// from edges the language extractors already emit, so they stay in
// sync without per-language work, while giving a single edge kind to
// walk instead of joining through the config / dataflow layers.
//
// EdgeReadsEnv links a function/method to the environment variable it
// reads. The target is the typed env-var node (KindConfigKey, id
// cfg::env::<NAME>) shared with infra EdgeUsesEnv declarations, so
// "which code reads $AWS_SECRET" is one reverse walk. Emitted parallel
// to every EdgeReadsConfig whose target is a cfg::env:: key.
EdgeReadsEnv EdgeKind = "reads_env"
// EdgeExecutesProcess links a function/method to a process it spawns
// (os/exec, subprocess, child_process, system, Open3, Command::new,
// Runtime.exec, …). The target is a typed process node (KindString,
// id string::process::<mechanism>) so an audit can enumerate every
// function that shells out. Synthesised from EdgeCalls whose callee is
// a known process-execution API.
EdgeExecutesProcess EdgeKind = "executes_process"
// EdgeAccessesField links a function/method to a struct field / class
// property it reads or writes — the union of EdgeReads and EdgeWrites
// restricted to KindField targets, as one traversable capability edge.
// Meta["access"] ∈ read|write. Synthesised from EdgeReads / EdgeWrites
// edges that target a KindField node.
EdgeAccessesField EdgeKind = "accesses_field"
// EdgeTogglesFlag links a function to a feature flag it checks or
// toggles. Meta["op"] ∈ read|write|register.
EdgeTogglesFlag EdgeKind = "toggles_flag"
// EdgeEmits links a function to a log/metric/trace event it emits.
// Meta carries level (for logs), unit (for metrics), and label keys.
//
// EdgeEmits is also the publish side of the event pub/sub layer:
// when a function publishes to a message broker (NATS / Kafka /
// RabbitMQ / Redis pub-sub) or an in-process EventEmitter / Socket.IO
// channel, the edge targets a KindEvent node with
// Meta["event_kind"]="pubsub". The subscribe side is EdgeListensOn.
// Meta["transport"] (nats|kafka|rabbitmq|redis|socketio|eventemitter|
// unknown) and Meta["method"] (the matched call name) ride on the
// edge so `analyze kind=pubsub` can group by broker without
// re-deriving it.
EdgeEmits EdgeKind = "emits"
// EdgeListensOn links a subscriber/consumer function to the
// KindEvent topic node it listens on — the read side of the event
// pub/sub layer that parallels EdgeEmits' publish side. Emitted for
// message-broker subscriptions (NATS Subscribe / Kafka consumer
// subscribe / RabbitMQ Consume / Redis (P)Subscribe) and in-process
// listener registration (EventEmitter.on / Socket.IO socket.on).
// The target node always carries Meta["event_kind"]="pubsub";
// Meta["transport"] and Meta["method"] ride on the edge. Origin:
// ast_inferred — detection is a method-name + string-literal-topic
// heuristic, not a type-checked fact, so it shares the tier the
// observability extractor uses for EdgeEmits.
EdgeListensOn EdgeKind = "listens_on"
// EdgeGeneratedBy links a generated file to its schema source
// (.proto, .graphql, openapi.yaml, etc.). Detected via comment
// markers (// Code generated …), conventional adjacency, or
// go:generate directives.
EdgeGeneratedBy EdgeKind = "generated_by"
// EdgeDependsOnModule links a file/package/import to a KindModule
// node. One edge per import statement; aggregable to package-level.
EdgeDependsOnModule EdgeKind = "depends_on_module"
// EdgePackageWorkspaceMember links a package-manager workspace root
// to one of its member packages. A package-manager workspace is a
// root manifest that owns a set of member packages — an npm/yarn
// root package.json with a "workspaces" array, a pnpm
// pnpm-workspace.yaml with a `packages:` list, or a Cargo root
// Cargo.toml with `[workspace] members`. The From endpoint is the
// synthetic workspace-root node (`pkgws::<ecosystem>:<root-dir>`);
// the To endpoint is the member package's manifest file node.
// Glob member patterns (`packages/*`) are expanded against the
// filesystem at index time, so one edge is emitted per resolved
// member directory.
//
// This is the package-manager's notion of a workspace and is
// unrelated to Node.WorkspaceID, which is Gortex's hard graph
// boundary for grouping whole repositories. Origin: ast_resolved —
// membership is read structurally from the manifest text.
EdgePackageWorkspaceMember EdgeKind = "package_workspace_member"
// EdgeOwns links a team to a file or directory. Sourced from
// CODEOWNERS. Directory entries materialize per-file.
EdgeOwns EdgeKind = "owns"
// EdgeAuthored links a person/team to a node they last touched.
// Meta carries commit and timestamp. People are stored as
// KindTeam nodes with Meta["kind"]="person".
EdgeAuthored EdgeKind = "authored"
// EdgeCoveredBy links a function/method to a test that exercises
// it, with coverage_pct attached in Meta. Directional inverse of
// EdgeTests, distinguished by carrying the coverage metric.
EdgeCoveredBy EdgeKind = "covered_by"
// EdgeAliases links a type alias `type X = Y` to its underlying
// type. Distinct from EdgeExtends (`type X Y` newtype) — agents
// distinguish by edge kind to compute correct blast radius.
EdgeAliases EdgeKind = "aliases"
// EdgeComposes links a type to an embedded/composed/mixed-in type
// (Go struct embedding, Rust trait bounds, Python multiple
// inheritance). Distinct from EdgeExtends (newtype/inheritance/
// interface extension).
EdgeComposes EdgeKind = "composes"
// EdgeOverrides links a method to the parent-class or interface
// method it overrides. Distinct from EdgeImplements (interface
// implementation) and EdgeExtends (class hierarchy) — those are
// type-level relationships; EdgeOverrides is method-level. Emitted
// alongside EdgeExtends/EdgeImplements when a child type declares
// a method that shadows a parent method with the same signature.
// Origin tier:
// lsp_resolved when the LSP server confirmed the override (e.g.
// tsserver / rust-analyzer / clangd report it via type hierarchy
// or its workspace symbol provider).
// ast_resolved when the parent type is in the same compilation
// unit and the indexer can prove the method exists in both.
// ast_inferred when the override is heuristic (same name only,
// parent type unknown).
EdgeOverrides EdgeKind = "overrides"
// EdgeLicensedAs links a file to its SPDX license. Sourced from
// the file's SPDX-License-Identifier header, falling back to the
// repo-level LICENSE file.
EdgeLicensedAs EdgeKind = "licensed_as"
// CPG-lite dataflow primitives. Together they form the data-
// dependence layer Gortex layers on top of the call graph: agents
// can answer "where does this value flow?" with a single graph
// walk instead of hand-tracing source.
//
// Local-binding ID convention. For language extractors that emit
// dataflow without materialising a graph node per local variable,
// edges target a synthetic ID of the form:
//
// <ownerID>#local:<name>@+<offsetFromOwnerStartLine>
//
// where ownerID is the enclosing function/method/closure node
// and the offset is the local's 1-based line minus the owner's
// declaration line (leading `+` flags the value as a relative
// offset). The offset-based ID keeps locals stable across edits
// that shift the function as a whole — only edits inside the
// function above a binding shift that binding's ID. Each ID is
// also materialised as a KindLocal node linked to the owner
// via EdgeMemberOf; the search index excludes KindLocal so
// these per-binding nodes don't pollute name lookups.
// These IDs are valid edge endpoints — BFS traverses them — but
// no graph node is created, keeping search results free of
// every transient binding in every function body.
//
// EdgeValueFlow links a value-producing position to a value-
// consuming position within the same function/method/closure
// body. Captures intra-procedural data-dependence: assignment
// LHS↔RHS, range source↔induction var, return value↔function
// symbol. Both endpoints are inside the same enclosing
// function so the edge is fully resolved at extraction time.
// Origin: ast_resolved by construction.
// EdgeHandlesRoute links a handler function/method to the
// KindContract node that represents its route. Emitted alongside
// EdgeProvides whenever the contract type is HTTP/gRPC/WS/GraphQL/
// topic — the framework layer that an agent asks "which symbol
// serves /v1/users/:id?" about. The narrower edge kind lets
// `analyze kind=routes` walk it without pulling in the broader
// EdgeProvides graph (which also covers env keys, OpenAPI specs,
// migrations, DI tokens, …). Origin tier mirrors the underlying
// extractor; defaults to ast_resolved (structural by construction).
EdgeHandlesRoute EdgeKind = "handles_route"
// EdgeProducesTopic links a producer function/method to the
// KindTopic node it publishes onto — the framework-layer message-
// broker analogue of EdgeHandlesRoute for HTTP. Emitted by the
// contracts pairing pass whenever a topic contract (Kafka /
// RabbitMQ / NATS / Redis pub-sub) matches at least one consumer
// across the same workspace; unpaired producers remain as
// orphan ContractTopic records. The narrower edge kind lets
// `analyze kind=event_emitters` and architecture queries walk
// the producer→topic surface without pulling in the broader
// EdgeProvides graph. The target KindTopic node carries
// Meta["broker"] and Meta["name"] so traversals don't need to
// re-parse the synthetic node ID. Origin: ast_resolved —
// structural by construction once the matcher has paired the
// underlying topic contract.
EdgeProducesTopic EdgeKind = "produces_topic"
// EdgeConsumesTopic is the read side of EdgeProducesTopic —
// links a consumer function/method to the KindTopic node it
// subscribes to. Same broker families, same pairing pass, same
// ast_resolved tier. Multi-consumer fanout falls out naturally:
// one producer paired with N consumers on the same (broker,
// topic) bucket yields N EdgeConsumesTopic edges plus one
// EdgeProducesTopic edge, all sharing the same topic node.
EdgeConsumesTopic EdgeKind = "consumes_topic"
// EdgeModelsTable links an ORM model type/class to the KindTable
// node it persists. Per language:
//
// go struct with `gorm:"..."` tags or a `TableName() string`
// method on the receiver type
// python SQLAlchemy / Django class with __tablename__ /
// class Meta: db_table
// ruby class X < ApplicationRecord (or ActiveRecord::Base),
// with optional self.table_name = "..."
// java @Entity class, optional @Table(name="...")
// typescript TypeORM @Entity({ name: "..." }) class
//
// Lets agents ask "which table does this class write?" and "which
// model owns the users table?" with a single graph hop instead of
// joining through migrations and raw SQL.
EdgeModelsTable EdgeKind = "models_table"
// EdgeRendersChild links a parent component (function / method /
// class) to a child component it renders inside its JSX/TSX/Vue/
// Svelte template body. Captures the component dependency tree so
// agents can ask "what renders <DataTable />?" or "what does
// <CheckoutPage /> reach into?" without grepping for the
// component name.
//
// Detection is heuristic: capital-first-letter element names
// inside JSX expressions are treated as component references and
// resolved through normal name resolution. Lowercase names map to
// HTML/SVG primitives and are skipped — the edge graph would be
// noise otherwise.
EdgeRendersChild EdgeKind = "renders_child"
EdgeValueFlow EdgeKind = "value_flow"
// EdgeArgOf links an argument expression at a call site to the
// callee's parameter — the inter-procedural binding produced
// by passing a value across a function boundary. Direction:
// caller-side argument source → callee parameter. The
// resolver lifts the unresolved callee target the same way
// EdgeCalls is lifted; a follow-up indexer pass rewrites the
// edge target from the callee function ID to the param node ID
// at the recorded position (Meta["arg_position"]).
EdgeArgOf EdgeKind = "arg_of"
// EdgeReturnsTo links a callee function/method to the receiving
// binding at a call site (`x := f(...)` produces returns_to(f, x)).
// Direction: callee → assignment LHS. Stored at extraction time
// with From = enclosing-caller ID and Meta["returns_to_call"] +
// Meta["call_line"] as a placeholder; a follow-up indexer pass
// rewrites From to the resolved callee ID by joining against the
// EdgeCalls edge from the same caller+line.
EdgeReturnsTo EdgeKind = "returns_to"
// Infrastructure-graph edges. Materialised by the K8s
// manifest, Kustomize, and Dockerfile extractors.
//
// EdgeConfigures links a workload Resource (Pod / Deployment /
// StatefulSet / DaemonSet / Job / CronJob) to a ConfigMap or
// Secret it pulls configuration from via `envFrom:`,
// `valueFrom: configMapKeyRef`, or `valueFrom: secretKeyRef`.
// Direction: consumer → provider (workload → ConfigMap/Secret).
// Origin: ast_resolved by construction.
EdgeConfigures EdgeKind = "configures"
// EdgeMounts links a workload Resource to a volume source —
// ConfigMap, Secret, or PersistentVolumeClaim — referenced from
// `spec.volumes`. Direction: workload → volume source. Distinct
// from EdgeConfigures (which is env-side wiring); EdgeMounts is
// the filesystem-side wiring. Origin: ast_resolved.
EdgeMounts EdgeKind = "mounts"
// EdgeExposes links a Resource or Image to a port surface it
// publishes. Source: K8s Service `spec.ports[]`, Deployment/Pod
// `containerPorts[]`, Ingress rules, Dockerfile `EXPOSE`. Target:
// a synthetic port node with ID `port::<proto>::<n>` (proto ∈
// tcp|udp|http|https|grpc). Origin: ast_resolved.
EdgeExposes EdgeKind = "exposes"
// EdgeDependsOn captures runtime/build dependencies between
// infrastructure entities — Ingress → Service backend, Service
// → Pod (selector), Kustomization → base Kustomization,
// Dockerfile stage → parent stage / external base Image,
// Resource → Image. Direction: dependent → dependency. Origin:
// ast_resolved.
//
// Also the model-lineage edge for the dbt / SQLMesh graph layer:
// a dbt model → the models / seeds / snapshots it `ref()`s and the
// sources it `source()`s, and a SQLMesh model → the models its
// body reads via FROM / JOIN. Direction is the same (dependent →
// dependency), so a downstream-impact walk over EdgeDependsOn
// answers "what breaks if this model changes?" uniformly across
// infra manifests and the transformation DAG. Edge Meta["link"]
// disambiguates (ref|source|from).
EdgeDependsOn EdgeKind = "depends_on"
// EdgeUsesEnv links a Resource (workload) or Image (Dockerfile
// stage) to a KindConfigKey representing an environment variable
// it declares it needs at runtime. Direction: container surface
// → config_key. The config_key ID convention `cfg::env::<NAME>`
// matches what Go / Python / Node extractors emit for
// `os.Getenv("NAME")` (and equivalents) so the cross-ref between
// infra-side declaration and code-side consumption materialises
// for free via shared node IDs. Origin: ast_resolved.
EdgeUsesEnv EdgeKind = "uses_env"
// EdgeSimilarTo links two function/method nodes whose bodies are
// near-duplicates ("clones"). Materialised by the graph-wide
// MinHash + LSH clone-detection pass: each function body is reduced
// to a 64-slot MinHash signature at index time (stored on
// Node.Meta["clone_sig"]), LSH banding produces candidate pairs,
// and a Jaccard-similarity threshold filter keeps the true clones.
// Emitted symmetrically — both fA→fB and fB→fA — so "what are the
// clones of X" is a single out-edge walk from either endpoint.
// Meta["similarity"] carries the estimated Jaccard score (0..1);
// Confidence mirrors it. Origin: ast_inferred — the relationship is
// a statistical estimate over normalised tokens, not a structural
// fact. Pairs with dead-code analysis to surface "dead duplicates
// of live code" — a near-duplicate of a live function that itself
// has zero callers.
EdgeSimilarTo EdgeKind = "similar_to"
// EdgeSemanticallyRelated links two function/method nodes that are
// plausibly semantically related without being near-duplicate
// clones. Where EdgeSimilarTo captures only high direct token
// overlap, this edge is materialised by a graph-diffusion smoothing
// pass that runs after clone detection: it walks the EdgeSimilarTo
// graph and, for a pair (A,C) joined through a shared similar
// neighbour B, derives a diffused score from similarity(A,B) and
// similarity(B,C). Transitive relatedness — "A is similar to B and
// B is similar to C, so A and C are related" — surfaces pairs whose
// direct Jaccard is below the clone threshold. Emitted symmetrically
// — both fA→fB and fB→fA — so "what is X related to" is a single
// out-edge walk from either endpoint. Meta["similarity"] carries the
// diffused score (0..1); Confidence mirrors it. Origin: ast_inferred
// — like EdgeSimilarTo the relationship is a statistical estimate
// over normalised tokens, here additionally smoothed across the
// similarity graph, not a structural fact. A pair that already has a
// direct EdgeSimilarTo edge is never re-emitted as semantically
// related — the two edge kinds partition cleanly.
EdgeSemanticallyRelated EdgeKind = "semantically_related"
// EdgeCoChange links two KindFile nodes that git history shows are
// repeatedly committed together — "logical coupling". This is the
// relationship the import graph cannot see: a handler and its
// test, a struct and the serializer that mirrors it, a schema and
// its migration are coupled even when neither imports the other.
// Materialised by the cochange enrichment pass (`gortex enrich
// cochange`, or lazily by the find_co_changing_symbols tool):
// `git log --name-only` is mined for files that co-occur in a
// commit, weighted by a cosine association score over per-file
// commit-touch counts. Emitted symmetrically — both fA→fB and
// fB→fA — so "what co-changes with X" is a single out-edge walk
// from either endpoint. Meta["count"] carries the number of
// commits touching both files; Meta["score"] (and Confidence)
// carry the association strength (0..1). Origin: ast_inferred —
// the relationship is a statistical estimate over git history,
// not a structural fact.
EdgeCoChange EdgeKind = "co_change"
// Cross-repo edge kinds. Materialised by the resolver's
// detectCrossRepoEdges pass: whenever a calls / implements / extends
// edge has a From node and a To node in two different repos, a
// parallel edge of the matching cross_repo_* kind is emitted
// alongside the base edge (the base edge keeps its kind and also
// gets Edge.CrossRepo set). The narrower kinds let
// `analyze kind=cross_repo` walk only the repo-boundary-crossing
// subset of the call / type-hierarchy graph without re-deriving the
// boundary test from node RepoPrefix on every edge. From/To/FilePath/
// Line/Origin/Confidence mirror the base edge; Origin is therefore
// inherited (lsp_resolved for an LSP-confirmed call, ast_resolved
// for a structural implements/extends, etc.). Idempotent —
// graph.AddEdge dedupes by edgeKey — and incremental-safe — EvictFile
// removes a node's edges in both directions, so a stale parallel
// edge cannot survive a reindex of either endpoint's file.
EdgeCrossRepoCalls EdgeKind = "cross_repo_calls"
EdgeCrossRepoImplements EdgeKind = "cross_repo_implements"
EdgeCrossRepoExtends EdgeKind = "cross_repo_extends"
// EdgeCrossRepoMotivates parallels EdgeMotivates across a repo
// boundary — knowledge in one repo (e.g. an org-wide ADR / docs repo)
// explaining code in another tracked repo.
EdgeCrossRepoMotivates EdgeKind = "cross_repo_motivates"
)
// CrossRepoKindFor maps a base edge kind to its parallel cross-repo
// variant. Only the call / type-hierarchy kinds named in M3 have a
// variant; every other kind returns ok == false. The mapping is the
// single source of truth for both the resolver's detectCrossRepoEdges
// pass and `analyze kind=cross_repo`.
func CrossRepoKindFor(base EdgeKind) (EdgeKind, bool) {
switch base {
case EdgeCalls:
return EdgeCrossRepoCalls, true
case EdgeImplements:
return EdgeCrossRepoImplements, true
case EdgeExtends:
return EdgeCrossRepoExtends, true
case EdgeMotivates:
return EdgeCrossRepoMotivates, true
}
return "", false
}
// BaseKindForCrossRepo is the inverse of CrossRepoKindFor: it maps a
// cross-repo edge kind back to the base relation it parallels. Returns
// ok == false for any non-cross-repo kind.
func BaseKindForCrossRepo(cr EdgeKind) (EdgeKind, bool) {
switch cr {
case EdgeCrossRepoCalls:
return EdgeCalls, true
case EdgeCrossRepoImplements:
return EdgeImplements, true
case EdgeCrossRepoExtends:
return EdgeExtends, true
case EdgeCrossRepoMotivates:
return EdgeMotivates, true
}
return "", false
}
// BaseKindsForCrossRepo returns the set of base edge kinds that have a
// parallel cross_repo_* variant. The slice is the single source of
// truth for callers (DetectCrossRepoEdges, the CrossRepoCandidates
// storage capability) that need the kind list without iterating
// CrossRepoKindFor over every edge.
func BaseKindsForCrossRepo() []EdgeKind {
return []EdgeKind{EdgeCalls, EdgeImplements, EdgeExtends, EdgeMotivates}
}
type Edge struct {
From string `json:"from"`
To string `json:"to"`
Kind EdgeKind `json:"kind"`
FilePath string `json:"file_path"`
Line int `json:"line"`
// Confidence is the numeric score (0..1). Kept on the in-memory
// struct for internal filtering (min_tier, etc.) but excluded from
// JSON — agents act on ConfidenceLabel, and the float adds ~15
// chars to every edge in large graph responses.
Confidence float64 `json:"-"`
ConfidenceLabel string `json:"confidence_label,omitempty"`
Origin string `json:"origin,omitempty"`
// Tier is the coarse provenance label derived from Origin
// (ast / lsp / heuristic). It is the agent-facing summary used by
// retrieval UIs and competitor-parity columns (tokensave's
// edges.resolved_by). Populated by enrichSubGraphEdges and the
// dataflow encoders; empty by default on the in-memory edge.
Tier string `json:"tier,omitempty"`
CrossRepo bool `json:"cross_repo,omitempty"`
// Context is the per-reference role a usage plays relative to the
// target symbol — parameter_type, return_type, field, value, type,
// attribute, generic_arg, or call. Empty on the stored edge; populated
// on demand by RefContextOf when find_usages classifies a usage, so
// agents can filter references by how they use the symbol
// (`find_usages context:"parameter_type"`). Not part of the edge
// identity / dedup key.
Context string `json:"context,omitempty"`
// ReturnUsage is how the call site consumes the callee's return
// value — discarded, assigned, partially_ignored, returned,
// goroutine, deferred, argument, or condition. Like Context it is
// empty on the stored edge and populated on demand from
// Meta[MetaReturnUsage] (stamped by the language extractors at
// call-edge creation) when find_usages renders a usage, so agents
// can ask "who actually uses the return?" before changing a return
// signature. Not part of the edge identity / dedup key.
ReturnUsage string `json:"return_usage,omitempty"`
// Via is the human-readable provenance of a synthesized edge — the
// framework-dispatch synthesizer that produced it (Swift↔ObjC bridge,
// observer channel, React setState, …), derived from Meta["via"]. Empty
// on directly-extracted edges; populated on demand by enrichSubGraphEdges
// so traversal tools can render a "— via X" suffix. Not part of the edge
// identity / dedup key.
Via string `json:"via,omitempty"`
// Alias is the renamed name carried by a per-binding import or a
// re-export edge — the local name for `import { x as alias }` and the
// exported name for `export { x as alias } from`. Empty when the binding
// is not renamed (the local/exported name equals the upstream name) and
// on every non-import edge. The edge's To still targets the upstream
// (original) name, so the alias is the only place the renamed identifier
// is recorded. Not part of the edge identity / dedup key.
Alias string `json:"alias,omitempty"`
// Meta is intentionally excluded from JSON. It holds internal
// instrumentation (semantic_source, provider hints, etc.) that agents
// don't consume but that adds measurable bytes to every edge in
// responses returning hundreds of call-graph edges. Internal callers
// can still read/write the field; external MCP consumers don't see it.
Meta map[string]any `json:"-"`
}
// ViaLabelFor maps a synthesizer Meta["via"] value to a human-readable
// provenance label for the "— via X" suffix on traversal output. Unmapped
// values pass through verbatim; an empty via yields an empty label.
func ViaLabelFor(via string) string {
switch via {
case "":
return ""
case "swift.objc.bridge":
return "Swift↔ObjC bridge"
case "observer.channel":
return "observer channel"
case "closure.collection":
return "closure collection"
case "react.setstate":
return "React setState"
case "flutter.setstate":
return "Flutter setState"
case "kmp.expect-actual":
return "KMP expect/actual"
case "rn.native.pair":
return "RN native pairing"
case "rn.bridge", "rn.native":
return "RN bridge"
case "event.channel":
return "event channel"
case "store-factory":
return "store factory"
default:
return via
}
}
// IdentityHash returns the edge's provenance-bearing identity: a stable
// 128-bit hash over (From, To, Kind, FilePath, Line, Origin). It is
// deliberately broader than the logical edgeKey the adjacency-list
// dedup index uses — that key omits Origin so a provenance upgrade
// replaces an edge in place instead of creating a parallel one. The
// identity hash, by contrast, treats Origin as load-bearing: two edges
// that differ only in Origin have different identities, so a silent
// provenance change is observable as an identity change.
//
// Changing an in-graph edge's Origin should therefore be modeled as a
// retirement of the old identity and creation of a new one — see
// Graph.SetEdgeProvenance, the only sanctioned mutation path.
func (e *Edge) IdentityHash() edgeHash {
return hashEdgeIdentity(keyOf(e), e.Origin)
}
// Edge.Origin values — call-graph confidence tiers, highest → lowest. Use
// MeetsMinTier / OriginRank to compare.
//
// - lsp_resolved: Compiler-grade. LSP, go/types, or SCIP confirms that this
// edge's target is the precise symbol being referenced. Safe to rely on
// for refactors.
// - lsp_dispatch: Interface → implementation dispatch resolved by a
// semantic provider. One step less direct than a literal target match.
// - ast_resolved: Tree-sitter / AST extraction found a unique target in
// the same compilation unit. No type system involved, but structurally
// unambiguous.
// - ast_inferred: Heuristic resolution using type info we extracted from
// the AST. Not compiler-verified.
// - text_matched: Name-only match. The weakest tier — could be a false
// positive.
const (
OriginLSPResolved = "lsp_resolved"
OriginLSPDispatch = "lsp_dispatch"
OriginASTResolved = "ast_resolved"
OriginASTInferred = "ast_inferred"
OriginTextMatched = "text_matched"
// OriginSpeculative ranks strictly below text_matched: a best-guess
// dynamic-dispatch edge (obj[name]() / getattr / decorator registry) that
// is present-but-hidden-by-default. Always carries Meta[MetaSpeculative]=true
// so queries exclude it unless the caller opts in.
OriginSpeculative = "speculative"
)
// MetaSpeculative is the edge Meta key marking a speculative (best-guess)
// edge. It is the single source of truth for default-exclusion: any
// edge-returning surface drops these unless the caller opts in.
const MetaSpeculative = "speculative"
// MetaReparsePendingEnrichment is a KindFile-node Meta key (not an edge key)
// set by the indexer when a live watch re-parse resolved the file's references
// without re-running semantic enrichment — so the file's edges may sit below
// the tier the enrichment pass would mint. find_usages / get_callers read it
// to flag their default text_matched suppression as re-verification-pending,
// making a hidden-but-real usage diagnosable instead of silently dropped.
const MetaReparsePendingEnrichment = "reparse_pending_enrichment"
// OriginRank returns a numeric rank for origin comparison. Higher = more
// confident. Unknown or empty origin returns 0 so it sorts below all known
// tiers; filters treat it as "untagged" and fall back to legacy inference.
func OriginRank(origin string) int {
switch origin {
case OriginLSPResolved:
return 6
case OriginLSPDispatch:
return 5
case OriginASTResolved:
return 4
case OriginASTInferred:
return 3
case OriginTextMatched:
return 2
case OriginSpeculative:
return 1
}
return 0
}
// IsSpeculative reports whether the edge is a best-guess speculative edge
// (excluded from default query results).
func (e *Edge) IsSpeculative() bool {
if e == nil || e.Meta == nil {
return false
}
v, _ := e.Meta[MetaSpeculative].(bool)
return v
}
// MeetsMinTier returns true when origin is at least as confident as minTier.
// Empty minTier always passes (no filter). Empty origin fails any non-empty
// filter — callers wanting legacy fallback should first backfill via
// DefaultOriginFor.
func MeetsMinTier(origin, minTier string) bool {
if minTier == "" {
return true
}
return OriginRank(origin) >= OriginRank(minTier)
}
// ResolvedBy maps an Origin tier to a coarse provenance label used by
// agent retrieval UIs and competitor parity rows (cf. tokensave's
// `edges.resolved_by` column). The mapping collapses the five Origin
// tiers to three buckets:
//
// - "lsp": compiler-grade evidence (OriginLSPResolved / Dispatch)
// - "ast": structurally-unambiguous AST extraction
// - "heuristic": name- or score-derived guess (ast_inferred / text_matched)
//
// Empty origin or unknown values return "heuristic" — a safe fallback
// for back-compat with graphs produced before Origin was stamped.
func ResolvedBy(origin string) string {
switch origin {
case OriginLSPResolved, OriginLSPDispatch:
return "lsp"
case OriginASTResolved:
return "ast"
case OriginASTInferred, OriginTextMatched:
return "heuristic"
}
return "heuristic"
}
// DefaultOriginFor derives an origin tier for edges that don't have Origin
// set yet (edges from providers not updated to set Origin directly, or from
// indexes produced before this field existed). Uses edge kind, confidence
// score, and semantic_source meta as fallback signals. Never returns empty.
func DefaultOriginFor(kind EdgeKind, confidence float64, semanticSource string) string {
if semanticSource != "" {
if kind == EdgeImplements || kind == EdgeOverrides {
return OriginLSPDispatch
}
return OriginLSPResolved
}
// Structural AST edges are unambiguous by construction.
switch kind {
case EdgeDefines, EdgeImports, EdgeContains, EdgeExtends, EdgeMemberOf,
EdgeImplements, EdgeProvides, EdgeConsumes, EdgeMatches, EdgeBridges,
// Coverage structural edges: the extractor produces an
// unambiguous source→target binding for each, so they share
// the AST-resolved tier.
EdgeParamOf, EdgeAliases, EdgeComposes, EdgeOverrides, EdgeLicensedAs,
EdgeOwns, EdgeAuthored, EdgeGeneratedBy, EdgeDependsOnModule,
EdgePackageWorkspaceMember,
EdgeCaptures,
// Framework-layer edges. Each is materialised by an extractor
// that already proved the relationship (handler → route via
// the contracts pipeline, model → table via the ORM detector,
// parent → child via JSX walking) so they ride at ast_resolved.
EdgeHandlesRoute, EdgeProducesTopic, EdgeConsumesTopic,
EdgeModelsTable, EdgeRendersChild,
// Dataflow edges. EdgeValueFlow is intra-procedural and
// fully resolved at extraction. EdgeArgOf / EdgeReturnsTo
// inherit ast_resolved once the post-resolution pass has
// landed both ends; the dispatcher here just stamps the
// default tier so freshly emitted edges classify cleanly.
EdgeValueFlow, EdgeArgOf, EdgeReturnsTo,
// Infrastructure-graph edges. Each is materialised by an
// extractor (K8s/Kustomize/Dockerfile) that resolves the
// relationship structurally from the manifest text.
EdgeConfigures, EdgeMounts, EdgeExposes, EdgeDependsOn, EdgeUsesEnv,
// Capability edges synthesised from already-structural edges
// (reads_config / reads / writes) ride at the same tier.
EdgeReadsEnv, EdgeAccessesField,
// Cross-repo type-hierarchy edges parallel the structural
// implements/extends edges, so they ride at the same tier.
// EdgeCrossRepoCalls is intentionally excluded — it parallels
// the resolution-derived `calls` edge and inherits that tier
// via the confidence fallback below.
EdgeCrossRepoImplements, EdgeCrossRepoExtends:
return OriginASTResolved
}
// Resolution-derived edges fall back to confidence score.
switch {
case confidence >= 0.9:
return OriginASTResolved
case confidence >= 0.5:
return OriginASTInferred
}
return OriginTextMatched
}
// EdgeTierScore maps an edge's Origin provenance tier to a 0..1 confidence
// weight, falling back to the edge kind when Origin is unstamped. It is the one
// shared provenance→confidence mapping consumed by the dataflow and callpath
// path-confidence rankers, so a path's confidence means the same thing across
// flow_between, taint_paths and trace_path.
func EdgeTierScore(origin string, kind EdgeKind) float64 {
switch origin {
case OriginLSPResolved:
return 1
case OriginLSPDispatch:
return 0.95
case OriginASTResolved:
return 0.9
case OriginASTInferred:
return 0.7
case OriginTextMatched:
return 0.4
}
// Unstamped: fall back to the kind tier. value_flow is intra-procedural
// and cheap to ground; arg_of / returns_to are cross-call and start lower
// until the resolver lifts them.
switch kind {
case EdgeValueFlow:
return 0.85
case EdgeArgOf, EdgeReturnsTo:
return 0.7
}
return 0.5
}
// Provenance-attenuation weights for graph centrality (HITS /
// PageRank) and the rerank provenance signal. Code intelligence
// enriched by LSP providers (gopls, clangd, tsserver) materialises a
// dense layer of framework-wiring and interface-dispatch edges;
// counting every such edge at full weight inflates the apparent
// centrality of utility and framework code over genuine domain
// authorities. Attenuating the abundant lsp tier — and the weak,
// possibly-spurious text-matched tier — relative to the
// structurally-unambiguous ast_resolved baseline rebalances authority
// toward real load-bearing code. ProvenanceWeightMin / Max bound the
// returned band so consumers can normalise onto [0,1].
const (
ProvenanceWeightMin = 0.5 // text_matched: weakest, possible false positive
ProvenanceWeightMax = 1.0 // ast_resolved: the trusted centrality baseline
provWeightLSP = 0.6 // lsp_resolved / lsp_dispatch: abundant, attenuate
provWeightASTInferred = 0.8 // heuristic type inference: slight discount
)
// ProvenanceWeight returns the centrality weight for an edge based on
// its resolution provenance, in [ProvenanceWeightMin, ProvenanceWeightMax].
// Edges with an unset Origin are backfilled via DefaultOriginFor so
// pre-Origin indexes weight consistently. A nil edge weights at the
// trusted baseline.
func ProvenanceWeight(e *Edge) float64 {
if e == nil {
return ProvenanceWeightMax
}
origin := e.Origin
if origin == "" {
sem, _ := e.Meta["semantic_source"].(string)
origin = DefaultOriginFor(e.Kind, e.Confidence, sem)
}
switch origin {
case OriginLSPResolved, OriginLSPDispatch:
return provWeightLSP
case OriginASTResolved:
return ProvenanceWeightMax
case OriginASTInferred:
return provWeightASTInferred
case OriginTextMatched:
return ProvenanceWeightMin
}
return provWeightASTInferred
}
// Reference-context labels — the role a usage plays relative to the
// symbol it references. Mirrors graphify's REFERENCE_CONTEXTS so
// find_usages can filter "show me only the places this type is used as a
// parameter / return type / field …".
const (
RefContextParameterType = "parameter_type"
RefContextReturnType = "return_type"
RefContextField = "field"
RefContextValue = "value"
RefContextType = "type"
RefContextAttribute = "attribute"
RefContextGenericArg = "generic_arg"
RefContextCall = "call"
// RefContextImport marks an import / re-export statement that names the
// symbol (`from x import name`, `import {name} from …`, `export {name}
// from …`). LSP reference sets include these lines; find_usages can
// isolate or exclude them via context:"import".
RefContextImport = "import"
// RefContextCallback marks a function referenced as a value to be invoked
// later — registered as a callback / handler rather than called directly.
RefContextCallback = "callback"
// RefContextTemplate marks a reference that originates from a markup
// template render site (`<Child />` in a Vue/Svelte/Astro component) rather
// than from code. find_usages can then separate "rendered here" usages from
// code-level references and show each positioned render location.
RefContextTemplate = "template"
// RefContextInherit marks a reference that names a supertype / conformed
// protocol in a type declaration's inheritance clause (`class X: Base,
// Proto`). It lets find_usages isolate "used as a base / conformance"
// references from incidental type uses.
RefContextInherit = "inherit"
// RefContextCast marks a reference where the target type is used in a cast
// or type test (`x as Foo`, `x as? Foo`, `x is Foo`) rather than a
// declaration.
RefContextCast = "cast"
// RefContextStaticAccess marks a reference that reaches a type's static
// member / nested type through the type name (`Foo.shared`,
// `Foo.Constant`) rather than constructing or declaring it.
RefContextStaticAccess = "static_access"
)
// RefContextOf classifies the reference context of an edge given the kind
// of its origin (usage-site) node. An extractor-stamped
// Meta["ref_context"] always wins (it lets a language extractor mark a
// context the structure alone can't reveal — e.g. generic_arg). Otherwise
// the context is derived from the edge kind plus the origin node kind,
// which Gortex's edge model already distinguishes:
//
// - returns → return_type
// - typed_as → parameter_type (from a param), field (from a field),
// value (from a variable/constant/local), else type
// - annotated → attribute
// - reads / writes → value
// - references / implements / extends / composes / aliases / overrides → type
// - calls / instantiates → call
//
// Returns "" for edge kinds that carry no reference context.
func RefContextOf(e *Edge, fromKind NodeKind) string {
if e == nil {
return ""
}
if e.Meta != nil {
if c, _ := e.Meta["ref_context"].(string); c != "" {
return c
}
// A bound callback registration is a value-use of the function, not a
// type reference — classify it so find_usages can filter callbacks.
if v, _ := e.Meta["via"].(string); v == "callback_registration" {
return RefContextCallback
}
// A markup render site (`<Child />`) is a template usage, not a code
// type reference — classify it so find_usages can isolate render sites.
if t, _ := e.Meta["template"].(bool); t {
return RefContextTemplate
}
}
switch e.Kind {
case EdgeReturns:
return RefContextReturnType
case EdgeTypedAs:
switch fromKind {
case KindParam:
return RefContextParameterType
case KindField:
return RefContextField
case KindVariable, KindConstant, KindLocal:
return RefContextValue
}
return RefContextType
case EdgeAnnotated:
return RefContextAttribute
case EdgeReads, EdgeWrites:
return RefContextValue
case EdgeReferences, EdgeImplements, EdgeExtends, EdgeComposes, EdgeAliases, EdgeOverrides:
return RefContextType
case EdgeCalls, EdgeInstantiates:
return RefContextCall
case EdgeImports, EdgeReExports:
return RefContextImport
}
return ""
}
// Return-usage labels — how a call site consumes the callee's return
// value. Stamped by the language extractors as Meta[MetaReturnUsage]
// on EdgeCalls edges at creation time, so the classification persists
// through every backend and survives reindexing.
const (
// ReturnUsageDiscarded: the call is a bare expression statement (or
// every result is bound to a blank sink, e.g. Go `_, _ = f()`).
ReturnUsageDiscarded = "discarded"
// ReturnUsageAssigned: the result is bound to variables or fields.
ReturnUsageAssigned = "assigned"
// ReturnUsagePartiallyIgnored: a multi-result call where some (but
// not all) results are bound to blank sinks — Go `v, _ := f()`.
ReturnUsagePartiallyIgnored = "partially_ignored"
// ReturnUsageReturned: the call sits inside a return statement (or
// the implicit-return tail position of an expression-bodied lambda
// / Rust function / Ruby method).
ReturnUsageReturned = "returned"
// ReturnUsageGoroutine: the call is launched via a Go `go`
// statement — the return value is unobservable.
ReturnUsageGoroutine = "goroutine"
// ReturnUsageDeferred: the call is a Go `defer` statement.
ReturnUsageDeferred = "deferred"
// ReturnUsageArgument: the result feeds another call — either as a
// literal argument or as the receiver of a chained call.
ReturnUsageArgument = "argument"
// ReturnUsageCondition: the call sits inside an if / for / while /
// switch condition.
ReturnUsageCondition = "condition"
)
// MetaReturnUsage is the edge Meta key carrying the return-usage label
// of a call site. Single source of truth for the extractors that stamp
// it and the read paths (find_usages, verify_change) that surface it.
const MetaReturnUsage = "return_usage"
// ReturnUsageOf returns the extractor-stamped return-usage label of a
// call edge, or "" when the edge carries none (non-call edges, call
// shapes the classifier could not place).
func ReturnUsageOf(e *Edge) string {
if e == nil || e.Meta == nil {
return ""
}
s, _ := e.Meta[MetaReturnUsage].(string)
return s
}
// ConfidenceLabelFor returns EXTRACTED, INFERRED, or AMBIGUOUS for an edge
// based on its kind and confidence value.
//
// Kept for back-compat; new code should prefer Origin tiers (OriginRank /
// MeetsMinTier) which distinguish LSP-grade from AST-grade evidence.
func ConfidenceLabelFor(kind EdgeKind, confidence float64) string {
// Structural edges from AST are always extracted.
switch kind {
case EdgeDefines, EdgeImports, EdgeContains, EdgeExtends, EdgeMemberOf, EdgeImplements,
EdgeProvides, EdgeConsumes, EdgeMatches, EdgeBridges,
EdgeParamOf, EdgeAliases, EdgeComposes, EdgeOverrides, EdgeLicensedAs,
EdgeOwns, EdgeAuthored, EdgeGeneratedBy, EdgeDependsOnModule,
EdgePackageWorkspaceMember,
EdgeCaptures,
// Framework-layer edges. Each is materialised by an extractor
// that already proved the relationship (handler → route via
// the contracts pipeline, model → table via the ORM detector,
// parent → child via JSX walking) so they ride at ast_resolved.
EdgeHandlesRoute, EdgeProducesTopic, EdgeConsumesTopic,
EdgeModelsTable, EdgeRendersChild,
EdgeValueFlow, EdgeArgOf, EdgeReturnsTo,
// Infrastructure-graph edges (K8s / Kustomize / Dockerfile).
EdgeConfigures, EdgeMounts, EdgeExposes, EdgeDependsOn, EdgeUsesEnv,
// Capability edges synthesised from structural edges.
EdgeReadsEnv, EdgeAccessesField,
// Cross-repo type-hierarchy edges parallel structural
// implements/extends; EdgeCrossRepoCalls falls through to the
// confidence-score classifier like the base `calls` edge.
EdgeCrossRepoImplements, EdgeCrossRepoExtends:
return "EXTRACTED"
}
// Resolution-derived edges: classify by confidence score.
switch {
case confidence >= 0.9:
return "EXTRACTED"
case confidence >= 0.5:
return "INFERRED"
case confidence > 0:
return "AMBIGUOUS"
default:
// confidence == 0 means resolved without type info.
return "INFERRED"
}
}