20 KiB
Gortex
Code intelligence engine written in Go. Indexes repositories into an in-memory knowledge graph and exposes it via CLI and MCP Server.
Build & Test
go build -o gortex ./cmd/gortex/ # requires CGO (tree-sitter C bindings)
go test -race ./... # all test packages must pass
Codebase Overview
- Languages: go (primary)
- Entry point:
cmd/gortex/main.go - Source: 1,338 Go files (728 non-test) across the
cmd/andinternal/trees - Graph size: ~31k nodes, ~206k edges when the daemon indexes this repo
MANDATORY: Use Gortex's graph tools instead of Read/Grep/Glob
Gortex's workhorse tools are reachable two equivalent ways — over the registered MCP server, or via the equivalent gortex CLI verbs (gortex edit verify, gortex memory surface, gortex analyze, and gortex call <tool> for anything else; the verb reference is in docs/cli.md). Use whichever your harness has mounted — they are two front doors over the same handlers, and the daemon routes tool calls by name so the CLI reaches the full surface even under the core preset.
Either way, you MUST prefer graph queries over file reads on every task in this repo — search_symbols, find_usages, get_symbol_source, get_editing_context, smart_context, edit_symbol / edit_file / rename_symbol / batch_edit (or the matching gortex verbs). PreToolUse hooks deny Read / Grep / Glob against indexed source; the deny message names the right tool. The MCP server registers 180+ tools but by default publishes only a curated core preset (~34 dev-cycle workhorses) eagerly in tools/list, deferring the rest behind the tools_search discovery tool (the core/defer default — see docs/mcp.md). Opt into the full eager surface with GORTEX_TOOLS=full; GORTEX_LAZY_TOOLS=1 is the older all-or-nothing defer switch. The cross-project rule tables live in ~/.claude/CLAUDE.md — neither is restated here. This file carries only project-specific guidance.
Discovery (read once, then keep using)
- Graph schema —
gortex://schemaresource (node kinds, edge kinds, what each carries). - Analyzer rollups —
gortex://report,gortex://surprises,gortex://god-nodes,gortex://questions,gortex://audit. - Bootstrap state —
gortex://stats,gortex://index-health,gortex://workspace,gortex://repos,gortex://active-project.
LLM provider (powers ask and search_symbols assist: modes)
Selected via llm.provider in .gortex.yaml or ~/.config/gortex/config.yaml. The HTTP and subprocess providers are pure Go — available without -tags llama.
| Provider | Backend | Requires |
|---|---|---|
local (default) |
in-process llama.cpp | -tags llama build + llm.local.model (a .gguf path) |
anthropic |
Messages API | llm.anthropic.model + ANTHROPIC_API_KEY |
openai |
Chat Completions | llm.openai.model + OPENAI_API_KEY. Optional llm.openai.effort → reasoning_effort. |
azure |
Azure OpenAI Service | llm.azure.deployment + endpoint (llm.azure.endpoint or AZURE_OPENAI_ENDPOINT) + AZURE_OPENAI_API_KEY. Deployment-in-path + api-version query + api-key header; llm.azure.api_version defaults to a recent GA. Same json_schema structured output as openai. |
ollama |
Ollama daemon | llm.ollama.model (+ llm.ollama.host, default localhost:11434) |
claudecli |
claude CLI subprocess |
claude on $PATH (signed in once); optional llm.claudecli.model (sonnet/opus/…). Reuses the user's Claude Code subscription. |
codex |
OpenAI codex CLI subprocess |
codex on $PATH (signed in once); optional llm.codex.model. Runs codex exec in a read-only sandbox; reuses the user's Codex / ChatGPT sign-in. |
copilot |
GitHub Copilot CLI subprocess | copilot on $PATH (signed in via gh); optional llm.copilot.model. Runs copilot -p. |
cursor |
Cursor Agent CLI subprocess | cursor-agent on $PATH (signed in once); optional llm.cursor.model. Runs cursor-agent --output-format text -p. |
opencode |
opencode CLI subprocess | opencode on $PATH (signed in once); optional llm.opencode.model (provider/model). Runs opencode run. |
gemini |
Google Gemini generateContent REST |
llm.gemini.model (default gemini-2.5-pro) + GEMINI_API_KEY. Structured output via responseSchema (additionalProperties stripped — Gemini rejects it). |
bedrock |
AWS Bedrock Converse API (SigV4) | llm.bedrock.model_id (e.g. anthropic.claude-sonnet-4-20250514-v1:0) + AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY (+ optional AWS_SESSION_TOKEN for STS). Region defaults to us-east-1 (llm.bedrock.region). Structured output via forced respond tool. No AWS SDK dependency — SigV4 is implemented in ~100 LOC of stdlib. |
deepseek |
DeepSeek Chat Completions (OpenAI-compatible) | llm.deepseek.model (default deepseek-chat) + DEEPSEEK_API_KEY. Structured output uses response_format: json_object plus a schema hint in the system prompt — DeepSeek does not support strict json_schema. |
GORTEX_LLM_PROVIDER / GORTEX_LLM_MODEL / GORTEX_LLM_{CLAUDECLI,CODEX,COPILOT,CURSOR,OPENCODE}_BINARY / GORTEX_LLM_BEDROCK_REGION / GORTEX_LLM_AZURE_{ENDPOINT,DEPLOYMENT,API_VERSION} / GORTEX_LLM_EFFORT override the file config. GORTEX_LLM_MODEL targets the active provider's model field (Gemini → llm.gemini.model, Bedrock → llm.bedrock.model_id, DeepSeek → llm.deepseek.model, etc.). If the active provider can't construct (missing model / API key, local without -tags llama, claudecli / codex without claude / codex on $PATH, bedrock without AWS credentials), the daemon logs a warning and ask stays unregistered — fall through to direct tools.
Custom providers. Any OpenAI-compatible endpoint can be registered by name with gortex provider add/list/show/remove (writes providers.json next to the config; a repo-local .gortex/providers.json loads only under GORTEX_ALLOW_LOCAL_PROVIDERS=1). A registered name is selectable like any built-in via llm.provider / GORTEX_LLM_PROVIDER; entries carry base_url + model + optional api_key_env, schema_mode (json_schema/json_object/prompt), headers, and informational pricing.
Anthropic tuning. llm.anthropic.model accepts the tier sentinels claude-haiku / claude-sonnet / claude-opus, resolved to the newest live model id (per-auth cache + pinned fallback; override per tier via GORTEX_LLM_ANTHROPIC_{HAIKU,SONNET,OPUS}_MODEL). Opt-in llm.anthropic.prompt_caching (+ cache_ttl) marks the system prompt and structured-output tool as ephemeral cache breakpoints; llm.anthropic.thinking_mode (off/auto/manual/adaptive) drives extended thinking on freeform requests; llm.anthropic.effort / GORTEX_LLM_EFFORT sends a model-gated reasoning effort.
llm.routing (off by default) routes the ask agent to a cheaper or more capable model by graph-derived task complexity — set routing.enabled, routing.simple_model, routing.complex_model; the chosen model / complexity ride on the ask response.
search_symbols assist: modes: auto (default — skips LLM for identifier queries, expands NL queries), on (forces expansion+rerank), off (pure BM25), deep (adds a body-grounded verification pass; +1.5–4 s; quality is highly model-dependent — unreliable on 3B local models, fine on 7B+ or hosted).
Non-obvious capabilities worth knowing
compress_bodies: trueonread_file/get_symbol_source/get_editing_contextelides function bodies to stubs while keeping signatures + doc-comments + structure. ~30–40% of original tokens. 14 languages.- Overlay sessions (
overlay_push,overlay_list,overlay_drop,compare_with_overlay) let editor extensions push unsaved buffers as a per-session shadow graph — every subsequent tool call reads through it without mutating base. Bound to the MCP session lifecycle; idle TTL viaGORTEX_OVERLAY_IDLE_TTL(default 30m). - Speculative execution (
preview_edit,simulate_chain) takes an LSPWorkspaceEditand returns the graph diff + broken callers/implementors + impact rollup + suggested tests + (optional) LSP diagnostics — disk untouched.simulate_chainwithkeep: truepromotes the final state into a real overlay. - Change-contract pipeline (
change_contract,symbols_for_ranges) — one envelope every change source lowers into.change_contracttakes a WorkspaceEdit, a git diff range (source:diff base:…), an explicit symbol set, or file line-ranges, runs LOWER → PREDICT → EVALUATE (guards + architecture + event-boundary rule families) → SCORE → CLASSIFY → EMIT, and returns one verdict{allow|warn|refuse}with reasons, risk, averification_command, a checkablestop_condition, and anedit_strategy.lens:apifocuses it on public-surface / API drift;risk_gate:truerequires a TTL'd impact-review ack (ack:true, stored as a development memory) for load-bearing symbols.symbols_for_rangesis the standalone lowering primitive. The pre-write parse gate onedit_file/write_filerefuses an edit that would introduce new tree-sitter parse errors (override withallow_parse_errors);safe_delete_symbol propagate:truepatches surviving call sites;analyze kind=suggest_boundariesseeds anarchitecture:block from detected communities. - MCP 2026 Streamable HTTP at
POST /mcp—gortex serveralways mounts it;gortex daemon --http-addr <addr>opts the daemon in (non-localhost binds require--http-auth-token). - Session memory (
save_note,query_notes,distill_session) persists agent-authored notes per repo, auto-linked to symbols mentioned in the body. Notes survive daemon restarts and context compactions, scoped to the session's workspace. - Development memories (
store_memory,query_memories,surface_memories) — cross-session, symbol-linked durable knowledge that compounds the longer a team uses Gortex. Memories carrykind(invariant / constraint / convention / gotcha / decision / incident / reference),importance(1..5),confidence(0..1), and are surfaced proactively bysurface_memorieswhen their anchor symbols / files enter the agent's working set. - Artifacts — non-code knowledge files (DB schemas, API specs, ADRs, infra configs) declared in
.gortex.yamlartifacts:are indexed asartifactnodes;search_artifacts/get_artifactsurface them andEdgeReferenceslinks code to the spec it implements. - Code search beyond symbols —
search_textis a trigram-indexed literal / regex search (the grep replacement for non-symbol strings);search_astruns structural tree-sitter queries;analyze kind=sastis a 190-rule, CWE/OWASP-tagged security scan across 8 languages. - Push notifications — beyond
notifications/diagnostics, the server pushesgraph_invalidated(graph hot-reload),daemon_health,stale_refs, andworkspace_readiness.subscribe_*once per session instead of polling. get_architecture— one-call architectural snapshot (languages, communities, hotspots, processes); passresolutionfor a hierarchical symbol → file → package → service → system rollup.- Capability edges —
reads_env/executes_process/accesses_fieldare first-class traversable edges (in thewalk_graph/nav/graph_querysurface) synthesised post-resolution, so a supply-chain / least-privilege audit can ask "what reads $AWS_SECRET", "what shells out", "what writes this field" in one hop. - PR review, end-to-end —
gortex prstriages open pull requests from the graph (gortex prs <N>for one PR;--triage/--conflicts/--worktrees/--base/--format;gortex prs bundle), andgortex review [<base>|--diff] [--audience agent|human] [--post]reviews a diff. The MCP surface mirrors it:pr_risk/list_prs/get_pr_impact/triage_prs/conflicts_prs/suggest_reviewersscore and rank PRs against the graph, whilereview/review_pack/post_review/pr_review_context/suggested_review_questions/critique_review/suppress_findingdrive the review itself. - Multimodal + broad ingest — image files (
KindImageassets with format/dimensions/sha256) and PDF documents (per-page searchableKindDocnodes) are graph nodes; new first-class extractors cover Terraform/HCL cross-block references, Helm charts/templates, Ansible playbooks, .NET.sln/.csproj, MCP server configs, Quarto.qmd, Luau, COBOL paragraphs + JCL, and C/C++#definemacros. Grammar-less languages can register a regex fallback chunker (index.fallback_chunkers) or an external extractor plugin (index.extractor_plugins) from config — no fork.gortex db schema --postgres <dsn>ingests a live database's schema. analyzeis a 61-kind dispatcher — beyond the structural kinds, it now coversimpact(composite change-risk score),bottlenecks(interprocedural computation-bottleneck risk — cognitive complexity, loop depth, transitive/hidden-O(n^k) loop nesting across calls, unguarded recursion),health_score(per-symbol A–F grade),sast/named/unsafe_patterns(security),clusters,suggest_boundaries(Leiden-community-seeded architecture-layer suggestions),connectivity_health,tests_as_edges,synthesizers(framework-dispatch-synthesized edges, grouped by pass + provenance),resolution_outcomes(structured why-unresolved taxonomy),review(an idiomatic/correctness rulepack — NPE, thread-safety check-then-act, N+1, logic errors across Go + Python — with a graph-grounded false-positive-reduction pass), and more.
MANDATORY: Session memory — save, recall, distill
The save_note / query_notes / distill_session triplet is the agent's durable scratchpad. The graph remembers code; these tools remember why you made a call. Without them, every compaction erases hard-won context.
Three triggers — not suggestions:
- After a context compaction (or at session start in a touched repo) — call
distill_sessionfirst thing. Returns top symbols, pinned notes, decisions, and recent excerpts from prior sessions in this workspace. Use the digest to seed your mental model before reading any file. - At every decision point — call
save_note tags:"decision" body:"<what+why>"when you pick an approach, reject an alternative, discover a non-obvious constraint, or commit to an invariant. Mention the affected symbol/file by ID (pkg/foo.go::Bar) so the auto-linker attaches the note to the graph. Pin (pinned:true) anything that should survive the store cap. - Before editing a symbol you've touched before — call
query_notes symbol_id:"<id>". Prior decisions, bug-fix notes, or "do not change this without …" warnings ride on the symbol's note list and you should see them before re-deriving (or worse, reverting) past work.
What to save vs. skip:
- Save: decisions ("chose X over Y because Z"), non-obvious constraints, follow-ups ("revisit when …"), bug reproductions, surprising graph findings, partial-progress hand-offs.
- Skip: play-by-play of what you just did (the diff says it), code patterns derivable from the graph, anything already in CLAUDE.md.
Useful tags: decision, bug, follow-up, gotcha, invariant. decision-tagged notes are surfaced in their own section by distill_session.
MANDATORY: Development memories — store, query, surface
save_note is a per-session scratchpad; store_memory is the workspace-wide durable knowledge base. The two are complementary, not redundant:
save_note (session) |
store_memory (cross-session) |
|
|---|---|---|
| Scope | session_id | workspace-wide |
| Lifetime | survives compaction | survives daemon restarts, agent changes, team rotation |
| Audience | future-you in this session | every future agent in this workspace |
| Surfacing | distill_session (manual) |
surface_memories (proactive, ranked) |
| Right when | "remember this for the next 30 min" | "every agent touching Bar should know this" |
Three triggers — not suggestions:
- At task start, after
smart_context— callsurface_memories task:"<task>" symbol_ids:"<top hits from smart_context>". Returns memories ranked by anchor symbol overlap, file overlap, task-keyword hits, importance, pinning, recency, and confidence. Memories prefixed withmatch_reasons:["symbol:pkg/foo.go::Bar"]are direct evidence the memory applies to your working set. Ifsurface_memoriesreturns nothing, don't probe further. - When you discover a durable fact worth teaching the team — call
store_memory kind:"<invariant|gotcha|convention|decision|constraint|incident>" body:"<what+why>" symbol_ids:"pkg/foo.go::Bar" importance:5. Pin (pinned:true) anything load-bearing. Setkindhonestly:invariantmeans "violating this breaks the system",gotchameans "an agent will get this wrong without warning". Title (title:"...") the memory if the body is long — it becomes the headline. - When a memory is no longer true — call
store_memory id:"<new>" supersedes:"<old-id>" body:"<corrected fact>". The old memory stays in the store (for audit) but is hidden fromsurface_memoriesby default. Don't delete unless the original was wrong; supersession preserves history.
What to store vs. skip:
- Store: invariants ("Bar must hold the lock"), conventions ("this package never uses gob"), incident learnings ("once, doing X under Y crashed prod"), API contracts not enforced by types, debugging traps, cross-cutting decisions with non-obvious rationale.
- Skip: anything derivable from the code (the graph already knows), session-local play-by-play (use
save_note), CLAUDE.md content (it's already loaded), one-off observations with no actionable consequence.
Useful kinds and tags: invariant, constraint, convention, gotcha, decision, incident, reference. Tag liberally — query_memories tag:"<x>" is the primary lookup path when you don't know the anchor symbol.
Required workflow (every task on this repo)
These are not suggestions — run each step at the trigger.
- Confirm the daemon is up with
index_health(cheap liveness + scope). Callgraph_statsonly when you actually need node/edge counts orper_repoorientation — it returns a large payload and can block during warmup. - If
total_nodesis 0, callindex_repositorywith"."before anything else. - In multi-repo mode, call
get_active_projectto see scope; useset_active_projectto switch. - Open a non-trivial task with
smart_contextfor orientation. For a single known symbol or file, go straight tosearch_symbols/get_symbol_source— don't front-loadsmart_contextbefore every read. - Immediately after
smart_context, callsurface_memories task:"<task>" symbol_ids:"<top hits>"to pick up any cross-session invariants / gotchas / decisions anchored to your working set. Skipping this re-derives knowledge other agents have already recorded. - Before editing a file, call
get_editing_contexton it first. - Before changing any function signature, call
verify_changeto catch broken callers and interface implementors (cross-repo). - For any refactor, call
get_edit_planfor the dependency-ordered file list, thenbatch_editto apply atomically. - Verify with the project's real build/test (
go build/go test). Reservecheck_guardsfor guard-relevant changes andget_test_targets(includes cross-repo tests) to find the tests covering a substantive change — not mechanically after every edit. - Before committing, call
detect_changesfor scope anddiff_contextfor graph-enriched review. - When the task is done, if you used
smart_context, optionally callfeedback action: "record"to score which suggestions were useful / not needed / missing — it improves future context quality. If the task surfaced a durable invariant / decision / gotcha worth teaching the team, callstore_memoryso the next agent inherits the lesson.