chore: import upstream snapshot with attribution
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
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
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
=== root/.aiderignore ===
|
||||
# Added by `gortex init` — Gortex cache artifacts are not source
|
||||
.gortex/
|
||||
*.gortex-cache
|
||||
=== root/CONVENTIONS.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
@@ -0,0 +1,98 @@
|
||||
=== home/.gemini/antigravity/knowledge/gortex-workflow/artifacts/gortex-instructions.md ===
|
||||
---
|
||||
type: "Knowledge Item"
|
||||
description: "Gortex Workflow and Tools for Antigravity"
|
||||
---
|
||||
|
||||
# Gortex Engine Usage
|
||||
|
||||
This codebase is indexed by Gortex, an in-memory knowledge graph code intelligence engine.
|
||||
|
||||
**MANDATORY:** You MUST prioritize using the Gortex CLI via `run_command` instead of using standard text searches (`grep_search`) or full file reads (`view_file`). This drastically improves token efficiency and prevents hallucination.
|
||||
|
||||
## Setup
|
||||
If the daemon is not tracking your target repos, register the current repo so the graph tools activate:
|
||||
```bash
|
||||
gortex track .
|
||||
```
|
||||
|
||||
## Standard Workflow Translation
|
||||
|
||||
| Instead of... | You MUST use... (via `run_command`) |
|
||||
|---|---|
|
||||
| `grep_search` to find a class or function | `./gortex query symbol <name> --format text` (AST-aware search) |
|
||||
| `grep_search` to find all references | `./gortex query usages <id>` (zero false positives) |
|
||||
| `view_file` to read a whole file to find a method | `./gortex query symbol <name>` or `./gortex query callers <func_id>` |
|
||||
| Guessing what breaks during a refactor | `./gortex query dependents <id>` (impact analysis) |
|
||||
| Creating circular dependencies | Evaluate `./gortex query deps <id>` first |
|
||||
|
||||
## Example Usage
|
||||
|
||||
### 1. View Architecture and Communities
|
||||
```bash
|
||||
./gortex query stats
|
||||
```
|
||||
|
||||
### 2. Find specific symbol definition
|
||||
```bash
|
||||
./gortex query symbol MyController
|
||||
```
|
||||
|
||||
### 3. Trace blast radius
|
||||
If you are modifying `core/parser.go::Parse`, check what will break:
|
||||
```bash
|
||||
./gortex query dependents core/parser.go::Parse --depth 2
|
||||
```
|
||||
|
||||
This gives you perfectly accurate AST-level analysis, guaranteeing safe edits.
|
||||
=== home/.gemini/antigravity/knowledge/gortex-workflow/metadata.json ===
|
||||
{
|
||||
"summary": "MANDATORY: Instructions on how to use the local gortex engine CLI to significantly improve codebase intelligence. Antigravity must use run_command with gortex query over standard file read commands.",
|
||||
"references": ["artifacts/gortex-instructions.md"]
|
||||
}
|
||||
=== home/.gemini/antigravity/mcp_config.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== home/.gemini/settings.json ===
|
||||
{
|
||||
"hooks": {
|
||||
"AfterTool": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "gortex hook --agent antigravity",
|
||||
"description": "Gortex graph context + stale-index hint",
|
||||
"name": "gortex",
|
||||
"timeout": 10000,
|
||||
"type": "command"
|
||||
}
|
||||
],
|
||||
"matcher": "run_shell_command|search_file_content|glob"
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "gortex hook --agent antigravity",
|
||||
"description": "Gortex session orientation",
|
||||
"name": "gortex",
|
||||
"timeout": 10000,
|
||||
"type": "command"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
=== root/.claude/settings.json ===
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__gortex__*"
|
||||
]
|
||||
}
|
||||
}
|
||||
=== root/.mcp.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"command": "gortex",
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "${GORTEX_WORKERS:-8}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/CLAUDE.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
=== root/.clinerules/gortex-communities.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
=== home/.codex/config.toml ===
|
||||
[mcp_servers]
|
||||
[mcp_servers.gortex]
|
||||
args = ['mcp']
|
||||
command = 'gortex'
|
||||
|
||||
[mcp_servers.gortex.env]
|
||||
GORTEX_INDEX_WORKERS = '8'
|
||||
=== root/AGENTS.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== root/.continue/mcpServers/gortex.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/.continue/rules/gortex-communities.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
=== root/.cursor/mcp.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/.cursor/rules/gortex-communities.mdc ===
|
||||
---
|
||||
description: Gortex code intelligence — prefer graph tools over file reads
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
=== root/.cursor/rules/gortex-workflow.mdc ===
|
||||
---
|
||||
description: Gortex code intelligence — prefer graph tools over file reads
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
## Gortex in Cursor
|
||||
|
||||
This repository wires the **gortex** MCP server via .cursor/mcp.json (merge-managed by Gortex).
|
||||
|
||||
**MANDATORY: use graph tools, not blind file reads**
|
||||
|
||||
You **MUST** prefer Gortex graph queries over text search and whole-file opens on every task. These are not suggestions.
|
||||
|
||||
- **Start** a new chat with **index_health** to confirm the daemon/index (cheap); use **graph_stats** only when you need node/edge counts or multi-repo orientation.
|
||||
- **Use** **search_symbols**, **get_symbol_source**, **get_file_summary**, **get_call_chain**, **find_usages**, and **smart_context** instead of opening whole files or guessing with text search.
|
||||
- Before any signature or API change, **run** **verify_change**; for test selection **run** **get_test_targets**.
|
||||
|
||||
**MANDATORY: session memory**
|
||||
|
||||
- **At session start**, call **distill_session** to recover decisions, pinned notes, and recent excerpts saved in prior sessions in this workspace.
|
||||
- **At every decision point** (picking an approach, rejecting an alternative, spotting a non-obvious constraint), call **save_note** with `tags:"decision"` and mention affected symbol IDs in the body — they auto-link.
|
||||
- **Before editing a symbol you've touched before**, call **query_notes** with `symbol_id:"<id>"` to surface prior warnings and decisions.
|
||||
|
||||
**MANDATORY: development memories (cross-session)**
|
||||
|
||||
- **Immediately after smart_context** on every task, call **surface_memories** with `task:"<task>"` and `symbol_ids:"<top hits>"` — returns memories ranked by anchor overlap, importance, pinning, recency.
|
||||
- **When you find a durable invariant, gotcha, or decision worth teaching the team**, call **store_memory** with `kind:"<invariant|gotcha|convention|decision>"`, `symbol_ids:"<id>"`, `importance:5`. Pin load-bearing memories. Use `supersedes:"<old-id>"` when newer knowledge replaces older.
|
||||
- Memories are workspace-wide and outlive sessions, agents, and teammates — every future agent inherits them.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
=== root/.gemini/settings.json ===
|
||||
{
|
||||
"hooks": {
|
||||
"AfterTool": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "gortex hook --agent gemini",
|
||||
"description": "Gortex graph context + stale-index hint",
|
||||
"name": "gortex",
|
||||
"timeout": 10000,
|
||||
"type": "command"
|
||||
}
|
||||
],
|
||||
"matcher": "run_shell_command|search_file_content|glob"
|
||||
}
|
||||
],
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "gortex hook --agent gemini",
|
||||
"description": "Gortex session orientation",
|
||||
"name": "gortex",
|
||||
"timeout": 10000,
|
||||
"type": "command"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/GEMINI.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+1675
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
=== root/.kilocoderules ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
=== root/.kimi-code/mcp.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
=== root/.kiro/hooks/gortex-post-edit.json ===
|
||||
{
|
||||
"name": "Gortex: Post-Edit Impact Check",
|
||||
"version": "1.0.0",
|
||||
"description": "After saving a source file, runs detect_changes and get_test_targets to show blast radius and which tests to run.",
|
||||
"when": {
|
||||
"type": "fileEdited",
|
||||
"patterns": ["**/*.go", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.py", "**/*.rs", "**/*.java", "**/*.kt", "**/*.scala", "**/*.swift", "**/*.rb", "**/*.cs", "**/*.php"]
|
||||
},
|
||||
"then": {
|
||||
"type": "askAgent",
|
||||
"prompt": "A source file was just edited. Call Gortex detect_changes with scope unstaged to see which symbols were affected and the risk level. If symbols were changed, also call get_test_targets with those symbol IDs to identify which tests should be run. Briefly report the risk level and test commands."
|
||||
}
|
||||
}
|
||||
=== root/.kiro/hooks/gortex-pre-read.json ===
|
||||
{
|
||||
"name": "Gortex: Enrich File Reads",
|
||||
"version": "1.0.0",
|
||||
"description": "Before reading a source file, calls get_editing_context to inject symbol context, callers, callees, and imports.",
|
||||
"when": {
|
||||
"type": "preToolUse",
|
||||
"toolTypes": ["read"]
|
||||
},
|
||||
"then": {
|
||||
"type": "askAgent",
|
||||
"prompt": "SKIP this hook entirely (do nothing, proceed with the read) if ANY of these are true: (1) the file path contains .kiro/, .claude/, .github/, .vscode/, or node_modules/, (2) the file extension is .md, .json, .yaml, .yml, .toml, .txt, .lock, .sum, .mod, .env, .gitignore, .html, .css, or .svg, (3) the file is not a source code file. ONLY for source code files (.go, .ts, .tsx, .js, .jsx, .py, .rs, .java, .kt, .cs, .rb, .php, .swift, .scala, .c, .cpp, .h): call get_editing_context or get_file_summary for the file to get symbol context before reading it."
|
||||
}
|
||||
}
|
||||
=== root/.kiro/hooks/gortex-smart-context.json ===
|
||||
{
|
||||
"name": "Gortex: Smart Context on Prompt",
|
||||
"version": "1.0.0",
|
||||
"description": "On each new prompt, calls smart_context to assemble task-relevant code context from the knowledge graph in one shot.",
|
||||
"when": {
|
||||
"type": "promptSubmit"
|
||||
},
|
||||
"then": {
|
||||
"type": "askAgent",
|
||||
"prompt": "If the user's message describes a coding task (adding a feature, fixing a bug, refactoring, understanding code), call Gortex's smart_context tool with the task description to get relevant symbols, source code, relationships, and an edit plan in one call. Skip this for non-coding questions or simple chat."
|
||||
}
|
||||
}
|
||||
=== root/.kiro/settings/mcp.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"autoApprove": [
|
||||
"graph_stats",
|
||||
"search_symbols",
|
||||
"winnow_symbols",
|
||||
"get_symbol",
|
||||
"get_file_summary",
|
||||
"get_editing_context",
|
||||
"get_dependencies",
|
||||
"get_dependents",
|
||||
"get_call_chain",
|
||||
"get_callers",
|
||||
"find_implementations",
|
||||
"find_usages",
|
||||
"get_cluster",
|
||||
"get_symbol_source",
|
||||
"batch_symbols",
|
||||
"find_import_path",
|
||||
"explain_change_impact",
|
||||
"get_recent_changes",
|
||||
"smart_context",
|
||||
"get_edit_plan",
|
||||
"get_test_targets",
|
||||
"suggest_pattern",
|
||||
"get_communities",
|
||||
"get_processes",
|
||||
"detect_changes",
|
||||
"index_repository",
|
||||
"reindex_repository",
|
||||
"verify_change",
|
||||
"check_guards",
|
||||
"prefetch_context",
|
||||
"analyze",
|
||||
"diff_context",
|
||||
"index_health",
|
||||
"get_symbol_history",
|
||||
"scaffold",
|
||||
"batch_edit",
|
||||
"contracts",
|
||||
"feedback",
|
||||
"save_note",
|
||||
"query_notes",
|
||||
"distill_session",
|
||||
"store_memory",
|
||||
"query_memories",
|
||||
"surface_memories"
|
||||
],
|
||||
"command": "gortex",
|
||||
"disabled": false,
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/.kiro/steering/gortex-debug.md ===
|
||||
---
|
||||
inclusion: manual
|
||||
---
|
||||
|
||||
# Debugging with Gortex
|
||||
|
||||
## Workflow
|
||||
|
||||
1. `search_symbols({query: "<error or suspect>"})` — find related symbols
|
||||
2. `get_callers({id: "<suspect>"})` — who calls it?
|
||||
3. `get_call_chain({id: "<suspect>"})` — what does it call?
|
||||
4. `get_editing_context({path: "<file>"})` — full file context
|
||||
5. `get_process({id: "<process>"})` — trace execution flow
|
||||
|
||||
## Debugging patterns
|
||||
|
||||
| Symptom | Gortex Approach |
|
||||
| -------------------- | --------------- |
|
||||
| Error message | `search_symbols` for error-related names, then `get_callers` on throw sites |
|
||||
| Wrong return value | `get_call_chain` on the function, trace callees for data flow |
|
||||
| Intermittent failure | `get_editing_context`, look for external calls and async deps |
|
||||
| Performance issue | `find_usages`, find symbols with many callers (hot paths) |
|
||||
| Recent regression | `detect_changes`, see what your changes affect |
|
||||
=== root/.kiro/steering/gortex-explore.md ===
|
||||
---
|
||||
inclusion: manual
|
||||
---
|
||||
|
||||
# Exploring Codebases with Gortex
|
||||
|
||||
## Workflow
|
||||
|
||||
1. `index_health` — confirm the index is ready (`graph_stats` for node/edge counts)
|
||||
2. `get_communities` — see functional clusters (architecture overview)
|
||||
3. `search_symbols({query: "<concept>"})` — find symbols related to a concept
|
||||
4. `get_processes` — discover execution flows
|
||||
5. `get_process({id: "<process-id>"})` — trace a specific flow step by step
|
||||
6. `get_editing_context({path: "<file>"})` — deep dive on a specific file
|
||||
|
||||
## When to use
|
||||
|
||||
- "How does authentication work?"
|
||||
- "What's the project structure?"
|
||||
- "Show me the main components"
|
||||
- Understanding code you haven't seen before
|
||||
|
||||
## Key tools
|
||||
|
||||
- `get_communities` for architectural overview (functional clusters with cohesion scores)
|
||||
- `get_processes` for execution flow discovery (entry points to call chains)
|
||||
- `search_symbols` for concept-based symbol search (BM25 + camelCase-aware)
|
||||
- `get_editing_context` for 360-degree file view (symbols, callers, callees, imports)
|
||||
=== root/.kiro/steering/gortex-impact.md ===
|
||||
---
|
||||
inclusion: manual
|
||||
---
|
||||
|
||||
# Impact Analysis with Gortex
|
||||
|
||||
## Workflow
|
||||
|
||||
1. `search_symbols({query: "X"})` — find the symbol ID
|
||||
2. `explain_change_impact({ids: "<id1>, <id2>"})` — risk-tiered blast radius
|
||||
3. `get_dependents({id: "<symbol-id>", depth: 3})` — detailed dependent tree
|
||||
4. `detect_changes({scope: "staged"})` — pre-commit check
|
||||
|
||||
## Risk tiers
|
||||
|
||||
| Depth | Risk Level | Meaning |
|
||||
| ----- | -------------- | ------------------------ |
|
||||
| d=1 | WILL BREAK | Direct callers/importers |
|
||||
| d=2 | LIKELY AFFECTED| Indirect dependencies |
|
||||
| d=3 | MAY NEED TESTING| Transitive effects |
|
||||
|
||||
## Before any non-trivial change
|
||||
|
||||
- Call `explain_change_impact` with all symbols you plan to modify
|
||||
- Review the risk level (LOW/MEDIUM/HIGH/CRITICAL)
|
||||
- Check `by_depth`: d=1 items WILL BREAK
|
||||
- Note `affected_processes` and `affected_communities`
|
||||
- Check `test_files` that need re-running
|
||||
- Before commit: `detect_changes` to verify scope
|
||||
=== root/.kiro/steering/gortex-refactor.md ===
|
||||
---
|
||||
inclusion: manual
|
||||
---
|
||||
|
||||
# Refactoring with Gortex
|
||||
|
||||
## Workflow
|
||||
|
||||
1. `search_symbols({query: "X"})` — find the symbol ID
|
||||
2. `explain_change_impact({ids: "<id>"})` — map blast radius
|
||||
3. `get_editing_context({path: "<file>"})` — see all symbols and relationships
|
||||
4. `find_usages({id: "<id>"})` — every reference to change
|
||||
5. `get_edit_plan({ids: "<ids>"})` — dependency-ordered edit sequence
|
||||
6. Edit in order: interfaces -> implementations -> callers -> tests
|
||||
7. `detect_changes({scope: "all"})` — verify after changes
|
||||
|
||||
## Rename symbol
|
||||
|
||||
- `find_usages` to get every reference location
|
||||
- `explain_change_impact` to assess blast radius
|
||||
- Edit in dependency order: definition, then callers, then tests
|
||||
|
||||
## Extract module
|
||||
|
||||
- `get_editing_context` on the source file to see all symbols
|
||||
- `get_dependents` on symbols to extract to find external callers
|
||||
- `find_import_path` for correct import paths in the new location
|
||||
|
||||
## Split function/service
|
||||
|
||||
- `get_call_chain` to understand all callees
|
||||
- `get_callers` to map all call sites that need updating
|
||||
- `explain_change_impact` for full blast radius
|
||||
=== root/.kiro/steering/gortex-workflow.md ===
|
||||
---
|
||||
inclusion: always
|
||||
---
|
||||
|
||||
# Gortex Code Intelligence
|
||||
|
||||
Gortex is running as an MCP server. It indexes this repository into an in-memory knowledge graph and exposes tools for code navigation, impact analysis, and refactoring.
|
||||
|
||||
## MANDATORY: Use Gortex tools instead of file reads
|
||||
|
||||
You **MUST** prefer Gortex graph queries over file reads on every task in this repo. These are not suggestions — the tools below replace the corresponding read/grep flows. From a shell (no MCP tools), every tool below is reachable as `gortex call <tool> --arg k=v` (e.g. `gortex call read_file --arg path=<file>`) — there is no bare `gortex <tool>` verb.
|
||||
|
||||
### Navigation and Reading
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Reading a whole file for one function | `get_symbol_source` with `id: "path/to/file.go::SymbolName"` (methods carry the receiver: `"path/to/file.go::Recv.Name"`; 80% fewer tokens) — use `get_file_summary` first if you don't know the symbol name |
|
||||
| Reading to find a function | `get_symbol` or `get_editing_context` |
|
||||
| Multiple `get_symbol` calls | `batch_symbols` (one call for N symbols) |
|
||||
| Searching for references | `find_usages` (zero false positives) |
|
||||
| Searching to find a symbol by name | `search_symbols` (BM25 + camelCase) |
|
||||
| Filtering `search_symbols` by hand | `winnow_symbols` — structured constraint chain (kind, language, community, path_prefix, min_fan_in, min_churn) with per-axis score contributions |
|
||||
| Reading to understand a file | `get_file_summary` or `get_editing_context` |
|
||||
| Reading multiple files to trace calls | `get_call_chain` / `get_callers` |
|
||||
| Guessing an import path | `find_import_path` |
|
||||
| Reading to check a function signature | `get_symbol_signature` |
|
||||
| 5-10 calls to explore for a task | `smart_context` (one call) |
|
||||
|
||||
### Impact Analysis and Safety
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Reading files to assess change scope | `explain_change_impact` (includes cross-community warnings) |
|
||||
| Guessing which tests to run | `get_test_targets` |
|
||||
| Manual dependency ordering | `get_edit_plan` |
|
||||
| Hoping signature changes are safe | `verify_change` — checks callers and interface implementors |
|
||||
| Manually checking team conventions | `check_guards` — evaluates guard rules from .gortex.yaml |
|
||||
| Wondering if a new dep creates a cycle| `analyze` with `kind: "would_create_cycle"` — checks before you add it |
|
||||
|
||||
### Structural Code Search
|
||||
|
||||
| Instead of... | Use... |
|
||||
|------------------------------------------|------------------------------------------|
|
||||
| Grep for an anti-pattern in this repo | `search_ast` with `detector: "<name>"` (`error-not-wrapped` / `sql-string-concat` / `weak-crypto` / `panic-in-library` / `goroutine-without-recover` / `http-client-no-timeout` / `hardcoded-secret` / `empty-catch` / `java-string-equality` / `python-mutable-default-arg`). Cross-language; matches enriched with enclosing `symbol_id`. |
|
||||
| Grep for a code shape | `search_ast` with `pattern: "..."` + `language` (tree-sitter S-expression; capture with `@name`, anchor with `@match`). |
|
||||
| Scoping audit to important code | Pass `min_fan_in_of_enclosing_func: <N>` — drops matches in functions with fewer than N callers. |
|
||||
|
||||
### Diagnostics and Code Actions
|
||||
|
||||
| Instead of... | Use... |
|
||||
|------------------------------------------|------------------------------------------|
|
||||
| Polling for diagnostics after every edit | `subscribe_diagnostics` — opt into push `notifications/diagnostics`. Initial state replays as `initial_replay: true`; thereafter delta-changed files only. `min_severity` / `path_prefix` filters scope the stream. |
|
||||
| Manual diagnostics fetch | `get_diagnostics` — last stored `publishDiagnostics` for a file; `wait` + `timeout_ms` block until the first publish. |
|
||||
| Forgetting to opt out | `unsubscribe_diagnostics` — idempotent; auto-fires on session disconnect. |
|
||||
| Hand-applying compiler suggestions | `get_code_actions` then `apply_code_action` (atomic temp+rename, both `changes` and `documentChanges`). |
|
||||
| Walking a file to apply every fix | `fix_all_in_file` — one-shot `source.fixAll` for the whole file. |
|
||||
|
||||
### Code Quality and Analysis
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Manually hunting unused code | `analyze` with `kind: "dead_code"` — zero incoming edges (excludes entry points, tests, exports) |
|
||||
| Guessing which symbols are over-coupled| `analyze` with `kind: "hotspots"` — ranks by fan-in, fan-out, community crossings |
|
||||
| Manually scanning for circular deps | `analyze` with `kind: "cycles"` — Tarjan's SCC with severity classification |
|
||||
| Surveying K8s manifests in the repo | `analyze` with `kind: "k8s_resources"` — KindResource fan-out (depends_on / configures / mounts / exposes / uses_env); `k8s_kind` / `namespace` / `name` filters |
|
||||
| Listing container images in use | `analyze` with `kind: "images"` — KindImage with consumer count (Dockerfile FROM + K8s `container.image`); `role` / `ref` / `tag` filters |
|
||||
| Mapping the Kustomize overlay tree | `analyze` with `kind: "kustomize"` — KindKustomization with base / resource fan-out; `dir` filter |
|
||||
| Auditing what crosses repo boundaries | `analyze` with `kind: "cross_repo"` — calls / implements / extends edges crossing repo boundaries, grouped by source → target repo; `repo` / `base_kind` / `path_prefix` filters |
|
||||
| Surveying dbt / SQLMesh models | `analyze` with `kind: "dbt_models"` — dbt / SQLMesh models, seeds, snapshots, sources with column count + lineage fan-in/out; `framework` / `type` / `materialized` / `name` filters |
|
||||
| Checking if the index is stale | `index_health` — health score, parse failures, stale files |
|
||||
| Wondering what changed this session | `get_symbol_history` — modification counts, flags churning (3+ edits) |
|
||||
|
||||
### Code Generation and Editing
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Reading files to learn a pattern | `suggest_pattern` |
|
||||
| Manually scaffolding from a pattern | `scaffold` — generates code, wiring, and test stubs from an example |
|
||||
| Sequencing multi-file edits yourself | `batch_edit` — applies edits in dependency order, re-indexes between steps |
|
||||
| Reading a diff without graph context | `diff_context` — enriches git diff with callers, callees, community, risk |
|
||||
| Guessing what context you need next | `prefetch_context` — predicts needed symbols from task + recent activity |
|
||||
|
||||
### Dataflow (CPG-lite)
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Hand-tracing a value through helpers | `flow_between(source_id, sink_id, max_depth=8)` — ranked dataflow paths over `value_flow` ∪ `arg_of` ∪ `returns_to` |
|
||||
| Grepping for sources / sinks | `taint_paths(source_pattern, sink_pattern)` — pattern sweep. Patterns: bare = name substring; `exact:Foo`; `path:dir/`; `kind:method`. Sinks auto-expand functions to params. |
|
||||
|
||||
### Clone Detection
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Eyeballing the repo for copy-paste | `find_clones` — near-duplicate function/method clusters from the `similar_to` graph layer (MinHash + LSH; catches renamed-variable clones) |
|
||||
| Finding safe-to-delete duplicates | `find_clones` with `dead_only: true` — clusters containing a dead-code symbol ("dead duplicates of live code") |
|
||||
|
||||
### Multi-Repo Management
|
||||
|
||||
| Instead of... | Use... |
|
||||
|---------------------------------------|------------------------------------------|
|
||||
| Manually adding a repo to config | `track_repository` — indexes immediately, persists to config |
|
||||
| Manually removing a repo from config | `untrack_repository` — evicts nodes/edges, persists to config |
|
||||
| Refreshing the graph after edits | `reindex_repository` — incremental re-index of changed files only; pass `paths` to scope |
|
||||
| Wondering which project is active | `get_active_project` — returns project name and repo list |
|
||||
| Switching project context | `set_active_project` — re-scopes all subsequent queries |
|
||||
| Scoping a query to one repo | Pass `repo` param to `search_symbols`, `find_usages`, etc. |
|
||||
| Scoping a query to a project | Pass `project` param to any query tool |
|
||||
| Filtering by reference tag | Pass `ref` param to any query tool |
|
||||
|
||||
### Session Memory (save_note / query_notes / distill_session)
|
||||
|
||||
Gortex remembers code; this triplet remembers **why you made a call**. Notes persist per-repo across daemon restarts and context compactions, are scoped to the session's workspace, and are auto-linked to symbols mentioned in the body.
|
||||
|
||||
| Trigger | Use |
|
||||
|----------------------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| Session start in a touched repo (after a compaction or on a fresh run) | `distill_session` — returns top symbols, pinned notes, decisions, recent excerpts. Seed your mental model before reading any file. |
|
||||
| Making a decision, rejecting an alternative, hitting a non-obvious constraint, committing to an invariant | `save_note tags:"decision" body:"<what+why>"` — mention symbol IDs (`pkg/foo.go::Bar`) in the body for auto-linking; pin (`pinned:true`) anything load-bearing. |
|
||||
| Before editing a symbol you've touched before | `query_notes symbol_id:"<id>"` — surfaces prior decisions and warnings attached to that symbol. |
|
||||
|
||||
Save: decisions, non-obvious constraints, follow-ups, bug reproductions, surprising findings, partial-progress hand-offs. Skip: play-by-play of what you just did (the diff says it), patterns derivable from the graph, anything already in the steering docs. Canonical tags: `decision`, `bug`, `follow-up`, `gotcha`, `invariant`.
|
||||
|
||||
### Development Memories (store_memory / query_memories / surface_memories)
|
||||
|
||||
`save_note` is a **per-session scratchpad**; `store_memory` is the **workspace-wide durable knowledge base** — entries outlive sessions, agents, and teammates so every future agent in the workspace inherits them.
|
||||
|
||||
| Trigger | Use |
|
||||
|----------------------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| Immediately after `smart_context` (every new task) | `surface_memories task:"<task>" symbol_ids:"<top hits>"` — memories ranked by anchor overlap, importance, pinning, recency. Each hit carries `match_reasons`. |
|
||||
| You discover a durable invariant / gotcha / decision worth teaching the team | `store_memory kind:"<invariant|gotcha|convention|decision>" body:"<what+why>" symbol_ids:"<id>" importance:5` — pin load-bearing memories. |
|
||||
| A memory is no longer true | `store_memory body:"<corrected>" supersedes:"<old-id>"` — preserves audit trail; old memory hidden by default. |
|
||||
|
||||
Store: invariants, conventions, incident learnings, API contracts not enforced by types, debugging traps, cross-cutting decisions. Skip: anything derivable from code, session-local play-by-play (use `save_note`), steering-doc content.
|
||||
|
||||
## Session workflow
|
||||
|
||||
1. Call `index_health` to confirm Gortex is running and indexed (`graph_stats` for counts). If `total_nodes` is 0, call `index_repository` with path `"."`.
|
||||
2. Call `distill_session` to recover prior session memory for this workspace.
|
||||
3. In multi-repo mode, call `get_active_project` to check scope. Use `set_active_project` to switch if needed.
|
||||
4. For a new task, call `smart_context` with the task description. Immediately after, call `surface_memories` with the same task description and the top symbol hits.
|
||||
5. Before editing any file, call `get_editing_context` first. If you've touched the symbol before, also call `query_notes symbol_id:"<id>"` and `query_memories symbol_id:"<id>"`.
|
||||
6. Before changing a function signature, call `verify_change` to catch contract violations — checks callers across all repos.
|
||||
7. Before any refactor, call `get_edit_plan` for dependency-ordered file list. Use `batch_edit` to apply atomically.
|
||||
8. Verify with the project's real build/test. Reserve `check_guards` for guard-relevant changes and `get_test_targets` to find the tests covering a substantive change (includes cross-repo test files).
|
||||
9. After making a meaningful decision or hitting a non-obvious constraint, call `save_note` so the next session can recover it. If the discovery is workspace-wide and worth teaching the team, call `store_memory` instead — that compounds across sessions.
|
||||
10. Before committing, call `detect_changes` to verify scope. Use `diff_context` for graph-enriched review.
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
=== root/.omp/mcp.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
=== home/.openclaw/openclaw.json ===
|
||||
{
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
=== root/AGENTS.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
=== root/opencode.json ===
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"gortex": {
|
||||
"command": [
|
||||
"gortex",
|
||||
"mcp"
|
||||
],
|
||||
"enabled": true,
|
||||
"environment": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
},
|
||||
"type": "local"
|
||||
}
|
||||
}
|
||||
}
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
=== root/.pi/extensions/gortex/index.ts ===
|
||||
// Gortex extension for the Pi coding agent (earendil-works/pi).
|
||||
//
|
||||
// Pi has no MCP support by design — the extension API's registerTool
|
||||
// covers it. This extension does two things:
|
||||
//
|
||||
// 1. Exposes Gortex's graph tools as native Pi tools. On every
|
||||
// session_start the daemon is brought up (ensureDaemon) and the tools
|
||||
// are (re)registered — Pi has no MCP, so the extension does what an MCP
|
||||
// client's `gortex mcp` proxy does for it, and Pi resets the session's
|
||||
// tool registry on each /new, so registration must happen per session.
|
||||
// Each registered tool then shells `gortex call <name>`, which
|
||||
// relays to the daemon. This mirrors the daemon's own core/defer tool
|
||||
// surface: the configured eager preset (GORTEX_TOOLS, default `core`)
|
||||
// is discovered from `gortex tools list` and registered up front, and
|
||||
// the deferred remainder of the full (~175-tool) catalogue is reached
|
||||
// on demand via the `gortex_tools_search` meta-tool, which runs the
|
||||
// BM25 catalogue search and dynamically registers the matches — the
|
||||
// analogue of MCP's tools_search + server-side promotion. If the
|
||||
// daemon is unreachable at session_start no graph tools are
|
||||
// registered; discovery re-runs on the next session.
|
||||
//
|
||||
// 2. Re-creates the Claude-Code "prefer graph tools over raw file
|
||||
// reads" enforcement. Rather than re-implementing the deny logic in
|
||||
// TypeScript, every relevant lifecycle event is marshalled into a
|
||||
// normalized envelope and handed to `gortex hook --agent=pi`, whose
|
||||
// decision is applied here. The Go side owns the deny / enrich /
|
||||
// consult-unlock / nudge postures, identical to every other agent.
|
||||
//
|
||||
// This file is written verbatim by the `pi` agent adapter, except for four
|
||||
// templated sentinels the adapter replaces at install time:
|
||||
//
|
||||
// "gortex" -> a JSON string: the resolved `gortex` binary path
|
||||
// ["gortex","hook","--agent=pi"] -> a JSON array: the full hook argv, e.g.
|
||||
// ["/usr/local/bin/gortex","hook","--agent=pi"]
|
||||
// (with a trailing "--mode=<mode>" when non-deny)
|
||||
// false -> a JSON boolean: whether to wire the read-discipline
|
||||
// enforcement (false when installed with --no-hooks;
|
||||
// the graph tools + orientation are still wired)
|
||||
// "core" -> a JSON string: the default eager tool preset
|
||||
// (core|edit|nav|readonly|full); GORTEX_TOOLS in the
|
||||
// environment overrides it at runtime.
|
||||
//
|
||||
// Each is emitted as a JSON literal so values containing spaces survive.
|
||||
//
|
||||
// NOTE: Pi is pre-1.0. The ExtensionAPI surface, event names, and the Pi
|
||||
// built-in tool names mapped in normalizeToolCall() below are pinned to
|
||||
// Pi's documented API (packages/coding-agent/docs/extensions.md). If a Pi
|
||||
// upgrade renames an event or a built-in tool, adjust the small maps here
|
||||
// — the Go side (the wire contract) stays unchanged.
|
||||
|
||||
// Zero runtime dependencies: only Node built-ins. The tool `parameters`
|
||||
// below are plain JSON Schema objects rather than TypeBox schemas — Pi's
|
||||
// tool layer accepts a plain JSON-schema object. This is deliberate:
|
||||
// a single-file auto-discovered extension has no package.json / node_modules,
|
||||
// so importing `typebox` would throw at load and take the whole extension
|
||||
// (tools AND enforcement) down with it.
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
|
||||
const GORTEX_BIN: string = "gortex";
|
||||
const HOOK_ARGV: string[] = ["gortex","hook","--agent=pi"];
|
||||
const ENFORCE: boolean = false;
|
||||
|
||||
// Which eager preset to expose as native Pi tools, mirroring the Gortex
|
||||
// daemon's own tool surface. The daemon defaults to the `core` preset in
|
||||
// `defer` mode and lets GORTEX_TOOLS override it — so we honour the same
|
||||
// env at runtime, falling back to the value baked at install time.
|
||||
// Recognised: core | edit | nav | readonly | full (full = the whole
|
||||
// catalogue eagerly). The deferred remainder is reachable via
|
||||
// `gortex_tools_search` below.
|
||||
const TOOLS_PRESET: string = ((process.env && process.env.GORTEX_TOOLS) || "core").trim();
|
||||
|
||||
// Names (all gortex_-prefixed) of the Gortex tools registered into Pi.
|
||||
const gortexToolNames = new Set<string>();
|
||||
|
||||
// Every Gortex tool is registered under a `gortex_` prefix so it can never
|
||||
// silently clobber a Pi built-in or another extension's tool of the same
|
||||
// name.
|
||||
// The execute() closure still calls the daemon with the bare name.
|
||||
const TOOL_PREFIX = "gortex_";
|
||||
function piToolName(bare: string): string {
|
||||
return bare.startsWith(TOOL_PREFIX) ? bare : TOOL_PREFIX + bare;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Subprocess helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function runGortex(args: string[], input?: string): string {
|
||||
return execFileSync(GORTEX_BIN, args, {
|
||||
input,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
timeout: 30_000,
|
||||
}).trim();
|
||||
}
|
||||
|
||||
// ensureDaemon brings the shared Gortex daemon up, the way an MCP host's
|
||||
// `gortex mcp` does for other agents.
|
||||
// Idempotent, fire-and-forget (we don't block on the launcher's ≤60s
|
||||
// readiness poll), and never throws — a missing binary or spawn failure
|
||||
// must not take the extension down. The first tool calls either find it
|
||||
// ready or degrade gracefully.
|
||||
function ensureDaemon(): void {
|
||||
try {
|
||||
const child = spawn(GORTEX_BIN, ["daemon", "start", "--detach"], {
|
||||
stdio: "ignore",
|
||||
detached: true,
|
||||
});
|
||||
child.on("error", () => { }); // binary missing / spawn failure — swallow
|
||||
// No teardown counterpart, by design: the daemon is shared, long-lived
|
||||
// infrastructure that outlives the session — like `gortex mcp`.
|
||||
child.unref();
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
interface PiDecision {
|
||||
block?: boolean;
|
||||
reason?: string;
|
||||
additional_context?: string;
|
||||
orientation?: string;
|
||||
}
|
||||
|
||||
// callHook sends a normalized event envelope to `gortex hook --agent=pi`
|
||||
// and parses the PiDecision it writes back. Fail-open: any error returns
|
||||
// an empty decision so the extension never blocks Pi's flow on a hook
|
||||
// hiccup (daemon down, parse error, timeout).
|
||||
function callHook(envelope: Record<string, unknown>): PiDecision {
|
||||
try {
|
||||
const out = execFileSync(HOOK_ARGV[0], HOOK_ARGV.slice(1), {
|
||||
input: JSON.stringify(envelope),
|
||||
encoding: "utf8",
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
timeout: 5_000,
|
||||
}).trim();
|
||||
if (!out) return {};
|
||||
return JSON.parse(out) as PiDecision;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool-call normalization (Pi vocabulary -> canonical Claude-Code vocabulary)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// The Go enrich() classifier switches on Claude-Code tool names
|
||||
// ("Read"/"Grep"/"Glob"/"Bash"/"Edit"/"Write") and reads canonical input
|
||||
// keys ("file_path"/"pattern"/"command"). Pi uses its own lowercase names
|
||||
// and input shapes, so we translate here — keeping all Pi-specific
|
||||
// knowledge in this Pi-specific file.
|
||||
const toolNameMap: Record<string, string> = {
|
||||
read: "Read",
|
||||
grep: "Grep",
|
||||
find: "Glob",
|
||||
ls: "Glob",
|
||||
bash: "Bash",
|
||||
edit: "Edit",
|
||||
write: "Write",
|
||||
};
|
||||
|
||||
function firstString(obj: Record<string, unknown>, keys: string[]): string | undefined {
|
||||
for (const k of keys) {
|
||||
const v = obj[k];
|
||||
if (typeof v === "string" && v !== "") return v;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizeToolCall(
|
||||
piName: string,
|
||||
piInput: Record<string, unknown>,
|
||||
): { tool_name: string; tool_input: Record<string, unknown> } {
|
||||
const canonical = toolNameMap[piName.toLowerCase()] ?? piName;
|
||||
const out: Record<string, unknown> = { ...piInput };
|
||||
|
||||
const path = firstString(piInput, ["file_path", "path", "file", "filepath", "absolute_path"]);
|
||||
if (path !== undefined) out.file_path = path;
|
||||
|
||||
let pattern = firstString(piInput, ["pattern", "glob", "query", "regex", "name"]);
|
||||
if (pattern === undefined && canonical === "Glob") pattern = path;
|
||||
if (pattern !== undefined) out.pattern = pattern;
|
||||
|
||||
const command = firstString(piInput, ["command", "cmd", "script"]);
|
||||
if (command !== undefined) out.command = command;
|
||||
|
||||
return { tool_name: canonical, tool_input: out };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dynamic tool registration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface ToolDescriptor {
|
||||
name: string;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// presetListArgs maps the configured preset to `gortex tools list` flags.
|
||||
// `full`/`all`/empty list the whole catalogue (no --preset filter); any
|
||||
// other value is passed straight through so a preset added upstream works
|
||||
// with no change here — the CLI is the source of truth for valid names. An
|
||||
// unrecognised name simply matches nothing, and discoverTools falls back to
|
||||
// the static set; the full catalogue stays reachable via search regardless.
|
||||
function presetListArgs(): string[] {
|
||||
const base = ["tools", "list", "--format", "json"];
|
||||
const p = TOOLS_PRESET.toLowerCase();
|
||||
if (p === "" || p === "full" || p === "all") return base;
|
||||
return [...base, "--preset", p];
|
||||
}
|
||||
|
||||
// discoverTools asks the daemon for the eager tool roster. Returns [] when
|
||||
// the daemon is unreachable.
|
||||
// TODO: poll for the daemon coming online mid-session and register then.
|
||||
function discoverTools(): ToolDescriptor[] {
|
||||
try {
|
||||
const raw = runGortex(presetListArgs());
|
||||
const parsed = JSON.parse(raw) as ToolDescriptor[];
|
||||
if (Array.isArray(parsed) && parsed.length > 0) return parsed;
|
||||
} catch {
|
||||
// daemon down / not tracking — no tools registered this session.
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// safeRegister registers a Pi tool, tolerating a redundant registration —
|
||||
// a config reload can re-fire session_start without resetting the session's
|
||||
// tool registry, in which case the name is already live and Pi may throw.
|
||||
function safeRegister(pi: any, def: any): void {
|
||||
try {
|
||||
pi.registerTool(def);
|
||||
} catch {
|
||||
// already live this session — the existing registration stands.
|
||||
}
|
||||
}
|
||||
|
||||
// registerOneTool registers a single Gortex tool as a native Pi tool named
|
||||
// `gortex_<bare>`, whose execute() shells `gortex call <bare>`. Idempotent —
|
||||
// a name already registered is skipped. Adds the prefixed name to
|
||||
// gortexToolNames so the read-discipline postures treat a call to it as a
|
||||
// graph query, not a raw file read. Used both for the eager preset and for
|
||||
// tools promoted later by gortex_tools_search.
|
||||
function registerOneTool(pi: any, desc: ToolDescriptor): void {
|
||||
const bare = desc.name;
|
||||
if (!bare) return;
|
||||
const name = piToolName(bare);
|
||||
if (gortexToolNames.has(name)) return;
|
||||
gortexToolNames.add(name);
|
||||
safeRegister(pi, {
|
||||
name,
|
||||
label: name,
|
||||
description: (desc.description || desc.summary || name).trim(),
|
||||
// Pass-through arguments: the model supplies whatever the underlying
|
||||
// Gortex tool accepts; `gortex call` validates names + args server
|
||||
// side and suggests near-matches on a typo.
|
||||
parameters: { type: "object", additionalProperties: true, properties: {} },
|
||||
async execute(_id: string, params: Record<string, unknown>) {
|
||||
try {
|
||||
const out = runGortex([
|
||||
"call",
|
||||
bare,
|
||||
"--json",
|
||||
JSON.stringify(params ?? {}),
|
||||
"--format",
|
||||
"gcx",
|
||||
]);
|
||||
return { content: [{ type: "text", text: out }], details: {} };
|
||||
} catch (err: any) {
|
||||
const msg = err?.stderr?.toString?.() || err?.message || String(err);
|
||||
throw new Error(`gortex call ${bare} failed: ${msg}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function registerGortexTools(pi: any): void {
|
||||
for (const desc of discoverTools()) registerOneTool(pi, desc);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// On-demand tool discovery (mirrors MCP tools_search + promotion)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// MCP clients on the default core/defer surface see only the eager preset;
|
||||
// the rest of the catalogue is deferred behind the tools_search meta-tool,
|
||||
// which returns a match's schema and PROMOTES it into the live server
|
||||
// (firing notifications/tools/list_changed) so it becomes callable. Pi has
|
||||
// no server-side promotion, so we replicate it: a single
|
||||
// `gortex_tools_search` meta-tool runs the BM25 catalogue search and
|
||||
// dynamically registers each match as a native Pi tool — Pi supports
|
||||
// registering tools after session init.
|
||||
|
||||
const SEARCH_TOOL_NAME = piToolName("tools_search");
|
||||
|
||||
// firstSentence returns the leading sentence of a description, up to and
|
||||
// including the first ". " boundary.
|
||||
function firstSentence(desc: string): string {
|
||||
const t = desc.trim();
|
||||
if (!t) return "";
|
||||
const i = t.indexOf(". ");
|
||||
return i >= 0 ? t.slice(0, i + 1).trim() : t;
|
||||
}
|
||||
|
||||
// parseToolSearchJSON parses `gortex tools search --format json` output —
|
||||
// an array of {name, description}.
|
||||
function parseToolSearchJSON(raw: string): ToolDescriptor[] {
|
||||
let parsed: Array<{ name: string; description?: string }>;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed
|
||||
.filter((e) => e && typeof e.name === "string" && e.name)
|
||||
.map((e) => ({
|
||||
name: e.name,
|
||||
description: e.description ?? "",
|
||||
summary: firstSentence(e.description ?? ""),
|
||||
}));
|
||||
}
|
||||
|
||||
function searchTools(query: string, limit: number): ToolDescriptor[] {
|
||||
return parseToolSearchJSON(
|
||||
runGortex(["tools", "search", query, "--limit", String(limit), "--format", "json"]),
|
||||
);
|
||||
}
|
||||
|
||||
function registerSearchTool(pi: any): void {
|
||||
if (gortexToolNames.has(SEARCH_TOOL_NAME)) return;
|
||||
gortexToolNames.add(SEARCH_TOOL_NAME);
|
||||
safeRegister(pi, {
|
||||
name: SEARCH_TOOL_NAME,
|
||||
label: SEARCH_TOOL_NAME,
|
||||
description:
|
||||
"Search the full Gortex tool catalogue and register the matches as callable tools. " +
|
||||
"Use this when the eagerly-loaded set doesn't cover what you need — e.g. taint_paths, " +
|
||||
"flow_between, contracts, find_clones, pr_risk, overlay/simulation tools. Returns the " +
|
||||
"names + summaries now available; call them directly on the next turn. (Gortex graph tool.)",
|
||||
parameters: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "What you want to do, e.g. 'trace taint to a sink' or 'detect duplicate code'.",
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Maximum number of tools to register (default 5).",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
async execute(_id: string, params: Record<string, unknown>) {
|
||||
const query = typeof params?.query === "string" ? params.query.trim() : "";
|
||||
if (!query) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Provide a 'query' describing the tool you need." }],
|
||||
details: {},
|
||||
};
|
||||
}
|
||||
const rawLimit = typeof params?.limit === "number" ? Math.floor(params.limit) : 5;
|
||||
const limit = rawLimit > 0 ? rawLimit : 5;
|
||||
|
||||
let matches: ToolDescriptor[];
|
||||
try {
|
||||
matches = searchTools(query, limit);
|
||||
} catch (err: any) {
|
||||
const msg = err?.stderr?.toString?.() || err?.message || String(err);
|
||||
throw new Error(`gortex tools search failed: ${msg}`);
|
||||
}
|
||||
|
||||
const newlyAvailable: string[] = [];
|
||||
for (const desc of matches) {
|
||||
const name = desc.name ? piToolName(desc.name) : "";
|
||||
if (!name || name === SEARCH_TOOL_NAME) continue;
|
||||
if (!gortexToolNames.has(name)) newlyAvailable.push(name);
|
||||
registerOneTool(pi, desc); // idempotent
|
||||
}
|
||||
|
||||
// Report the prefixed names — that's what the model calls them by.
|
||||
const lines = matches
|
||||
.filter((d) => d.name && piToolName(d.name) !== SEARCH_TOOL_NAME)
|
||||
.map((d) => `- ${piToolName(d.name)}${d.summary ? ` — ${d.summary}` : ""}`);
|
||||
const header =
|
||||
matches.length === 0
|
||||
? `No tools matched "${query}".`
|
||||
: newlyAvailable.length > 0
|
||||
? `Registered ${newlyAvailable.length} tool(s) — now callable: ${newlyAvailable.join(", ")}.`
|
||||
: "Matching tools are already registered and callable.";
|
||||
return { content: [{ type: "text", text: [header, ...lines].join("\n") }], details: {} };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extension entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function (pi: any) {
|
||||
let orientationInjected = false;
|
||||
// Orientation awaiting injection into the next LLM call. The `context`
|
||||
// hook appends it as a tail user message rather than mutating
|
||||
// systemPrompt: a systemPrompt change sits at messages[0] and invalidates
|
||||
// prefix prompt caching. Computed once per session.
|
||||
let pendingOrientation = "";
|
||||
|
||||
// Pi resets the session's tool registry on every session_start, so
|
||||
// (re)register here. Clear the name guard first — it persists across
|
||||
// sessions and would otherwise suppress re-registration.
|
||||
pi.on("session_start", () => {
|
||||
orientationInjected = false;
|
||||
pendingOrientation = "";
|
||||
ensureDaemon();
|
||||
gortexToolNames.clear();
|
||||
registerGortexTools(pi);
|
||||
registerSearchTool(pi);
|
||||
});
|
||||
|
||||
// Fires before the agent loop's first LLM call. It can't mutate messages
|
||||
// itself, so it just parks the orientation for the `context` hook.
|
||||
pi.on("before_agent_start", () => {
|
||||
if (orientationInjected) return;
|
||||
const decision = callHook({ event: "session_start", cwd: pi?.cwd ?? process.cwd() });
|
||||
if (decision.orientation) {
|
||||
pendingOrientation = decision.orientation;
|
||||
orientationInjected = true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
// Fires before each LLM call with a mutable message array. Append the
|
||||
// parked orientation once, then clear it so it isn't repeated each call.
|
||||
pi.on("context", (event: any) => {
|
||||
if (!pendingOrientation) return;
|
||||
const text = pendingOrientation;
|
||||
pendingOrientation = "";
|
||||
try {
|
||||
if (event && Array.isArray(event.messages)) {
|
||||
event.messages.push({ role: "user", content: text });
|
||||
return { messages: event.messages };
|
||||
}
|
||||
} catch {
|
||||
// best effort — never break context assembly.
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
if (!ENFORCE) return;
|
||||
|
||||
// Enforcement: every non-Gortex tool call is checked against the Go hook.
|
||||
pi.on("tool_call", async (event: any, ctx: any) => {
|
||||
const piName: string = event?.toolName ?? "";
|
||||
const piInput: Record<string, unknown> = event?.input ?? {};
|
||||
const isGortexTool = gortexToolNames.has(piName);
|
||||
|
||||
const norm = normalizeToolCall(piName, piInput);
|
||||
const decision = callHook({
|
||||
event: "tool_call",
|
||||
tool_name: norm.tool_name,
|
||||
tool_input: norm.tool_input,
|
||||
cwd: ctx?.cwd ?? pi?.cwd ?? process.cwd(),
|
||||
session_id: ctx?.sessionManager?.sessionId ?? "",
|
||||
is_gortex_tool: isGortexTool,
|
||||
});
|
||||
|
||||
if (decision.block) {
|
||||
return { block: true, reason: decision.reason ?? "[Gortex] blocked — prefer graph tools." };
|
||||
}
|
||||
if (decision.additional_context) {
|
||||
// Soft guidance: surface it without blocking the call.
|
||||
try {
|
||||
pi.sendMessage(
|
||||
{ customType: "gortex", content: decision.additional_context, display: true },
|
||||
{ deliverAs: "steer" },
|
||||
);
|
||||
} catch {
|
||||
// sendMessage shape can vary across Pi versions; never fatal.
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
=== root/AGENTS.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== root/.github/copilot-instructions.md ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
=== root/.vscode/mcp.json ===
|
||||
{
|
||||
"servers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== home/.codeium/mcp_config.json ===
|
||||
{
|
||||
"mcpServers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/.windsurfrules ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
=== home/.config/zed/settings.json ===
|
||||
{
|
||||
"context_servers": {
|
||||
"gortex": {
|
||||
"args": [
|
||||
"mcp"
|
||||
],
|
||||
"command": "gortex",
|
||||
"env": {
|
||||
"GORTEX_INDEX_WORKERS": "8"
|
||||
},
|
||||
"source": "custom"
|
||||
}
|
||||
}
|
||||
}
|
||||
=== root/.rules ===
|
||||
<!-- gortex:communities:start -->
|
||||
- [example-community](.claude/skills/example/SKILL.md) — example routing block
|
||||
|
||||
<!-- gortex:communities:end -->
|
||||
Reference in New Issue
Block a user