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
192 lines
15 KiB
Go
192 lines
15 KiB
Go
package mcp
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// The guide is the single on-demand home for Gortex reference detail that
|
|
// used to be pre-paid in the installed CLAUDE.md section: the LLM-provider
|
|
// matrix, the non-obvious capabilities catalog, the token-economy deep-dive,
|
|
// the MCP resources list, and pointers into the analyze / search_ast
|
|
// catalogs (which live authoritatively in `kind:"help"` / `detector:"help"`
|
|
// and the gortex://schema resource). Nothing here is deleted from the
|
|
// product — it stops being ambient and becomes reachable via the
|
|
// `gortex://guide` MCP resource and the `gortex guide [topic]` CLI verb.
|
|
//
|
|
// Single-home invariants enforced by tests:
|
|
// - the LLM-provider matrix lives ONLY here (not in the CLAUDE.md sections);
|
|
// - the analyze kind catalog lives in `kind:"help"` / gortex://schema and is
|
|
// rendered here from the same source — never re-inlined into CLAUDE.md;
|
|
// - the wire-FORMAT deep-dive lives ONLY in the server instructions
|
|
// (sharedParamLegend) — the guide points at it, it is not repeated here.
|
|
|
|
// guideProviders is the LLM-provider matrix + the `ask` delegation surface,
|
|
// relocated verbatim out of the installed rule block. The 13-provider
|
|
// enumeration is the single-home marker asserted by the gates.
|
|
const guideProviders = `## LLM providers (powers ` + "`ask`" + ` and ` + "`search_symbols assist:`" + ` modes)
|
|
|
|
Selected via ` + "`llm.provider`" + ` in ` + "`.gortex.yaml`" + ` or ` + "`~/.gortex/config.yaml`" + `, or ` + "`GORTEX_LLM_PROVIDER`" + ` / ` + "`GORTEX_LLM_MODEL`" + `. One of:
|
|
` + "`local`" + ` / ` + "`anthropic`" + ` / ` + "`openai`" + ` / ` + "`azure`" + ` / ` + "`ollama`" + ` / ` + "`claudecli`" + ` / ` + "`codex`" + ` / ` + "`copilot`" + ` / ` + "`cursor`" + ` / ` + "`opencode`" + ` / ` + "`gemini`" + ` / ` + "`bedrock`" + ` / ` + "`deepseek`" + `.
|
|
Only ` + "`local`" + ` needs a ` + "`-tags llama`" + ` build; the HTTP and subprocess adapters are pure Go, available in every binary.
|
|
|
|
| Provider | Backend | Requires |
|
|
|---|---|---|
|
|
| ` + "`local`" + ` (default) | in-process llama.cpp | ` + "`-tags llama`" + ` build + ` + "`llm.local.model`" + ` (a ` + "`.gguf`" + `) |
|
|
| ` + "`anthropic`" + ` | Messages API | ` + "`llm.anthropic.model`" + ` + ` + "`ANTHROPIC_API_KEY`" + ` |
|
|
| ` + "`openai`" + ` | Chat Completions | ` + "`llm.openai.model`" + ` + ` + "`OPENAI_API_KEY`" + ` |
|
|
| ` + "`azure`" + ` | Azure OpenAI | ` + "`llm.azure.deployment`" + ` + endpoint + ` + "`AZURE_OPENAI_API_KEY`" + ` |
|
|
| ` + "`ollama`" + ` | Ollama daemon | ` + "`llm.ollama.model`" + ` (+ ` + "`llm.ollama.host`" + `) |
|
|
| ` + "`claudecli`" + ` / ` + "`codex`" + ` / ` + "`copilot`" + ` / ` + "`cursor`" + ` / ` + "`opencode`" + ` | CLI subprocess | the named CLI on ` + "`$PATH`" + ` (signed in once) |
|
|
| ` + "`gemini`" + ` | Gemini ` + "`generateContent`" + ` | ` + "`llm.gemini.model`" + ` + ` + "`GEMINI_API_KEY`" + ` |
|
|
| ` + "`bedrock`" + ` | AWS Bedrock Converse (SigV4) | ` + "`llm.bedrock.model_id`" + ` + AWS creds (region default ` + "`us-east-1`" + `) |
|
|
| ` + "`deepseek`" + ` | DeepSeek Chat Completions | ` + "`llm.deepseek.model`" + ` + ` + "`DEEPSEEK_API_KEY`" + ` |
|
|
|
|
Custom OpenAI-compatible endpoints register by name with ` + "`gortex provider add/list/show/remove`" + `. Anthropic accepts the tier sentinels ` + "`claude-haiku`" + ` / ` + "`claude-sonnet`" + ` / ` + "`claude-opus`" + ` and opt-in prompt caching / thinking / effort. ` + "`llm.routing`" + ` (off by default) routes ` + "`ask`" + ` to a cheaper or more capable model by graph-derived task complexity.
|
|
|
|
### Delegate research to a local agent (` + "`ask`" + `)
|
|
|
|
When a provider is configured the ` + "`ask`" + ` MCP tool is registered — a grammar-constrained agent that uses gortex tools to research one question and returns a synthesized answer. Reach for it when you'd otherwise issue many ` + "`search_symbols`" + ` / ` + "`get_callers`" + ` / ` + "`contracts`" + ` calls; pass ` + "`chain: true`" + ` to trace a request across repos. If ` + "`ask`" + ` isn't in ` + "`tools/list`" + `, no provider could construct — fall through to direct tools.
|
|
|
|
### ` + "`search_symbols assist:`" + ` modes
|
|
|
|
` + "`auto`" + ` (default — skips the LLM for identifier queries, expands NL queries), ` + "`on`" + ` (forces expansion+rerank), ` + "`off`" + ` (pure BM25), ` + "`deep`" + ` (adds a body-grounded verification pass; +1.5-4s; quality is model-dependent).`
|
|
|
|
// guideCapabilities is the non-obvious capabilities tour, relocated from the
|
|
// rule block. Reference material an agent reaches for on demand, not a
|
|
// mid-session action list.
|
|
const guideCapabilities = `## Non-obvious capabilities
|
|
|
|
- **` + "`compress_bodies: true`" + `** on ` + "`read_file`" + ` / ` + "`get_symbol_source`" + ` / ` + "`get_editing_context`" + ` elides function bodies to stubs while keeping signatures + doc-comments + structure. ~30-40% of original tokens. Composes with ` + "`format:\"gcx\"`" + `; safe to set unconditionally (no-op on unparseable input).
|
|
- **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. Idle TTL via ` + "`GORTEX_OVERLAY_IDLE_TTL`" + ` (default 30m).
|
|
- **Speculative execution** (` + "`preview_edit`" + ` / ` + "`simulate_chain`" + `) takes an LSP ` + "`WorkspaceEdit`" + ` and returns the graph diff + broken callers/implementors + impact rollup + suggested tests + optional diagnostics — disk untouched. ` + "`simulate_chain keep:true`" + ` promotes the final state into a real overlay.
|
|
- **Change-contract pipeline** (` + "`change_contract`" + ` / ` + "`symbols_for_ranges`" + `) lowers a WorkspaceEdit / diff range / symbol set / line-ranges into one verdict ` + "`{allow|warn|refuse}`" + ` with reasons, risk, a verification command, a stop condition, and an edit strategy. The pre-write **parse gate** on ` + "`edit_file`" + ` / ` + "`write_file`" + ` refuses edits that introduce new tree-sitter parse errors (override ` + "`allow_parse_errors`" + `).
|
|
- **MCP 2026 Streamable HTTP** at ` + "`POST /mcp`" + ` — ` + "`gortex server`" + ` always mounts it; ` + "`gortex daemon --http-addr <addr>`" + ` opts the daemon in (non-localhost binds require ` + "`--http-auth-token`" + `).
|
|
- **Artifacts** — non-code knowledge files (DB schemas, API specs, ADRs, infra configs) declared in ` + "`.gortex.yaml`" + ` ` + "`artifacts:`" + ` are indexed as ` + "`artifact`" + ` nodes; ` + "`search_artifacts`" + ` / ` + "`get_artifact`" + ` surface them.
|
|
- **` + "`get_architecture`" + `** — one-call architectural snapshot (languages, communities, hotspots, processes); pass ` + "`resolution`" + ` for a symbol → file → package → service → system rollup.
|
|
- **Capability edges** — ` + "`reads_env`" + ` / ` + "`executes_process`" + ` / ` + "`accesses_field`" + ` are first-class traversable edges for supply-chain / least-privilege audits ("what reads $AWS_SECRET", "what shells out", "what writes this field").
|
|
- **PR review, end-to-end** — ` + "`gortex prs`" + ` triages open PRs from the graph; ` + "`gortex review [<base>|--diff] [--audience agent|human]`" + ` reviews a diff. MCP mirrors: ` + "`pr_risk`" + ` / ` + "`list_prs`" + ` / ` + "`triage_prs`" + ` / ` + "`review`" + ` / ` + "`review_pack`" + `.
|
|
- **Broad ingest** — image + PDF nodes; first-class extractors for Terraform/HCL, Helm, Ansible, .NET ` + "`.sln`" + `/` + "`.csproj`" + `, MCP configs, Quarto, Luau, COBOL/JCL, C/C++ macros. ` + "`gortex db schema --postgres <dsn>`" + ` ingests a live DB schema.`
|
|
|
|
// guideTokens is the token-economy deep-dive MINUS the wire-format section
|
|
// (which lives authoritatively in the server instructions / sharedParamLegend
|
|
// and is only pointed at here — the single-home invariant for format).
|
|
const guideTokens = `## Token economy
|
|
|
|
The wire-FORMAT knobs (` + "`format`" + ` = json/gcx/toon, ` + "`max_bytes`" + `, ` + "`cursor`" + `, ` + "`fields`" + `, ` + "`limit`" + `, ` + "`scope`" + `, ` + "`repo`" + `/` + "`project`" + `/` + "`workspace`" + `/` + "`ref`" + `) are documented once in the MCP server ` + "`instructions`" + ` (the shared-parameter legend emitted at initialize). Read them there; this section covers the orthogonal content knobs.
|
|
|
|
### Content compression (` + "`compress_bodies`" + `)
|
|
|
|
Replaces every function/method body in returned source with a ` + "`{ /* N lines elided */ }`" + ` stub (language-appropriate). Signatures, doc-comments, imports, top-level constants/types, and structure stay intact. A 200-line file lands at ≤ 60 lines. Wired in 16 languages. Composes with ` + "`format:\"gcx\"`" + ` for stacked savings.
|
|
|
|
### Graceful degradation
|
|
|
|
List-shaped tools run through a per-response budget. On overflow the server strips verbose meta, then drops tier-3 rows (params/closures/low-confidence edges), then tier-2 rows, then tail-trims the longest tier-1 list — each escape stamped (` + "`_meta_stripped`" + `, ` + "`_dropped_tier_<N>_<list>`" + `, ` + "`_truncated_by_budget`" + `). Use the metadata to decide whether to narrow the filter, raise ` + "`max_bytes`" + `, or paginate.
|
|
|
|
### Reuse and freshness
|
|
|
|
Treat what you fetch as cached for the task — don't re-run the same query. ` + "`read_file`" + ` / ` + "`get_symbol_source`" + ` / ` + "`get_file_summary`" + ` / ` + "`get_editing_context`" + ` / ` + "`smart_context`" + ` return an ` + "`etag`" + ` and accept ` + "`if_none_match`" + ` — an unchanged target comes back ` + "`not_modified`" + ` at ~0 tokens. A ` + "`count: 0`" + ` while the daemon is warming (a ` + "`warming`" + ` block, or ` + "`index_health`" + ` not ready) means *not indexed yet*, not *absent*.`
|
|
|
|
// guideResources is the MCP resources catalog, relocated from the rule block.
|
|
const guideResources = `## MCP resources
|
|
|
|
Bootstrap-state tools are also exposed as read-only, URI-addressable resources — subscribe via ` + "`resources/subscribe`" + ` once and receive ` + "`notifications/resources/updated`" + ` after each graph re-warm (no polling).
|
|
|
|
| Resource | Payload |
|
|
|---|---|
|
|
| ` + "`gortex://stats`" + ` | ` + "`graph_stats`" + ` |
|
|
| ` + "`gortex://index-health`" + ` | ` + "`index_health`" + ` |
|
|
| ` + "`gortex://workspace`" + ` | ` + "`workspace_info`" + ` |
|
|
| ` + "`gortex://repos`" + ` | ` + "`list_repos`" + ` |
|
|
| ` + "`gortex://active-project`" + ` | ` + "`get_active_project`" + ` |
|
|
| ` + "`gortex://schema`" + ` | graph schema + analyze/search_ast catalogs |
|
|
| ` + "`gortex://guide`" + ` | this reference |
|
|
|
|
Analyzer rollups (read-only summaries of the indexed state): ` + "`gortex://report`" + ` (orientation), ` + "`gortex://god-nodes`" + ` (top hotspots), ` + "`gortex://surprises`" + ` (cycles + dead code + hubs), ` + "`gortex://audit`" + ` (CLAUDE.md drift), ` + "`gortex://questions`" + ` (TODOs), plus ` + "`gortex://communities`" + ` / ` + "`gortex://processes`" + ` templates.`
|
|
|
|
// guideWorkflow is the full session-start checklist, relocated here so the
|
|
// installed policy core keeps only the memory-trigger essentials.
|
|
const guideWorkflow = `## Session-start checklist
|
|
|
|
1. Confirm the daemon with ` + "`index_health`" + ` (cheap liveness + scope). Call ` + "`graph_stats`" + ` only when you need node/edge counts or ` + "`per_repo`" + ` orientation.
|
|
2. If ` + "`total_nodes`" + ` is 0, ` + "`index_repository`" + ` with path ` + "`\".\"`" + ` before anything else.
|
|
3. Call ` + "`distill_session`" + ` to recover prior-session memory (decisions, pinned notes, recent excerpts).
|
|
4. In multi-repo mode, ` + "`get_active_project`" + ` to check scope; ` + "`set_active_project`" + ` to switch.
|
|
5. Open a non-trivial task with ` + "`smart_context`" + `; then ` + "`surface_memories task:\"…\" symbol_ids:\"…\"`" + ` to pick up cross-session invariants.
|
|
6. Before editing a file, ` + "`get_editing_context`" + `; if you've touched the symbol before, ` + "`query_notes`" + ` / ` + "`query_memories`" + ` by symbol_id.
|
|
7. Before a signature change, ` + "`verify_change`" + `. For a refactor, ` + "`get_edit_plan`" + ` then ` + "`batch_edit`" + `.
|
|
8. Verify with the project's real build/test. Reserve ` + "`check_guards`" + ` / ` + "`get_test_targets`" + ` for substantive changes.
|
|
9. Before committing, ` + "`detect_changes`" + ` for scope + ` + "`diff_context`" + ` for graph-enriched review.
|
|
|
|
The behavior-critical memory triggers (distill_session, surface_memories, save_note, store_memory) live in the installed policy core (~/.claude/CLAUDE.md). This checklist is the fuller reference.`
|
|
|
|
// guideSection maps a topic keyword to its rendered section. Section bodies
|
|
// that need a live catalog (analyze / search_ast) are rendered from the same
|
|
// source functions that back kind:"help" / detector:"help", so the catalog
|
|
// has a single source of truth.
|
|
func guideSection(topic string) (string, bool) {
|
|
switch strings.ToLower(strings.TrimSpace(topic)) {
|
|
case "providers", "provider", "llm", "ask":
|
|
return guideProviders, true
|
|
case "capabilities", "capability", "features", "feature":
|
|
return guideCapabilities, true
|
|
case "tokens", "token", "economy", "compress", "compress_bodies":
|
|
return guideTokens, true
|
|
case "resources", "resource":
|
|
return guideResources, true
|
|
case "workflow", "session", "checklist", "session-start":
|
|
return guideWorkflow, true
|
|
case "analyze", "analyzers", "kinds":
|
|
// Point at the single-source catalog rather than re-inlining it: the
|
|
// full per-kind reference lives in `analyze kind:"help"` and the
|
|
// gortex://schema resource.
|
|
return "## analyze\n\n" + analyzeGroupedSummary, true
|
|
case "search_ast", "ast", "detectors":
|
|
return "## search_ast\n\n" + buildSearchASTDescription() +
|
|
"\n\nFull detector catalog: `search_ast detector:\"help\"` or the gortex://schema resource.", true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
// guideTopicOrder is the canonical section order for the full render and the
|
|
// topic list.
|
|
var guideTopicOrder = []string{"providers", "capabilities", "tokens", "analyze", "search_ast", "resources", "workflow"}
|
|
|
|
// GuideTopics returns the addressable guide topic keywords, sorted.
|
|
func GuideTopics() []string {
|
|
out := append([]string(nil), guideTopicOrder...)
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// GuideText renders the guide. An empty topic (or "all"/"index") returns the
|
|
// full document with a table of contents; a known topic returns just that
|
|
// section; an unknown topic returns the overview with the topic list so the
|
|
// caller can retry.
|
|
func GuideText(topic string) string {
|
|
t := strings.ToLower(strings.TrimSpace(topic))
|
|
if t != "" && t != "all" && t != "index" && t != "toc" {
|
|
if sec, ok := guideSection(t); ok {
|
|
return sec + "\n"
|
|
}
|
|
// Unknown topic: fall through to the overview so the reply is still
|
|
// useful and names the valid topics.
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("# Gortex Guide\n\n")
|
|
b.WriteString("On-demand reference for Gortex. The mandatory graph-tools policy and the memory workflow live in the installed CLAUDE.md; everything below is reachable via the `gortex://guide` resource or `gortex guide [topic]`.\n\n")
|
|
if t != "" && t != "all" && t != "index" && t != "toc" {
|
|
b.WriteString("(Unknown topic \"" + topic + "\" — showing the full guide. Topics: " + strings.Join(guideTopicOrder, ", ") + ".)\n\n")
|
|
} else {
|
|
b.WriteString("Topics: " + strings.Join(guideTopicOrder, ", ") + ". Address one with `gortex guide <topic>` or `gortex://guide/<topic>`.\n\n")
|
|
}
|
|
for _, name := range guideTopicOrder {
|
|
sec, _ := guideSection(name)
|
|
b.WriteString(sec)
|
|
b.WriteString("\n\n")
|
|
}
|
|
return b.String()
|
|
}
|