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
316 lines
16 KiB
Go
316 lines
16 KiB
Go
package mcp
|
||
|
||
import "strings"
|
||
|
||
// analyzeKinds is the single source of truth for every `kind` the
|
||
// `analyze` dispatcher (handleAnalyze, tools_enhancements.go) accepts.
|
||
// It is the exact, sorted set of every `case` label in that switch —
|
||
// including the two that share one case (sast, hygiene) and every alias
|
||
// (review, domain, dbt_models, concepts, …). The AST-based anti-drift
|
||
// test in analyze_kinds_test.go asserts this slice equals the switch's
|
||
// case set exactly, so the two can never silently diverge.
|
||
//
|
||
// Keep it sorted: AnalyzeKinds returns a copy that callers may rely on
|
||
// being in stable order, and analyze_kinds_test.go asserts sortedness.
|
||
var analyzeKinds = []string{
|
||
"annotation_users",
|
||
"blame",
|
||
"bottlenecks",
|
||
"cgo_users",
|
||
"channel_ops",
|
||
"clusters",
|
||
"components",
|
||
"concepts",
|
||
"config_readers",
|
||
"connectivity_health",
|
||
"constructors_missing_fields",
|
||
"coverage",
|
||
"coverage_gaps",
|
||
"coverage_summary",
|
||
"cross_repo",
|
||
"cycles",
|
||
"dbt_models",
|
||
"dead_code",
|
||
"def_use",
|
||
"doc_staleness",
|
||
"domain",
|
||
"drupal_hooks",
|
||
"edge_audit",
|
||
"env_var_users",
|
||
"error_surface",
|
||
"event_emitters",
|
||
"external_calls",
|
||
"field_writers",
|
||
"fixes_history",
|
||
"goroutine_spawns",
|
||
"health_score",
|
||
"hotspots",
|
||
"hygiene",
|
||
"images",
|
||
"impact",
|
||
"indirect_mutations",
|
||
"k8s_resources",
|
||
"kcore",
|
||
"kustomize",
|
||
"log_events",
|
||
"louvain",
|
||
"models",
|
||
"named",
|
||
"orphan_tables",
|
||
"ownership",
|
||
"pagerank",
|
||
"pubsub",
|
||
"race_writes",
|
||
"ref_facts",
|
||
"releases",
|
||
"resolution_outcomes",
|
||
"retrieval_log",
|
||
"review",
|
||
"role",
|
||
"route_frameworks",
|
||
"routes",
|
||
"sast",
|
||
"scc",
|
||
"speculative",
|
||
"sql_call_sites",
|
||
"sql_rebuild",
|
||
"stale_code",
|
||
"stale_flags",
|
||
"string_emitters",
|
||
"suggest_boundaries",
|
||
"swiftui_views",
|
||
"synthesizers",
|
||
"temporal_orphans",
|
||
"temporal_verify",
|
||
"tests_as_edges",
|
||
"todos",
|
||
"uikit_classes",
|
||
"unclosed_channels",
|
||
"unreferenced_tables",
|
||
"unsafe_patterns",
|
||
"wasm_users",
|
||
"wcc",
|
||
"would_create_cycle",
|
||
}
|
||
|
||
// analyzeScopeAwareKinds is the set of analyze kinds whose result rows
|
||
// are genuinely narrowed by the uniform repo/project/workspace/scope
|
||
// overrides in v1. Three tiers populate it:
|
||
//
|
||
// - AUTO: kinds that obtain their working node set through the
|
||
// scopedNodes / scopedNodesByKinds / scopedNodeSlice accessors, so
|
||
// RepoAllow narrows them centrally with no per-handler code.
|
||
// - Per-row-filtered: kinds that read s.graph directly but gate every
|
||
// emitted row on analyzeNodeVisible (workspace ceiling + optional
|
||
// repo allow-set) — the three flagship kinds (dead_code, hotspots,
|
||
// cycles) plus the category-(a) edge-walk / graph-algorithm /
|
||
// framework kinds that prune their rows and re-tally their counts.
|
||
// - File-path-filtered: the category-(c) file/AST scans (sast, hygiene,
|
||
// review, domain, named, unsafe_patterns), already narrowed by
|
||
// resolveRepoFilter / buildASTTargets before the handler runs.
|
||
//
|
||
// handleAnalyze consults this set so that when a caller asks to narrow
|
||
// (resolved.RepoAllow != nil) but picks a kind that is NOT in the set,
|
||
// the response carries a `scope_note` disclosing that the kind is not
|
||
// repo-narrowed in v1 (a community / git-mining / per-id / synthesizer
|
||
// kind). This keeps the uniform `scope_applied` truthful while flagging
|
||
// the remaining long-tail no-op kinds — "no silent no-ops".
|
||
var analyzeScopeAwareKinds = map[string]bool{
|
||
// AUTO — narrowed centrally by the scoped-node accessors.
|
||
"todos": true,
|
||
"stale_code": true,
|
||
"ownership": true,
|
||
"coverage_gaps": true,
|
||
"stale_flags": true,
|
||
"cgo_users": true,
|
||
"wasm_users": true,
|
||
"orphan_tables": true,
|
||
"unreferenced_tables": true,
|
||
"coverage_summary": true,
|
||
"health_score": true,
|
||
"external_calls": true,
|
||
"k8s_resources": true,
|
||
"images": true,
|
||
"kustomize": true,
|
||
"dbt_models": true,
|
||
"role": true,
|
||
"constructors_missing_fields": true,
|
||
"impact": true,
|
||
"bottlenecks": true,
|
||
"connectivity_health": true,
|
||
// Tier-2 — bypass the accessors but filtered via analyzeNodeVisible.
|
||
"dead_code": true,
|
||
"hotspots": true,
|
||
"cycles": true,
|
||
// releases reads s.graph directly (NOT via the scoped-node accessors);
|
||
// per-row filtered via analyzeNodeVisible like the three flagship kinds.
|
||
"releases": true,
|
||
// Category (a) — per-row visibility filtered (workspace + repo allow-set).
|
||
"channel_ops": true,
|
||
"goroutine_spawns": true,
|
||
"field_writers": true,
|
||
"indirect_mutations": true,
|
||
"config_readers": true,
|
||
"env_var_users": true,
|
||
"event_emitters": true,
|
||
"pubsub": true,
|
||
"error_surface": true,
|
||
"speculative": true,
|
||
"ref_facts": true,
|
||
"annotation_users": true,
|
||
"race_writes": true,
|
||
"unclosed_channels": true,
|
||
"log_events": true,
|
||
"sql_call_sites": true,
|
||
"string_emitters": true,
|
||
"routes": true,
|
||
"route_frameworks": true,
|
||
"swiftui_views": true,
|
||
"uikit_classes": true,
|
||
"drupal_hooks": true,
|
||
"models": true,
|
||
"components": true,
|
||
"pagerank": true,
|
||
"louvain": true,
|
||
"kcore": true,
|
||
"wcc": true,
|
||
"scc": true,
|
||
"edge_audit": true,
|
||
"tests_as_edges": true,
|
||
"doc_staleness": true,
|
||
"temporal_orphans": true,
|
||
// Category (c) — file/AST scans, already narrowed via resolveRepoFilter/buildASTTargets.
|
||
"sast": true,
|
||
"hygiene": true,
|
||
"review": true,
|
||
"domain": true,
|
||
"named": true,
|
||
"unsafe_patterns": true,
|
||
}
|
||
|
||
// analyzeWorkspaceClampedKinds are the not-repo-narrowed analyze kinds
|
||
// (community detection + synthesizer enumeration) that now clamp their
|
||
// rows to the session workspace and self-disclose that via a body-
|
||
// visible `scope` block (workspaceScopeBlock). They are excluded from
|
||
// the _meta stampScopeNote path, whose text predates the clamp and would
|
||
// otherwise wrongly claim the result "may span the entire index / all
|
||
// workspaces".
|
||
var analyzeWorkspaceClampedKinds = map[string]bool{
|
||
"clusters": true,
|
||
"concepts": true,
|
||
"suggest_boundaries": true,
|
||
"synthesizers": true,
|
||
}
|
||
|
||
// AnalyzeKinds returns a defensive copy of the canonical analyze-kind
|
||
// set, in sorted order. Callers must not mutate the returned slice's
|
||
// backing array of the package-level source.
|
||
func AnalyzeKinds() []string {
|
||
out := make([]string, len(analyzeKinds))
|
||
copy(out, analyzeKinds)
|
||
return out
|
||
}
|
||
|
||
// analyzeKindsCSV returns the canonical analyze kinds comma-joined, for
|
||
// interpolation into tool descriptions and the dispatcher's error
|
||
// strings so those lists always match the switch exactly.
|
||
func analyzeKindsCSV() string {
|
||
return strings.Join(analyzeKinds, ", ")
|
||
}
|
||
|
||
// analyzeKindDescriptions is a one-line summary for every analyze kind,
|
||
// so `gortex analyze kinds` is self-documenting and a user need not reach
|
||
// for docs/source to learn what a kind does. It must stay in lock-step
|
||
// with analyzeKinds: the anti-drift test in analyze_kinds_test.go asserts
|
||
// every kind has a non-empty description here and that no description key
|
||
// is an orphan (a kind that no longer exists). Keep each value to a single
|
||
// terse line — the CLI aligns them into a two-column reference listing.
|
||
var analyzeKindDescriptions = map[string]string{
|
||
"annotation_users": "EdgeAnnotated rollup — sites using an annotation/decorator; scope with id/name (e.g. @Deprecated)",
|
||
"blame": "Stamp meta.last_authored on every blame-eligible node from git blame",
|
||
"bottlenecks": "Rank functions by computation-bottleneck risk — complexity, loop depth, transitive/hidden-O(n^k) nesting, unguarded recursion",
|
||
"cgo_users": "Files that import C (uses_cgo edge)",
|
||
"channel_ops": "Channels grouped by sends/receives — producer/consumer mismatches",
|
||
"clusters": "Community detection as an analyzer (algorithm=leiden/louvain/spectral, min_size)",
|
||
"components": "UI parent↔child fan-in/out from render edges (JSX/TSX + Phoenix HEEx)",
|
||
"concepts": "Concept clusters mined over the graph",
|
||
"config_readers": "config_key nodes grouped by reads_config edges; name filter",
|
||
"connectivity_health": "Graph-extraction quality — isolated nodes, leaf/source/sink counts, dead-weight by file",
|
||
"constructors_missing_fields": "Constructors that leave one or more struct fields unset",
|
||
"coverage": "Stamp meta.coverage_pct on executable symbols from a cover profile",
|
||
"coverage_gaps": "Symbols inside [min_pct, max_pct) — undertested code",
|
||
"coverage_summary": "Per-directory coverage rollup (avg, covered, partial, uncovered)",
|
||
"cross_repo": "Repo-boundary-crossing call/implements/extends edges grouped by repo pair + relation",
|
||
"cycles": "Dependency cycles via Tarjan's SCC, with severity classification",
|
||
"dbt_models": "dbt/SQLMesh models, seeds, snapshots, sources with column count + lineage fan-in/out",
|
||
"dead_code": "Symbols with zero incoming edges (excludes entry points, tests, exports)",
|
||
"def_use": "Per-function reaching-definition def→use chains over the on-demand CFG (pairs with get_cfg)",
|
||
"doc_staleness": "Docs/decisions whose content→code links have gone dangling or pending — stale references",
|
||
"domain": "Results of pluggable TOML domain-extractor rules — project-specific node/edge kinds",
|
||
"drupal_hooks": "Detected Drupal hook implementations grouped by the hook they implement",
|
||
"edge_audit": "Graph-completeness / edge-sanity diagnostic — missing or suspect edges",
|
||
"env_var_users": "reads_config edges restricted to env-var keys, grouped by variable",
|
||
"error_surface": "Function/method nodes with their throws targets — refactor blast radius",
|
||
"event_emitters": "Event/log/metric emit sites grouped by emit edges; level/name filters",
|
||
"external_calls": "Stdlib/module-cache attribution — module rollup of call/symbol counts",
|
||
"field_writers": "Mutability hotspots — fields ranked by write edges; id scopes to one field",
|
||
"fixes_history": "Mine git for bug-fix commits and rank fix-prone files",
|
||
"goroutine_spawns": "Spawn edges grouped by spawned target + mode (goroutine/async/promise)",
|
||
"health_score": "Composite per-symbol health 0-100 + A–F grade (coverage/complexity/recency/churn)",
|
||
"hotspots": "Over-coupled symbols ranked by fan-in, fan-out, and community crossings",
|
||
"hygiene": "SAST hygiene scan (alias of sast) — CWE/OWASP-tagged rules across 8 languages",
|
||
"images": "Container images (Dockerfile FROM / K8s container.image) with consumer count",
|
||
"impact": "Composite 0-100 change-impact score + risk label over 5 axes (PageRank, reach, complexity, co-change, span)",
|
||
"indirect_mutations": "Fields mutated indirectly via a method/sibling call on the receiver — surfaces the via method",
|
||
"k8s_resources": "Kubernetes resource fan-out (depends_on/configures/mounts/exposes/uses_env); k8s_kind/namespace filters",
|
||
"kcore": "k-core decomposition — the densely-connected graph core by k-degree (the infrastructure every layer leans on)",
|
||
"kustomize": "Kustomize overlay tree with base/resource fan-out; dir filter",
|
||
"log_events": "Logging-call sites surfaced as events",
|
||
"louvain": "Louvain community partitioning (typically more granular than the Leiden clusters kind)",
|
||
"models": "ORM model→table edges (gorm/SQLAlchemy/Django/ActiveRecord/JPA/TypeORM/Ecto); orm/table/model filters",
|
||
"named": "Run a named query bundle — built-ins cover sql-injection/xss/ssrf/hardcoded-secrets/…; repo bundles from .gortex.yaml",
|
||
"orphan_tables": "Tables queried but missing a migration that provides them",
|
||
"ownership": "Per-author rollup with symbol/file counts and oldest/newest timestamps",
|
||
"pagerank": "PageRank centrality — symbols ranked by random-walk authority",
|
||
"pubsub": "Pub/sub topics with publishers + subscribers (NATS/Kafka/RabbitMQ/Redis/EventEmitter/Socket.IO)",
|
||
"race_writes": "Concurrent-write race detection",
|
||
"ref_facts": "Resolved-reference facts — each reference edge + the provenance tier that resolved it",
|
||
"releases": "Stamp meta.added_in on file nodes from git tags",
|
||
"resolution_outcomes": "Classify unresolved call/ref edges by why the resolver gave up (ambiguous/out-of-scope/cross-language/stub/no-def)",
|
||
"retrieval_log": "Mine the retrieval query log — zero-result queries + per-tool latency/result-size rollups for recall tuning",
|
||
"review": "Idiomatic/correctness rulepack (NPE, thread-safety check-then-act, N+1, logic-error; Go+Python) — the engine behind gortex review",
|
||
"role": "Per-symbol architectural-role classification",
|
||
"route_frameworks": "Registered structural route passes + route-contract node count per framework",
|
||
"routes": "Handler↔route pairs from route edges (HTTP/gRPC/WS/GraphQL/topic); method/path/type filters",
|
||
"sast": "Bandit-parity SAST — 190+ rules across 8 languages, CWE/OWASP tagged; severity/cwe/tag/detector filters",
|
||
"scc": "Strongly connected components of the directed graph",
|
||
"speculative": "Audit best-guess speculative dynamic-dispatch edges grouped by shape with a candidate histogram",
|
||
"sql_call_sites": "Query edges grouped by calling symbol with table read/write split",
|
||
"sql_rebuild": "Re-derive the SQL table layer from the string-literal registry",
|
||
"stale_code": "Symbols whose last-author timestamp is older than older_than days",
|
||
"stale_flags": "Feature flags whose every toggling caller is older than older_than days",
|
||
"string_emitters": "String-literal emission sites surfaced as events",
|
||
"suggest_boundaries": "Seed an architecture: block from detected Leiden communities — a ready-to-paste .gortex.yaml layer map",
|
||
"swiftui_views": "SwiftUI types grouped by classified role (component/app_entry)",
|
||
"synthesizers": "Framework-dispatch-synthesized edges grouped by the pass that produced them",
|
||
"temporal_orphans": "Temporal call-graph integrity — broken dispatches, handler-less signals/queries, undispatched activities/workflows",
|
||
"temporal_verify": "LLM cleaning pass over Temporal dispatch edges (requires a configured LLM provider)",
|
||
"tests_as_edges": "View over the test→code edge layer; group_by symbol or test",
|
||
"todos": "TODO/FIXME/HACK/XXX/NOTE nodes; filter by tag/assignee/ticket",
|
||
"uikit_classes": "UIKit types grouped by classified role (view_controller/view/cell)",
|
||
"unclosed_channels": "Channels that are opened but never closed",
|
||
"unreferenced_tables": "Tables provided by a migration but with zero queries against them",
|
||
"unsafe_patterns": "Panic-prone / undefined-behavior primitive scan across all languages",
|
||
"wasm_users": "Files that use #[wasm_bindgen] (uses_wasm_bindgen edge)",
|
||
"wcc": "Weakly connected components of the graph",
|
||
"would_create_cycle": "Pre-flight check before adding a dependency — would the new edge close a cycle?",
|
||
}
|
||
|
||
// AnalyzeKindDescription returns the one-line summary for an analyze kind,
|
||
// or the empty string if kind is not a canonical analyze kind. Every kind
|
||
// in AnalyzeKinds() is guaranteed (by analyze_kinds_test.go) to have a
|
||
// non-empty description.
|
||
func AnalyzeKindDescription(kind string) string {
|
||
return analyzeKindDescriptions[kind]
|
||
}
|