{"id":48170597,"url":"https://github.com/cortexkit/aft","last_synced_at":"2026-04-16T14:11:42.764Z","repository":{"id":344643518,"uuid":"1182429846","full_name":"cortexkit/aft","owner":"cortexkit","description":"Tree-sitter powered code analysis tools for AI coding agents","archived":false,"fork":false,"pushed_at":"2026-04-02T09:41:29.000Z","size":4823,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T18:46:22.568Z","etag":null,"topics":["agent-framework","ai","ai-agents","ai-agents-framework","opencode"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cortexkit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-15T14:10:55.000Z","updated_at":"2026-04-04T00:04:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cortexkit/aft","commit_stats":null,"previous_names":["ualtinok/aft","cortexkit/aft"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/cortexkit/aft","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortexkit%2Faft","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortexkit%2Faft/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortexkit%2Faft/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortexkit%2Faft/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cortexkit","download_url":"https://codeload.github.com/cortexkit/aft/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cortexkit%2Faft/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31504897,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-framework","ai","ai-agents","ai-agents-framework","opencode"],"created_at":"2026-04-04T17:33:41.654Z","updated_at":"2026-04-16T14:11:42.744Z","avatar_url":"https://github.com/cortexkit.png","language":"Rust","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/banner.jpeg\" alt=\"AFT — Agent File Toolkit\" width=\"50%\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eAFT — Agent File Toolkit\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eTree-sitter powered code analysis tools for AI coding agents.\u003c/strong\u003e\u003cbr\u003e\n  Semantic editing, call-graph navigation, and structural search — all in one toolkit.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://crates.io/crates/agent-file-tools\"\u003e\u003cimg src=\"https://img.shields.io/crates/v/agent-file-tools?label=crate\u0026color=blue\u0026style=flat-square\" alt=\"crates.io\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@cortexkit/aft-opencode\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/@cortexkit/aft-opencode?color=blue\u0026style=flat-square\" alt=\"npm\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/cortexkit/aft/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-green?style=flat-square\" alt=\"MIT License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#get-started\"\u003eGet Started\u003c/a\u003e ·\n  \u003ca href=\"#what-is-aft\"\u003eWhat is AFT?\u003c/a\u003e ·\n  \u003ca href=\"#search-benchmarks\"\u003eBenchmarks\u003c/a\u003e ·\n  \u003ca href=\"#features\"\u003eFeatures\u003c/a\u003e ·\n  \u003ca href=\"#tool-reference\"\u003eTool Reference\u003c/a\u003e ·\n  \u003ca href=\"#configuration\"\u003eConfiguration\u003c/a\u003e ·\n  \u003ca href=\"#architecture\"\u003eArchitecture\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Get Started\n\n### OpenCode\n\nRun the setup wizard — it registers AFT in your OpenCode and TUI config and asks which experimental features to enable:\n\n```bash\nbunx @cortexkit/aft-opencode@latest setup\n```\n\nThat's it. On the next session start, the binary downloads if needed and all tools become\navailable. AFT replaces opencode's built-in `read`, `write`, `edit`, `apply_patch`,\n`ast_grep_search`, `ast_grep_replace`, and `lsp_diagnostics` with enhanced versions — all\npowered natively by AFT — plus adds the `aft_` family of semantic tools on top.\n\n\u003cdetails\u003e\n\u003csummary\u003eManual install\u003c/summary\u003e\n\nIf you prefer to edit your config by hand:\n\n```\nopencode plugin --global @cortexkit/aft-opencode@latest\n```\n\nor\n\n```json\n// ~/.config/opencode/config.json\n{\n  \"plugin\": [\"@cortexkit/aft-opencode@latest\"]\n}\n```\n\u003c/details\u003e\n\n### CLI Commands\n\nAFT ships a standalone CLI for setup, diagnostics, and issue reporting:\n\n| Command | What it does |\n|---|---|\n| `bunx @cortexkit/aft-opencode@latest setup` | Interactive first-time setup — registers plugin entries, enables experimental features |\n| `bunx @cortexkit/aft-opencode@latest doctor` | Check configuration and auto-fix common issues |\n| `bunx @cortexkit/aft-opencode@latest doctor --force` | Force-clear the OpenCode plugin cache (fixes stale `@latest` resolution) |\n| `bunx @cortexkit/aft-opencode@latest doctor --issue` | Collect diagnostics and open a GitHub issue with sanitized logs |\n\n**`setup`** — Interactive wizard that registers AFT in your OpenCode and TUI config, asks whether to enable `experimental_search_index` and `experimental_semantic_search`, and writes the result to `~/.config/opencode/aft.jsonc`. Run this once after installing.\n\n**`doctor`** — Checks everything that can go wrong: OpenCode install, plugin registration, plugin cache version, binary cache, config parse errors, ONNX Runtime availability (for semantic search), storage directory sizes, and log file status. Auto-fixes missing plugin entries and outdated plugin caches.\n\n**`doctor --force`** — Same as `doctor` but always clears the OpenCode plugin cache, forcing a fresh download of the latest plugin version. Use this when you're on an old version and `@latest` doesn't seem to update (OpenCode caches npm packages aggressively).\n\n**`doctor --issue`** — Collects a full diagnostic report, sanitizes your username and home path out of the logs, and opens a pre-filled GitHub issue on [cortexkit/aft](https://github.com/cortexkit/aft/issues). If you have `gh` installed, it submits directly; otherwise it writes the report to `./aft-issue-\u003ctimestamp\u003e.md` and opens the GitHub new-issue page in your browser so you can paste it in.\n\n---\n\n## What is AFT?\n\nAI coding agents are fast, but their interaction with code is often blunt. The typical pattern:\nread an entire file to find one function, construct a diff from memory, apply it by line number,\nand hope nothing shifted. Tokens burned on context noise. Edits that break when the file changes.\nNavigation that requires reading three files to answer \"what calls this?\"\n\nAFT is a toolkit built on top of tree-sitter's concrete syntax trees. Every operation addresses\ncode by what it *is* — a function, a class, a call site, a symbol — not by where it happens to\nsit in a file right now. Agents can outline a file's structure in one call, zoom into a single\nfunction, edit it by name, then follow its callers across the workspace. All without reading a\nsingle line they don't need.\n\nAFT **hoists** itself into opencode's built-in tool slots. The `read`, `write`, `edit`,\n`apply_patch`, `ast_grep_search`, `ast_grep_replace`, and `lsp_diagnostics` tools are replaced\nby AFT-enhanced versions — same names the agent already knows, but now backed by the Rust binary\nfor backups, formatting, inline diagnostics, and symbol-aware operations. With the experimental\nsearch index enabled, `grep` and `glob` are also hoisted with a trigram index for sub-millisecond\nsearch on any project size.\n\nThe toolkit is a two-component system: a Rust binary that does the heavy lifting (parsing,\nanalysis, edits, formatting) and a TypeScript plugin that integrates with OpenCode. The binary\nships pre-built for all major platforms and downloads automatically on first use — no install\nceremony required.\n\n---\n\n## How it Helps Agents\n\n**The token problem.** A 500-line file costs ~375 tokens to read. Most of the time, the agent\nneeds one function. `aft_zoom` with a `symbol` param returns that function plus a few lines of\ncontext: ~40 tokens. Over a multi-step task, the savings compound fast.\n\n**The fragile-edit problem.** Line-number edits break the moment anything above the target moves.\n`edit` in symbol mode addresses the function by name. The agent writes the new body; AFT finds\nthe symbol, replaces it, validates syntax, and runs the formatter. Nothing to count.\n\n**The navigation problem.** \"Where is this function called?\" means grep or reading every importer.\n`aft_navigate` with `callers` mode returns every call site across the workspace in one round trip.\n`impact` mode goes further: it tells the agent what else breaks if that function's signature changes.\n\nHere's a typical agent workflow:\n\n**1. Get the file structure:**\n\n```json\n// aft_outline\n{ \"filePath\": \"src/auth/session.ts\" }\n```\n\n```\nsrc/auth/session.ts\n  E fn    createSession(userId: string, opts?: SessionOpts): Promise\u003cSession\u003e 12:38\n  E fn    validateToken(token: string): boolean 40:52\n  E fn    refreshSession(sessionId: string): Promise\u003cSession\u003e 54:71\n  - fn    signPayload(data: Record\u003cstring, unknown\u003e): string 73:80\n  E type  SessionOpts 5:10\n  E var   SESSION_TTL 3:3\n```\n\n**2. Zoom into the specific function:**\n\n```json\n// aft_zoom\n{ \"filePath\": \"src/auth/session.ts\", \"symbol\": \"validateToken\" }\n```\n\n```\nsrc/auth/session.ts:40-52\n  calls_out: verifyJwt (src/auth/jwt.ts:8), isExpired (src/auth/utils.ts:15)\n  called_by: authMiddleware (src/middleware/auth.ts:22), handleLogin (src/routes/login.ts:45)\n\n  37: // --- context_before ---\n  38:\n  39: /** Validate a JWT token and check expiration. */\n  40: export function validateToken(token: string): boolean {\n  41:   if (!token) return false;\n  42:   const decoded = verifyJwt(token);\n  43:   if (!decoded) return false;\n  44:   return !isExpired(decoded.exp);\n  45: }\n  46:\n  47: // --- context_after ---\n  48: export function refreshSession(sessionId: string): Promise\u003cSession\u003e {\n```\n\n**3. Edit it by name:**\n\n```json\n// edit\n{\n  \"filePath\": \"src/auth/session.ts\",\n  \"symbol\": \"validateToken\",\n  \"content\": \"export function validateToken(token: string): boolean {\\n  if (!token) return false;\\n  return verifyJwt(token);\\n}\"\n}\n```\n\n**4. Check who calls it before changing its signature:**\n\n```json\n// aft_navigate\n{ \"op\": \"callers\", \"filePath\": \"src/auth/session.ts\", \"symbol\": \"validateToken\", \"depth\": 2 }\n```\n\n---\n\n## Search Benchmarks\n\nWith `experimental_search_index: true`, AFT builds a trigram index in the background and serves\ngrep queries from memory. Here's how it compares to ripgrep on real codebases:\n\n### opencode-aft (253 files)\n\n| Query | ripgrep | AFT | Speedup |\n|-------|---------|-----|---------|\n| `validate_path` | 31.4ms | 1.48ms | **21x** |\n| `BinaryBridge` | 31.0ms | 1.3ms | **24x** |\n| `fn handle_grep` | 31.3ms | 0.2ms | **136x** |\n| `experimental_search_index` | 31.5ms | 0.4ms | **71x** |\n\n### reth (1,878 Rust files)\n\n| Query | ripgrep | AFT | Speedup |\n|-------|---------|-----|---------|\n| `impl Display for` | 98.9ms | 1.10ms | **90x** |\n| `BlockNumber` | 61.6ms | 2.19ms | **28x** |\n| `EthApiError` | 32.7ms | 1.31ms | **25x** |\n| `fn execute` | 36.6ms | 2.19ms | **17x** |\n\n### Chromium/base (3,953 C++ files)\n\n| Query | ripgrep | AFT | Speedup |\n|-------|---------|-----|---------|\n| `WebContents` | 69.5ms | 0.29ms | **236x** |\n| `StringPiece` | 51.8ms | 0.78ms | **66x** |\n| `NOTREACHED` | 51.6ms | 2.16ms | **24x** |\n| `base::Value` | 54.4ms | 1.13ms | **48x** |\n\nRare queries see the biggest gains — the trigram index narrows candidates to a few files instantly.\nHigh-match queries still benefit from `memchr` SIMD scanning and early termination.\n\nIndex builds in ~2s for most projects (under 2K files). Larger codebases like Chromium/base\n(~4K files) take ~2 minutes for the initial build. Once built, the index persists to disk for\ninstant cold starts and stays fresh via file watcher and mtime verification.\n\n---\n\n## Features\n\n- **File read** — line-numbered file content, directory listing, and image/PDF detection\n- **Semantic outline** — list all symbols in a file (or several files, or a directory) with kind, name, line range, visibility\n- **Symbol editing** — replace a named symbol by name with auto-format and syntax validation\n- **Match editing** — find-and-replace by content with fuzzy fallback (4-pass: exact → trim trailing → trim both → normalize Unicode)\n- **Batch \u0026 transaction edits** — atomic multi-edit within a file, or atomic multi-file edits with rollback\n- **Glob replace** — pattern replace across all matching files in one call\n- **Patch apply** — multi-file `*** Begin Patch` format for creates, updates, deletes, and moves\n- **Call tree \u0026 callers** — forward call graph and reverse lookup across the workspace\n- **Trace-to \u0026 impact analysis** — how does execution reach this function? what breaks if it changes?\n- **Data flow tracing** — follow a value through assignments and parameters across files\n- **Auto-format \u0026 auto-backup** — every edit formats the file and saves a snapshot for undo\n- **Import management** — add, remove, organize imports language-aware (TS/JS/TSX/Python/Rust/Go)\n- **Structural transforms** — add class members, Rust derive macros, Python decorators, Go struct tags, wrap try/catch\n- **Workspace-wide refactoring** — move symbols between files (updates all imports), extract functions, inline functions\n- **Safety \u0026 recovery** — undo last edit, named checkpoints, restore to any checkpoint\n- **AST pattern search \u0026 replace** — structural code search using meta-variables (`$VAR`, `$$$`), powered by ast-grep\n- **Git conflict viewer** — show all merge conflicts across the repository in a single call with line-numbered regions\n- **Indexed search** *(experimental)* — trigram-indexed `grep` and `glob` that hoist opencode's built-ins, with background index building, disk persistence, and compressed output mode\n- **Semantic search** *(experimental)* — search code by meaning using local embeddings (fastembed + all-MiniLM-L6-v2), with cAST-style symbol chunking, cosine similarity ranking, and disk persistence\n- **Inline diagnostics** — write and edit return LSP errors detected after the change\n- **UI metadata** — the OpenCode desktop shows file paths and diff previews (`+N/-N`) for every edit\n- **Local tool discovery** — finds biome, prettier, tsc, pyright in `node_modules/.bin` automatically\n\n---\n\n## Tool Reference\n\n\u003e **All line numbers are 1-based** (matching editor, git, and compiler conventions).\n\u003e Line 1 is the first line of the file.\n\n### Hoisted tools\n\nThese replace opencode's built-ins. Registered under the same names by default. When\n`hoist_builtin_tools: false`, they get the `aft_` prefix instead (e.g. `aft_read`).\n\n| Tool | Replaces | Description | Key Params |\n|------|----------|-------------|------------|\n| `read` | opencode read | File read, directory listing, image/PDF detection | `filePath`, `startLine`, `endLine`, `offset`, `limit` |\n| `write` | opencode write | Write file with auto-dirs, backup, format, inline diagnostics | `filePath`, `content` |\n| `edit` | opencode edit | Find/replace, symbol replace, batch, transaction, glob | `filePath`, `oldString`, `newString`, `symbol`, `content`, `edits[]` |\n| `apply_patch` | opencode apply_patch | `*** Begin Patch` multi-file patch format | `patchText` |\n| `ast_grep_search` | oh-my-opencode ast_grep | AST pattern search with meta-variables | `pattern`, `lang`, `paths[]`, `globs[]` |\n| `ast_grep_replace` | oh-my-opencode ast_grep | AST pattern replace (applies by default) | `pattern`, `rewrite`, `lang`, `dryRun` |\n| `lsp_diagnostics` | opencode lsp_diagnostics | Errors/warnings from language server | `filePath`, `directory`, `severity`, `waitMs` |\n| `grep` | opencode grep *(experimental)* | Trigram-indexed regex search with compressed output | `pattern`, `path`, `include`, `exclude` |\n| `glob` | opencode glob *(experimental)* | Indexed file discovery with compressed output | `pattern`, `path` |\n\n### AFT-only tools\n\nAlways registered with `aft_` prefix regardless of hoisting setting.\n\n**Recommended tier** (default):\n\n| Tool | Description | Key Params |\n|------|-------------|------------|\n| `aft_outline` | Structural outline of a file, files, or directory | `filePath`, `files[]`, `directory` |\n| `aft_zoom` | Inspect symbols with call-graph annotations | `filePath`, `symbol`, `symbols[]` |\n| `aft_import` | Language-aware import add/remove/organize | `op`, `filePath`, `module`, `names[]` |\n| `aft_conflicts` | Show all git merge conflicts with line-numbered regions | *(none)* |\n| `aft_search` | Semantic code search by meaning *(experimental)* | `query`, `topK` |\n| `aft_safety` | Undo, history, checkpoints, restore | `op`, `filePath`, `name` |\n\n**All tier** (set `tool_surface: \"all\"`):\n\n| Tool | Description | Key Params |\n|------|-------------|------------|\n| `aft_delete` | Delete a file with backup | `filePath` |\n| `aft_move` | Move or rename a file with backup | `filePath`, `destination` |\n| `aft_navigate` | Call graph and data-flow navigation | `op`, `filePath`, `symbol`, `depth` |\n| `aft_transform` | Structural code transforms (members, derives, decorators) | `op`, `filePath`, `container`, `target` |\n| `aft_refactor` | Workspace-wide move, extract, inline | `op`, `filePath`, `symbol`, `destination` |\n\n---\n\n### read\n\nPlain file reading and directory listing. Pass `filePath` to read a file, or a directory path to\nlist its entries. Paginate large files with `startLine`/`endLine` or `offset`/`limit`.\n\n```json\n// Read full file\n{ \"filePath\": \"src/app.ts\" }\n\n// Read lines 50-100\n{ \"filePath\": \"src/app.ts\", \"startLine\": 50, \"endLine\": 100 }\n\n// Read 30 lines from line 200\n{ \"filePath\": \"src/app.ts\", \"offset\": 200, \"limit\": 30 }\n\n// List directory\n{ \"filePath\": \"src/\" }\n```\n\nReturns line-numbered content (e.g. `1: const x = 1`). Directories return sorted entries with\ntrailing `/` for subdirectories. Binary files return a size-only message. Image and PDF files\nreturn metadata suitable for UI preview. Output is capped at 50KB.\n\nFor symbol inspection with call-graph annotations, use `aft_zoom`.\n\n---\n\n### write\n\nWrite the full content of a file. Creates the file (and any missing parent directories) if it\ndoesn't exist. Backs up any existing content before overwriting.\n\n```json\n{ \"filePath\": \"src/config.ts\", \"content\": \"export const TIMEOUT = 10000;\\n\" }\n```\n\nReturns inline LSP diagnostics if type errors are introduced. Auto-formats using the project's\nconfigured formatter (biome, prettier, etc.).\n\nFor partial edits (find/replace), use `edit` instead.\n\n---\n\n### edit\n\nThe main editing tool. Mode is determined by which parameters you pass:\n\n**Find and replace** — pass `filePath` + `oldString` + `newString`:\n\n```json\n{ \"filePath\": \"src/config.ts\", \"oldString\": \"const TIMEOUT = 5000\", \"newString\": \"const TIMEOUT = 10000\" }\n```\n\nMatching uses a 4-pass fuzzy fallback: exact match first, then trailing-whitespace trim, then\nboth-ends trim, then Unicode normalization. Returns an error if multiple matches exist — use\n`occurrence: N` (0-indexed) to pick one, or `replaceAll: true` to replace all.\n\n**Symbol replace** — pass `filePath` + `symbol` + `content`:\n\n```json\n{\n  \"filePath\": \"src/utils.ts\",\n  \"symbol\": \"formatDate\",\n  \"content\": \"export function formatDate(d: Date): string {\\n  return d.toISOString().split('T')[0];\\n}\"\n}\n```\n\nIncludes decorators, doc comments, and attributes in the replacement range.\n\n**Batch edits** — pass `filePath` + `edits` array. Atomic: all edits apply or none do.\n\n```json\n{\n  \"filePath\": \"src/constants.ts\",\n  \"edits\": [\n    { \"oldString\": \"VERSION = '1.0'\", \"newString\": \"VERSION = '2.0'\" },\n    { \"startLine\": 5, \"endLine\": 7, \"content\": \"// updated header\\n\" }\n  ]\n}\n```\n\nSet `content` to `\"\"` to delete lines. Per-edit `occurrence` is supported.\n\n**Multi-file transaction** — pass `operations` array. Rolls back all files if any operation fails.\n\n```json\n{\n  \"operations\": [\n    { \"file\": \"a.ts\", \"command\": \"write\", \"content\": \"...\" },\n    { \"file\": \"b.ts\", \"command\": \"edit_match\", \"match\": \"x\", \"replacement\": \"y\" }\n  ]\n}\n```\n\n**Glob replace** — use a glob as `filePath` with `replaceAll: true`:\n\n```json\n{ \"filePath\": \"src/**/*.ts\", \"oldString\": \"oldName\", \"newString\": \"newName\", \"replaceAll\": true }\n```\n\nAll modes support `dryRun: true` to preview as a diff without modifying files. LSP diagnostics\nare returned automatically after every edit (unless `dryRun` is set) — if type errors are\nintroduced, they appear inline in the response.\n\n---\n\n### apply_patch\n\nApply a multi-file patch using the `*** Begin Patch` format. Creates, updates, deletes, and\nrenames files atomically — if any operation fails, all revert.\n\n```\n*** Begin Patch\n*** Add File: path/to/new-file.ts\n+line 1\n+line 2\n*** Update File: path/to/existing-file.ts\n@@ context anchor line\n-old line\n+new line\n*** Delete File: path/to/obsolete-file.ts\n*** End Patch\n```\n\nContext anchors (`@@`) use fuzzy matching to handle whitespace and Unicode differences.\nReturns LSP diagnostics inline for any updated files that introduce type errors.\n\n---\n\n### ast_grep_search\n\nSearch for structural code patterns using meta-variables. Patterns must be complete AST nodes.\n\n```json\n{ \"pattern\": \"console.log($MSG)\", \"lang\": \"typescript\" }\n```\n\n- `$VAR` matches a single AST node\n- `$$$` matches multiple nodes (variadic)\n\nReturns matches with file, line (1-based), column, matched text, and captured variable values.\nAdd `contextLines: 3` to include surrounding lines.\n\n```json\n// Find all async functions in JS/TS\n{ \"pattern\": \"async function $NAME($$$) { $$$ }\", \"lang\": \"typescript\" }\n```\n\n---\n\n### ast_grep_replace\n\nReplace structural code patterns across files. Applies changes by default — set `dryRun: true` to preview.\n\n```json\n{ \"pattern\": \"console.log($MSG)\", \"rewrite\": \"logger.info($MSG)\", \"lang\": \"typescript\" }\n```\n\nMeta-variables captured in `pattern` are available in `rewrite`. Returns unified diffs per file\nin dry-run mode, or writes changes with backups when applied.\n\n---\n\n### lsp_diagnostics\n\nGet errors, warnings, and hints from the language server. Lazily spawns the appropriate server\n(typescript-language-server, pyright, rust-analyzer, gopls) on first use.\n\n```json\n// Check a single file\n{ \"filePath\": \"src/api.ts\", \"severity\": \"error\" }\n\n// Check all files in a directory\n{ \"directory\": \"src/\", \"severity\": \"all\" }\n\n// Wait for fresh diagnostics after an edit\n{ \"filePath\": \"src/api.ts\", \"waitMs\": 2000 }\n```\n\nReturns `{ file, line, column, severity, message, code }` per diagnostic.\n\n---\n\n### aft_outline\n\nReturns all top-level symbols in a file with their kind, name, line range, visibility, and nested\n`members` (methods in classes, sub-headings in Markdown). Accepts a single `filePath`, a `files`\narray, or a `directory` to outline all source files recursively.\n\nFor **Markdown** files (`.md`, `.mdx`): returns heading hierarchy with section ranges — each\nheading becomes a symbol you can read by name.\n\n```json\n// Outline two files at once\n{ \"files\": [\"src/server.ts\", \"src/router.ts\"] }\n\n// Outline all source files in a directory\n{ \"directory\": \"src/auth\" }\n```\n\n---\n\n### aft_zoom\n\nInspect code symbols with call-graph annotations. Returns the full source of named symbols with\n`calls_out` (what it calls) and `called_by` (what calls it) annotations.\n\nUse this when you need to understand a specific function, class, or type in detail — not for\nreading entire files (use `read` for that).\n\n```json\n// Inspect a single symbol\n{ \"filePath\": \"src/app.ts\", \"symbol\": \"handleRequest\" }\n\n// Inspect multiple symbols in one call\n{ \"filePath\": \"src/app.ts\", \"symbols\": [\"Config\", \"createApp\"] }\n```\n\nFor Markdown files, use the heading text as the symbol name (e.g. `\"symbol\": \"Architecture\"`).\n\n---\n\n### aft_conflicts\n\nShow all git merge conflicts across the repository in a single call. Auto-discovers conflicted\nfiles via `git ls-files --unmerged`, parses conflict markers, and returns line-numbered regions\nwith 3 lines of surrounding context — the same format as `read` output.\n\n```json\n{}\n```\n\nNo parameters required. Returns output like:\n\n```\n9 files, 13 conflicts\n\n── src/manager.ts [3 conflicts] ──\n\n  15:   resolveInheritedPromptTools,\n  16:   createInternalAgentTextPart,\n  17: } from \"../../shared\"\n  18: \u003c\u003c\u003c\u003c\u003c\u003c\u003c HEAD\n  19: import { normalizeAgentForPrompt } from \"../../shared/agent-display-names\"\n  20: =======\n  21: import { applySessionPromptParams } from \"../../shared/session-prompt-params-helpers\"\n  22: \u003e\u003e\u003e\u003e\u003e\u003e\u003e upstream/dev\n  23: import { setSessionTools } from \"../../shared/session-tools-store\"\n```\n\nUse `edit` with the full conflict block (including markers) as `oldString` to resolve each conflict.\n\nWhen a `git merge` or `git rebase` produces conflicts, the plugin automatically appends a hint\nsuggesting `aft_conflicts` to the bash output.\n\n---\n\n### grep *(experimental)*\n\nTrigram-indexed regex search that hoists opencode's built-in `grep`. Requires\n`experimental_search_index: true` in config. The trigram index is built in a background thread\nat session start, persisted to disk for fast cold starts, and kept fresh via file watcher.\nFalls back to direct file scanning when the index isn't ready.\n\nFor out-of-project paths, shells out to ripgrep matching opencode's exact flags.\n\n```json\n{ \"pattern\": \"handleRequest\", \"include\": \"*.ts\" }\n```\n\nReturns matches grouped by file with relative paths, sorted by modification time (newest first),\ncapped at 100 matches:\n\n```\nsrc/server.ts\n42: export async function handleRequest(req: Request) {\n89:     return handleRequest(retryReq)\n\nsrc/test/server.test.ts\n15: import { handleRequest } from \"../server\"\n\nFound 3 match(es) across 2 file(s). [index: ready]\n```\n\nFiles with more than 5 matches show the first 5 and `... and N more matches`. Lines are truncated\nat 200 characters.\n\nParameters: `pattern` (required), `path` (optional — scope to subdirectory or absolute path),\n`include` (glob filter, e.g. `\"*.ts\"`), `exclude` (negate glob), `case_sensitive` (default true).\n\n---\n\n### glob *(experimental)*\n\nIndexed file discovery that hoists opencode's built-in `glob`. Requires\n`experimental_search_index: true`. Returns absolute paths sorted by modification time,\ncapped at 100 files.\n\n```json\n{ \"pattern\": \"**/*.test.ts\" }\n```\n\nReturns relative paths. For small result sets, a flat list:\n\n```\n3 files matching **/*.test.ts\n\nsrc/server.test.ts\nsrc/utils.test.ts\nsrc/auth/login.test.ts\n```\n\nFor larger result sets (\u003e20 files), groups by directory:\n\n```\n20 files matching **/*.test.ts\n\nsrc/ (8 files)\n  server.test.ts, utils.test.ts, config.test.ts, ...\n\nsrc/auth/ (4 files)\n  login.test.ts, session.test.ts, token.test.ts, permissions.test.ts\n\n... and 8 more files in 3 directories\n```\n\nParameters: `pattern` (required), `path` (optional — scope to subdirectory or absolute path).\n\n---\n\n### aft_search *(experimental)*\n\nSemantic code search — find code by describing what it does in natural language. Requires\n`experimental_semantic_search: true` and [ONNX Runtime](https://onnxruntime.ai/) installed on the system.\nUses a local embedding model (all-MiniLM-L6-v2, ~22MB, downloaded on first use) to embed all\nsymbols in the project and match queries by cosine similarity. No API keys or external services needed.\n\n**Install ONNX Runtime:**\n- **macOS:** `brew install onnxruntime`\n- **Linux (Debian/Ubuntu):** `apt install libonnxruntime`\n- **Linux (other):** Download from [ONNX Runtime releases](https://github.com/microsoft/onnxruntime/releases)\n- **Windows:** `winget install Microsoft.ONNXRuntime`\n\nWithout ONNX Runtime, all other AFT tools work normally — only `aft_search` is unavailable.\n\n```json\n{ \"query\": \"authentication middleware that validates JWT tokens\" }\n```\n\nReturns ranked results with relevance scores and code snippets:\n\n```\ncrates/aft/src/commands/configure.rs\n  handle_configure (function, exported) 17:253 [0.42]\n    pub fn handle_configure(req: \u0026RawRequest, ctx: \u0026AppContext) -\u003e Response {\n      let root = match req.params.get(\"project_root\")...\n      ...\n\npackages/opencode-plugin/src/bridge.ts\n  checkVersion (function) 150:175 [0.38]\n    private async checkVersion(): Promise\u003cvoid\u003e {\n      ...\n\nFound 10 results [semantic index: ready]\n```\n\nThe index is built in a background thread at session start, persisted to disk for fast cold\nstart, and uses cAST-style enrichment (file path + kind + name + signature + body snippet)\nfor better embedding quality.\n\nParameters: `query` (required — natural language description), `topK` (optional — default 10).\n\n---\n\n### aft_delete\n\nDelete a file with an in-memory backup. The backup survives for the session and can be restored\nvia `aft_safety`.\n\n```json\n{ \"filePath\": \"src/deprecated/old-utils.ts\" }\n```\n\nReturns `{ file, deleted, backup_id }` on success.\n\n---\n\n### aft_move\n\nMove or rename a file. Creates parent directories for the destination automatically. Falls back\nto copy+delete for cross-filesystem moves. Backs up the original before moving.\n\n```json\n{ \"filePath\": \"src/helpers.ts\", \"destination\": \"src/utils/helpers.ts\" }\n```\n\nReturns `{ file, destination, moved, backup_id }` on success.\n\n---\n\n### aft_navigate\n\nCall graph and data-flow analysis across the workspace.\n\n| Mode | What it does |\n|------|-------------|\n| `call_tree` | What does this function call? (forward, default depth 5) |\n| `callers` | Where is this function called from? (reverse, default depth 1) |\n| `trace_to` | How does execution reach this function from entry points? |\n| `impact` | What callers are affected if this function changes? |\n| `trace_data` | Follow a value through assignments and parameters. Needs `expression`. |\n\n```json\n// Find everything that would break if processPayment changes\n{\n  \"op\": \"impact\",\n  \"filePath\": \"src/payments/processor.ts\",\n  \"symbol\": \"processPayment\",\n  \"depth\": 3\n}\n```\n\n---\n\n### aft_import\n\nLanguage-aware import management for TS, JS, TSX, Python, Rust, and Go.\n\n```json\n// Add named imports with auto-grouping and deduplication\n{\n  \"op\": \"add\",\n  \"filePath\": \"src/api.ts\",\n  \"module\": \"react\",\n  \"names\": [\"useState\", \"useEffect\"]\n}\n\n// Remove a single named import\n{ \"op\": \"remove\", \"filePath\": \"src/api.ts\", \"module\": \"react\", \"removeName\": \"useEffect\" }\n\n// Re-sort and deduplicate all imports by language convention\n{ \"op\": \"organize\", \"filePath\": \"src/api.ts\" }\n```\n\n---\n\n### aft_transform\n\nScope-aware structural transformations that handle indentation correctly.\n\n| Op | Description |\n|----|-------------|\n| `add_member` | Insert a method or field into a class, struct, or impl block |\n| `add_derive` | Add Rust derive macros (deduplicates) |\n| `wrap_try_catch` | Wrap a TS/JS function body in try/catch |\n| `add_decorator` | Add a Python decorator to a function or class |\n| `add_struct_tags` | Add or update Go struct field tags |\n\n```json\n// Add a method to a TypeScript class\n{\n  \"op\": \"add_member\",\n  \"filePath\": \"src/user.ts\",\n  \"container\": \"UserService\",\n  \"code\": \"async deleteUser(id: string): Promise\u003cvoid\u003e {\\n  await this.db.users.delete(id);\\n}\",\n  \"position\": \"last\"\n}\n```\n\nAll ops support `dryRun` and `validate` (`\"syntax\"` or `\"full\"`).\n\n---\n\n### aft_refactor\n\nWorkspace-wide refactoring that updates imports and references across all files.\n\n| Op | Description |\n|----|-------------|\n| `move` | Move a symbol to another file, updating all imports workspace-wide |\n| `extract` | Extract a line range (1-based) into a new function (auto-detects parameters) |\n| `inline` | Replace a call site (1-based `callSiteLine`) with the function's body |\n\n```json\n// Move a utility function to a shared module\n{\n  \"op\": \"move\",\n  \"filePath\": \"src/pages/home.ts\",\n  \"symbol\": \"formatCurrency\",\n  \"destination\": \"src/utils/format.ts\"\n}\n```\n\n`move` saves a checkpoint before mutating anything. Use `dryRun: true` to preview as a diff.\n\n---\n\n### aft_safety\n\nBackup and recovery for risky edits.\n\n| Op | Description |\n|----|-------------|\n| `undo` | Undo the last edit to a file |\n| `history` | List all edit snapshots for a file |\n| `checkpoint` | Save a named snapshot of tracked files |\n| `restore` | Restore files to a named checkpoint |\n| `list` | List all available checkpoints |\n\n```json\n// Checkpoint before a multi-file refactor\n{ \"op\": \"checkpoint\", \"name\": \"before-auth-refactor\" }\n\n// Restore if something goes wrong\n{ \"op\": \"restore\", \"name\": \"before-auth-refactor\" }\n```\n\n\u003e **Note:** Backups are held in-memory for the session lifetime (lost on restart). Per-file undo\n\u003e stack is capped at 20 entries — oldest snapshots are evicted when exceeded.\n\n---\n\n## Configuration\n\nAFT uses a two-level config system: user-level defaults plus project-level overrides.\nBoth files are JSONC (comments allowed).\n\n**User config** — applies to all projects:\n```\n~/.config/opencode/aft.jsonc\n```\n\n**Project config** — overrides user config for a specific project:\n```\n.opencode/aft.jsonc\n```\n\n### Config Options\n\n```jsonc\n{\n  // Replace opencode's built-in read/write/edit/apply_patch and\n  // ast_grep_search/ast_grep_replace/lsp_diagnostics with AFT-enhanced versions.\n  // Default: true. Set to false to use aft_ prefix on all tools instead.\n  \"hoist_builtin_tools\": true,\n\n  // Auto-format files after every edit. Default: true\n  \"format_on_edit\": true,\n\n  // Auto-validate after edits: \"syntax\" (tree-sitter, fast) or \"full\" (runs type checker)\n  \"validate_on_edit\": \"syntax\",\n\n  // Per-language formatter overrides (auto-detected from project config files if omitted)\n  // Keys: \"typescript\", \"python\", \"rust\", \"go\"\n  // Values: \"biome\" | \"prettier\" | \"deno\" | \"ruff\" | \"black\" | \"rustfmt\" | \"goimports\" | \"gofmt\" | \"none\"\n  \"formatter\": {\n    \"typescript\": \"biome\",\n    \"rust\": \"rustfmt\"\n  },\n\n  // Per-language type checker overrides (auto-detected if omitted)\n  // Keys: \"typescript\", \"python\", \"rust\", \"go\"\n  // Values: \"tsc\" | \"biome\" | \"pyright\" | \"ruff\" | \"cargo\" | \"go\" | \"staticcheck\" | \"none\"\n  \"checker\": {\n    \"typescript\": \"biome\"\n  },\n\n  // Tool surface level: \"minimal\" | \"recommended\" (default) | \"all\"\n  // minimal:     aft_outline, aft_zoom, aft_safety only (no hoisting)\n  // recommended: minimal + hoisted tools + lsp_diagnostics + ast_grep + aft_import + aft_conflicts\n  //              + grep/glob (when experimental_search_index is enabled)\n  //              + aft_search (when experimental_semantic_search is enabled)\n  // all:         recommended + aft_navigate, aft_delete, aft_move, aft_transform, aft_refactor\n  \"tool_surface\": \"recommended\",\n\n  // List of tool names to disable after surface filtering\n  \"disabled_tools\": [],\n\n  // --- Experimental ---\n\n  // Enable trigram-indexed grep/glob that hoist opencode's built-ins.\n  // Builds a background index on session start, persists to disk, updates via file watcher.\n  // Falls back to direct scanning when the index isn't ready or for out-of-project paths.\n  // Default: false\n  \"experimental_search_index\": false,\n\n  // Enable semantic code search (aft_search tool).\n  // Requires ONNX Runtime installed (brew install onnxruntime on macOS).\n  // Builds embeddings for all symbols using a local model (all-MiniLM-L6-v2, ~22MB).\n  // The model is downloaded on first use. Index persists to disk for fast cold start.\n  // Default: false\n  \"experimental_semantic_search\": false,\n\n  // Restrict all file operations to the project root directory.\n  // Default: false (matches opencode's permission-based model — operations prompt via ctx.ask)\n  \"restrict_to_project_root\": false\n}\n```\n\nAFT auto-detects the formatter and checker from project config files (`biome.json` → biome,\n`.prettierrc` → prettier, `Cargo.toml` → rustfmt, `pyproject.toml` → ruff/black, `go.mod` →\ngoimports). Local tool binaries (biome, prettier, tsc, pyright) are discovered in\n`node_modules/.bin` before falling back to the system PATH. You only need per-language overrides\nif auto-detection picks the wrong tool or you want to pin a specific formatter.\n\n---\n\n## Architecture\n\nAFT is two components that talk over JSON-over-stdio:\n\n```\nOpenCode agent\n     |\n     | tool calls\n     v\n@cortexkit/aft-opencode (TypeScript plugin)\n  - Hoists enhanced read/write/edit/apply_patch/ast_grep_*/lsp_diagnostics/grep/glob\n  - Registers aft_outline/navigate/import/transform/refactor/safety/delete/move/search\n  - Manages a BridgePool (one aft process per session)\n  - Resolves the binary path (cache → npm → PATH → cargo → download)\n     |\n     | JSON-over-stdio (newline-delimited)\n     v\naft binary (Rust)\n  - tree-sitter parsing (14 language grammars)\n  - Symbol resolution, call graph, diff generation\n  - Format-on-edit (shells out to biome / rustfmt / etc.)\n  - Backup/checkpoint management\n  - Trigram search index (experimental: background thread, disk persistence, file watcher)\n  - Semantic search with local embeddings (experimental: fastembed + all-MiniLM-L6-v2)\n  - Persistent storage at ~/.local/share/opencode/storage/plugin/aft/\n```\n\nThe binary speaks a simple request/response protocol: the plugin writes a JSON object to stdin,\nthe binary writes a JSON object to stdout. One process per session stays alive for the session\nlifetime — warm parse trees, isolated undo history, no re-spawn overhead per call.\n\n---\n\n## Supported Languages\n\n| Language | Outline | Edit | Imports | Refactor |\n|----------|---------|------|---------|---------|\n| TypeScript | ✓ | ✓ | ✓ | ✓ |\n| JavaScript | ✓ | ✓ | ✓ | ✓ |\n| TSX | ✓ | ✓ | ✓ | ✓ |\n| Python | ✓ | ✓ | ✓ | ✓ |\n| Rust | ✓ | ✓ | ✓ | partial |\n| Go | ✓ | ✓ | ✓ | partial |\n| C | ✓ | ✓ | — | — |\n| C++ | ✓ | ✓ | — | — |\n| C# | ✓ | ✓ | — | — |\n| Zig | ✓ | ✓ | — | — |\n| Bash | ✓ | ✓ | — | — |\n| HTML | ✓ | ✓ | — | — |\n| Markdown | ✓ | ✓ | — | — |\n\n---\n\n## Development\n\nAFT is a monorepo: bun workspaces for TypeScript, cargo workspace for Rust.\n\n**Requirements:** Bun ≥ 1.0, Rust stable toolchain (1.80+).\n\n```sh\n# Install JS dependencies\nbun install\n\n# Build the Rust binary\ncargo build --release\n\n# Build the TypeScript plugin\nbun run build\n\n# Run all tests\nbun run test        # TypeScript tests\ncargo test          # Rust tests\n\n# Lint and format\nbun run lint        # biome check\nbun run lint:fix    # biome check --write\nbun run format      # biome format + cargo fmt\n```\n\n**Project layout:**\n\n```\nopencode-aft/\n├── crates/\n│   └── aft/              # Rust binary (tree-sitter core)\n│       └── src/\n├── packages/\n│   ├── opencode-plugin/  # TypeScript OpenCode plugin (@cortexkit/aft-opencode)\n│   │   └── src/\n│   │       ├── tools/    # One file per tool group\n│   │       ├── config.ts # Config loading and schema\n│   │       └── downloader.ts\n│   └── npm/              # Platform-specific binary packages\n└── scripts/\n    └── version-sync.mjs  # Keeps npm and cargo versions in sync\n```\n\n---\n\n## Roadmap\n\n- MCP server for Claude Code, Cursor, and other MCP-compatible hosts\n- LSP integration for type-aware symbol resolution (partially implemented)\n- Streaming responses for large call trees\n- Watch mode for live outline updates\n\n---\n\n## Contributing\n\nBug reports and pull requests are welcome. For larger changes, open an issue first to discuss\nthe approach.\n\nThe binary protocol is documented in `crates/aft/src/main.rs`. Adding a new command means\nimplementing it in Rust and adding a corresponding tool definition (or extending an existing one)\nin `packages/opencode-plugin/src/tools/`.\n\nRun `bun run format` and `cargo fmt` before submitting. The CI will reject unformatted code.\n\n---\n\n## License\n\n[MIT](LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcortexkit%2Faft","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcortexkit%2Faft","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcortexkit%2Faft/lists"}