Skip to main content
Glama
README.md
# AST Code Editor MCP Server

A robust, language-agnostic Model Context Protocol (MCP) server that provides AI coding agents with the ability to edit files surgically via Abstract Syntax Trees (AST) instead of relying on token-heavy, brittle search-and-replace or diff operations.

## Why AST Edits?

Every non-AST edit format — search/replace, unified diff, whole-file rewrite — requires the model to copy text *perfectly* from a file it saw once. One whitespace mismatch on a 4,000-line file and the edit fails. AST edits sidestep the problem entirely: the model names the target (e.g., `LRUCache.get`) and provides the new code; the parser figures out where it lives.

Geometric AGI benchmarked every major format across 4 models and 29 edit tasks. **AST edits were the only format to hit 100% correctness on 3 of 4 models, with 18x fewer output tokens than whole-file rewrite, and zero format failures.** Full methodology and results: [AST Edits: The Code Editing Format Nobody Uses](https://geometricagi.github.io/2026/04/02/ast-edits.html).

### Credits

This MCP server was inspired by research from [Jack Foxabbott](https://www.linkedin.com/in/foxabbott/) and the team at [Geometric AGI](https://geometricagi.github.io/). Their full findings, benchmark suite, and data are available here:

- [AST Edits: The Code Editing Format Nobody Uses](https://geometricagi.github.io/2026/04/02/ast-edits.html) (Blog)
- [Jack Foxabbott's original post](https://www.linkedin.com/posts/foxabbott_i-didnt-know-until-recently-that-all-the-share-7445506956783480832-dcXr/) (LinkedIn)
- [GeometricAGI/blog](https://github.com/GeometricAGI/blog) (Benchmark code & data)

## Estimated Token Savings

Per-edit output token savings versus other common edit formats:

| Edit size | File size | vs whole-file rewrite | vs unified diff | vs search/replace |
| :--- | :--- | :--- | :--- | :--- |
| 1-line tweak | 100 LoC | 3–5x | ~1.5x | ~1.5x |
| Function body rewrite | 500 LoC | 8–12x | 2–3x | 2–3x |
| Function body rewrite | 4,000 LoC | **15–20x** | 3–5x | 3–5x |
| Add 2 lines to a function | any size | **~20x** (via `prepend_to_body` / `append_to_body`) | 5–10x | 3–5x |

Per-read input token savings versus reading the entire file:

| Read task | File size | AST reader tool | vs full file read |
| :--- | :--- | :--- | :--- |
| One function's source | 500 LoC | `read_symbol` | **~20x fewer tokens** |
| One function's source | 2,000 LoC | `read_symbol` | **~50-100x fewer tokens** |
| Class API (10 methods, no bodies) | 500 LoC | `read_interface` | **~10x fewer tokens** |
| Import block only | any size | `read_imports` | **~20-50x fewer tokens** |
| Structural overview (names + line numbers) | any size | `list_symbols` | **~15-30x fewer tokens** |
| One function's signature | any size | `get_signature` | **~50-200x fewer tokens** |

**For daily agent users, a realistic 40-60% reduction in total tokens per session is achievable, on average** (combining output savings from surgical edits with input savings from targeted reads).

The savings come from four compounding effects:

- **Output tokens:** Using `prepend_to_body` / `append_to_body` for small additions instead of rewriting whole function bodies
- **Input tokens:** Using `read_symbol` / `read_interface` / `read_imports` to read only what's needed instead of entire files (~10-20x fewer input tokens per read)
- **Discovery:** `list_symbols` / `get_signature` instead of reading whole files
- **Zero format failures:** AST edits never fail on whitespace drift, eliminating retry loops that plague other formats.

## Supported Languages & Capabilities

| Language | Extensions | Structural edits | Comments | Docstrings | Notes |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **Python** | `.py` | ✅ | ✅ `#` | ✅ function/class | Decorators preserved. Module-level `dict`/`list` literals editable via `add_key` / `append_to_array`. |
| **JavaScript** | `.js`, `.jsx`, `.mjs`, `.cjs` | ✅ | ✅ `//` + `/* ... */` | — | |
| **TypeScript** | `.ts`, `.tsx` | ✅ | ✅ `//` + `/* ... */` | — | Interfaces are treated as classes for `add_method` / `add_field`. |
| **C** | `.c`, `.h` | ✅ | ✅ `//` + `/* ... */` (single + multi-line) | — | `.h` defaults to C — use `.hpp`/`.hxx`/`.hh` for C++ headers. |
| **C++** | `.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.hh` | ✅ | ✅ `//` + `/* ... */` (single + multi-line) | — | Supports `class`, `struct`, `union`, `enum`, `namespace`; `Class::method` qualified names resolve correctly. |
| **Ruby** | `.rb` | ✅ | ✅ `#` | — | Classes, modules, instance methods, `singleton_method` (class methods via `def self.foo`). `require`/`require_relative`/`load`/`autoload` recognized as imports. |
| **Go** | `.go` | ✅ | ✅ `//` + `/* ... */` | — | `struct`, `interface`, functions, methods. Methods are addressed by receiver: `Cache.Get` resolves to the top-level `func (c *Cache) Get(...)`. Grouped `import (...)` blocks supported. |
| **Java** | `.java` | ✅ | ✅ `//` + `/* ... */` + Javadoc `/** ... */` | — | `class`, `interface`, `enum`, `record`; methods, constructors, fields. Annotations (`@Override`, `@Deprecated`) travel with their method on edits (wrapped in the `modifiers` node). Enum methods (nested in `enum_body_declarations`) discovered via BFS in `list_symbols`. |
| **JSON** | `.json` | ✅ (keys, values, arrays) | — (no comment syntax) | — | |
| **YAML** | `.yml`, `.yaml` | ✅ (keys, values, sequences) | ✅ `#` | — | Block and flow sequences supported. |
| **TOML** | `.toml` | ✅ (keys, values, arrays, tables) | ✅ `#` | — | `[table]` headers addressable by name for comment tools. |

**Cross-cutting features:**

- **Decorated functions** (Python `@decorator`): decorators are preserved on body/signature edits and included on delete.
- **Byte-correct slicing**: multi-byte characters (emoji, `═`, `→`) handled safely in source text.
- **Idempotent imports**: `add_import` skips exact duplicates automatically. For Go specifically, when a parenthesized `import ( ... )` block already exists, new specs are inserted inside the block rather than as a bare top-level line (which would be a syntax error for spec-only input like `"path/filepath"`).
- **Doc-comment-aware deletion**: `delete_symbol` by default removes the contiguous leading comment block above the symbol (Godoc, Javadoc, `#`/`//` comment runs) so docs don't become orphaned. Opt out with `include_leading_comments=False`.

### Language-specific design decisions

A few tools have language-specific semantics where multiple reasonable interpretations exist. The chosen behavior is documented here for transparency:

**`add_field` (Ruby and Go) — option (a): literal text passthrough**

- **Ruby:** `add_field("LRUCache", "  attr_accessor :capacity")` inserts the literal string at the top of the class body. The tool does **not** auto-wrap bare names in `attr_accessor` — you provide the exact text you want (whether that's `attr_accessor`, `attr_reader`, `@instance_var = nil` in `initialize`, or `CLASS_CONST = 42`).
- **Go:** `add_field("Cache", "\tversion int")` inserts the literal string inside the `struct { ... }` body. The tool does **not** infer types from bare names — you provide the full Go field declaration.
- **Rationale:** consistent with how `add_field` works for other languages (Python, JS/TS, C++) where the caller provides the full source text. The alternative option (b) — auto-wrapping (e.g. `attr_accessor :foo` from the name `foo`) — would be more magical but harder to use for edge cases (typed fields, readonly fields, field with default value, etc.).

**`add_method` (Go) — option (a): top-level sibling insertion**

- `add_method("Cache", "func (c *Cache) Has(key string) bool { ... }")` locates the `type Cache struct { ... }` declaration and inserts the new method **immediately after it, at the top level** (not inside the struct's braces).
- **Rationale:** Go methods are lexically top-level, not nested inside their receiver type — this matches how Go code is actually written. The alternative option (b) — refusing because "Go methods aren't inside structs" — would be pedantically correct but force callers to use `insert_after("Cache", content)` instead, which loses the semantic signal that this is a method addition.

## Tools Exposed

All tools require `file_path` to be an **absolute path** to an existing file.

### Code editing — structural (Python, JS, TS, C, C++, Ruby, Go, Java)

| Tool | Parameters | Description |
| :--- | :--- | :--- |
| `replace_function` | `file_path`, `target`, `content` | Replace a full function definition (signature + body + decorators). |
| `replace_function_body` | `file_path`, `target`, `content` | Replace only the body of a function, preserving signature and decorators. |
| `replace_signature` | `file_path`, `target`, `new_signature` | Replace only the signature, preserving body and decorators. |
| `replace_in_body` | `file_path`, `target`, `old_snippet`, `new_snippet` | Replace a byte-identical snippet inside a function body. Scoped to target's body; raises on multiple matches. |
| `delete_in_body` | `file_path`, `target`, `snippet` | Delete a byte-identical snippet inside a function body. Scoped to target's body; raises on multiple matches. |
| `insert_in_body` | `file_path`, `target`, `new_snippet`, `at` \| `after` \| `before` | Insert a snippet inside a function body. Pass exactly ONE of: `at="top"` (prepend), `at="bottom"` (append), `after=<snippet>` (anchored), `before=<snippet>` (anchored). |
| `add_top_level` | `file_path`, `content`, `position="bottom"` | Insert top-level content. `position="bottom"` appends at end of file (default); `position="top"` inserts after preamble (package/imports/includes/leading comments, plus Python module docstring) and before the first real declaration. |
| `add_method` | `file_path`, `class_target`, `content` | Add a method at the end of a class body. |
| `add_field` | `file_path`, `class_target`, `content` | Add a field/attribute/member at the top of a class body. |
| `insert_sibling` | `file_path`, `target`, `content`, `position` | Insert content as a sibling of a named symbol. `position="before"` or `"after"`. |
| `delete_symbol` | `file_path`, `target`, `include_leading_comments=True` | Delete a function or class definition block (including decorators). By default also consumes the contiguous leading comment block above the symbol (Godoc, Javadoc `/** ... */`, `#` or `//` comments); pass `include_leading_comments=False` to keep it. |

### Parameters & signatures

| Tool | Parameters | Description |
| :--- | :--- | :--- |
| `add_parameter` | `file_path`, `target`, `parameter`, `position` | Add a parameter to a function signature (`position`: `"start"` or `"end"`). |
| `remove_parameter` | `file_path`, `target`, `parameter_name` | Remove a parameter by name. |

### Imports & includes

| Tool | Parameters | Description |
| :--- | :--- | :--- |
| `add_import` | `file_path`, `import_text` | Add an `import`/`from`/`#include` line. Skips duplicates. For Go, if a parenthesized `import ( ... )` block already exists, the spec is inserted inside that block (accepts either `import "foo"` or just `"foo"` / `alias "foo"` as input). |
| `remove_import` | `file_path`, `import_text` | Remove a matching import line. |
| `add_import_name` | `file_path`, `module`, `name` | Add one name to an existing named-import statement: `from <module> import a, b` (Python) or `import { a, b } from "<module>"` (JS/TS). Idempotent. |
| `remove_import_name` | `file_path`, `module`, `name` | Remove one name from a multi-name named-import statement (Python and JS/TS). If the last named import is removed and no default/namespace binding remains, the whole line is removed. |

### Comments & docstrings

| Tool | Parameters | Description |
| :--- | :--- | :--- |
| `edit_leading_comment` | `file_path`, `target`, `op`, `comment=""` | Edit the contiguous leading-comment block above a named symbol. `op="add"` inserts; `op="replace"` replaces (or inserts if none); `op="remove"` deletes. Works for `#` / `//` / `/* ... */` / Javadoc `/** ... */`. |
| `replace_docstring` | `file_path`, `target`, `new_docstring` | Replace or insert a Python function/class docstring. Python-only. |

### Dict/list editing (JSON, YAML, TOML, AND Python module-level dict/list literals)

| Tool | Parameters | Description |
| :--- | :--- | :--- |
| `replace_value` | `file_path`, `target`, `content` | Replace the value of an existing config key. `target` is the dotted key path. |
| `add_key` | `file_path`, `parent_target`, `key`, `value` | Add a key-value pair to a dict/object/mapping/table. For Python, `parent_target` is the dict variable name; for config, a dotted path (use `""` for root). |
| `delete_key` | `file_path`, `target` | Delete a key-value pair. Targets: JSON/YAML/TOML dotted path; Python `DictName.keyExpr`; JS/TS `VarName.keyName` on `const`/`let`/`var` or `export const` object literals (handles regular pairs, `{ key }` shorthand, and quoted `"complex-key"`). For JSON and JS/TS, adjacent comma is also removed. |
| `append_to_array` | `file_path`, `target`, `value` | Append a literal value to a list/array/sequence. For Python, `target` is the list variable name; for config, a dotted path. |
| `remove_from_array` | `file_path`, `target`, `value_match` | Remove the first matching element from a list/array/sequence. |

### Navigation & reading (read-only)

| Tool | Parameters | Description |
| :--- | :--- | :--- |
| `list_symbols` | `file_path` | Formatted outline of all top-level functions, classes, and methods with line numbers. |
| `find_references` | `file_path`, `target` | Syntactic search for all occurrences of an identifier (no scope awareness). |
| `read_symbol` | `file_path`, `target`, `depth="full"` | Return source text of a single named symbol. `depth` controls how much: `"full"` returns the entire source (typically **10-20x fewer tokens** than the whole file); `"interface"` returns a class stub (header + fields + method sigs with `...`) or a function's signature; `"signature"` returns signature-only. |
| `read_imports` | `file_path` | Return all import/include statements in the file. |

**Target format:** Use the exact function name (e.g., `get`) or dotted `Class.method` path (e.g., `LRUCache.get`). Decorated Python functions are fully supported — decorators are preserved when replacing bodies or signatures, and included when deleting or replacing the full function.

**Tip:** Call `list_symbols` first to discover exact target names before editing. This avoids guessing and makes subsequent edits much more reliable.

## Which tool should I use?

A decision guide grouped by intent. Start at the top and pick the narrowest match.

### Discovering what's in a file (do this first)

- **Don't know what symbols exist?** → `list_symbols`
- **Need one specific function's full source?** → `read_symbol` (`depth="full"`, the default — 10-20x cheaper than reading the whole file)
- **Need a class's public API (methods + fields, no bodies)?** → `read_symbol(target, depth="interface")`
- **Need just a function's signature?** → `read_symbol(target, depth="signature")`
- **Need to see a file's imports/dependencies?** → `read_imports`
- **Where is a symbol used?** → `find_references`

Dotted targets descend into closures: Go `stdioCmd.RunE` (func_literal in struct field), TS `app.handler` (arrow function in object literal).

### Adding new content

| Intent | Tool |
| :--- | :--- |
| New top-level function, class, constant, or type alias | `add_top_level` (use `position="top"` to prepend after preamble) |
| New method in an existing class | `add_method` |
| New field/attribute/member in a class | `add_field` |
| New content before or after a top-level symbol | `insert_sibling(position="before" \| "after")` |
| New lines at the top of an existing function body | `insert_in_body(at="top")` |
| New lines at the bottom of an existing function body | `insert_in_body(at="bottom")` |
| **New lines at a specific spot inside a function body (anchored to existing text)** | `insert_in_body(after=…)` or `insert_in_body(before=…)` |
| New parameter on an existing function | `add_parameter` |
| New import or `#include` | `add_import` |
| New name in an existing `from X import …` or `import { a, b } from "mod"` | `add_import_name` |
| New comment above a symbol | `edit_leading_comment(op="add")` |
| New Python docstring on a function/class | `replace_docstring` |
| New key in a dict/object/mapping/table (any lang) | `add_key` |
| New item in a list/array/sequence (any lang) | `append_to_array` |

### Modifying existing content

| Intent | Tool |
| :--- | :--- |
| Rewrite the full function (signature + body) | `replace_function` |
| Rewrite only the body, keep the signature | `replace_function_body` |
| **Change one statement/block inside a large body** | `replace_in_body` (scoped snippet match) |
| Change only the signature, keep the body | `replace_signature` |
| Change only the leading comment above a symbol | `edit_leading_comment(op="replace")` |
| Change only the Python docstring | `replace_docstring` |
| Change the value of an existing config key | `replace_value` |

### Removing content

| Intent | Tool |
| :--- | :--- |
| Remove a function, method, or class | `delete_symbol` (consumes leading doc comment by default) |
| **Remove one statement/line inside a function body** | `delete_in_body` |
| Remove a parameter from a function | `remove_parameter` |
| Remove an import or `#include` | `remove_import` |
| Remove one name from a multi-name named-import (Python or JS/TS) | `remove_import_name` |
| Remove a leading comment above a symbol | `edit_leading_comment(op="remove")` |
| Remove a key from a dict / config / JS-TS object literal | `delete_key` |
| Remove an item from a list/array | `remove_from_array` |

### Anti-patterns to avoid

- **Don't use `replace_function` or `replace_function_body` to change a few lines** — use `replace_in_body` (scoped snippet match) or `insert_in_body(at="top" \| "bottom")` for appending/prepending. Rewriting the whole function is wasteful and error-prone.
- **Don't use `replace_signature` to add or remove one parameter** — use `add_parameter`/`remove_parameter`.
- **Don't use `replace_value` to add a new key** — use `add_key`. `replace_value` only updates existing keys.
- **Don't use `add_import` to add a name to an existing `from X import …` or named import** — use `add_import_name`.
- **Don't guess at target names.** Call `list_symbols` first. Names are case-sensitive and must match exactly.

## Logging & Debugging

The server logs all tool invocations and errors to **stderr** (safe for stdio transport — does not interfere with JSON-RPC). Logs include timestamps and severity levels.

To inspect logs when running under Claude Desktop, check `~/Library/Logs/Claude/mcp*.log` (macOS) or `%APPDATA%\Claude\logs\mcp*.log` (Windows).

For interactive testing, use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).

## Prerequisites

This MCP server uses [`uv`](https://docs.astral.sh/uv/) to manage its Python environment and dependencies automatically. Install `uv` if you don't have it already:

**macOS / Linux:**

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

**Windows (PowerShell):**

```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

**Homebrew (macOS):**

```bash
brew install uv
```

**pip (any platform):**

```bash
pip install uv
```

Verify the installation:

```bash
uv --version
```

> For more options (Docker, Cargo, WinGet, etc.), see the [official uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).

## Installation

> **Note:** Replace `/absolute/path/to` below with the actual path to this repository on your machine.

### Method 1: CLI Configuration (Claude Code, Codex, Gemini)

If your agent supports adding servers via CLI, run the following:

**Claude Code / Codex CLI / Gemini CLI:**

`--scope user` installs the server globally so it's available in every project on your machine. Drop it if you only want the server active in the current project.

```bash
# Claude Code / Codex
[claude|codex] mcp add ast-editor --scope user -- uv --directory /absolute/path/to/ast-editor run ast-editor-mcp

# Gemini CLI
gemini mcp add --transport stdio --scope user ast-editor -- uv --directory /absolute/path/to/ast-editor run ast-editor-mcp
```

### Method 2: JSON Configuration

For tools that use a `mcp_config.json` or `settings.json` file, add the following block to the appropriate file path.

> **Important:** Use the **absolute path** to `uv` for `"command"`, not just `"uv"`. GUI-based MCP clients (Claude Desktop, Cursor) don't always inherit your shell `PATH`, so a bare `"uv"` will fail with a "command not found" error. Get your absolute path with:
>
> ```bash
> which uv
> # e.g. /Users/you/.local/bin/uv  or  /opt/homebrew/bin/uv
> ```

```json
{
  "mcpServers": {
    "ast-editor": {
      "command": "/absolute/path/to/uv",
      "args": [
        "--directory",
        "/absolute/path/to/ast-editor",
        "run",
        "ast-editor-mcp"
      ]
    }
  }
}
```

| Agent | Configuration File Path |
| :--- | :--- |
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS); `%APPDATA%\Claude\claude_desktop_config.json` (Windows) |
| **Cursor** | `.cursor/mcp.json` (Project) or `~/.cursor/mcp.json` (Global) |
| **Windsurf** | Agent Panel → "..." → MCP Servers → View raw config |
| **Antigravity** | `~/.gemini/config/mcp_config.json` — see [Antigravity IDE](#antigravity-ide) (uses `uvx --from`) |
| **Gemini CLI** | `~/.gemini/settings.json` (Global) or `.gemini/settings.json` (Project) |

### Antigravity IDE

The Antigravity IDE uses its own MCP config file and a `uvx`-based command — **not** the `uv --directory … run` form shown above. Add the server to `~/.gemini/config/mcp_config.json`:

> **Important:** Use the **absolute path** to `uvx` for `"command"`, not a bare `"uvx"` — Antigravity doesn't inherit your shell `PATH`. Find it with `which uvx` (e.g. `/opt/homebrew/bin/uvx`).

```json
{
  "mcpServers": {
    "ast-editor": {
      "command": "/absolute/path/to/uvx",
      "args": [
        "--from",
        "/absolute/path/to/ast-editor",
        "ast-editor-mcp"
      ]
    }
  }
}
```

### Using Standard Python (Fallback)

If you prefer not to use `uv`, install manually and point to the `.venv` executable in ast-editor directory:

```bash
python3 -m venv .venv && source .venv/bin/activate && pip install .
```

```json
{
  "mcpServers": {
    "ast-editor": {
      "command": "/absolute/path/to/ast-editor/.venv/bin/python",
      "args": ["-m", "ast_editor.server"]
    }
  }
}
```

## Agent Configuration (Important)

Coding agents are heavily biased toward their default tools. You **must** explicitly instruct them to use AST tools. The agent prompt lives in [`AST-EDITOR.md`](./AST-EDITOR.md) — a standalone file you wire into your agent's system instructions.

### Claude Code / Claude Desktop (via `@`-include)

Claude Code supports `@filename` includes in `CLAUDE.md`. Copy the prompt file into your global config directory and add one include line:

```bash
cp /absolute/path/to/ast-editor/AST-EDITOR.md ~/.claude/
echo '@AST-EDITOR.md' >> ~/.claude/CLAUDE.md
```

Or for a single project, place it next to the project's `CLAUDE.md` and add `@AST-EDITOR.md` there.

### Other agents (Cursor, Codex CLI, Windsurf, Antigravity, Aider, Gemini CLI, etc.)

Copy the **contents** of [`AST-EDITOR.md`](./AST-EDITOR.md) into your agent's instruction file (`AGENTS.md`, `.cursor/rules/*.mdc`, `.windsurfrules`, `.github/copilot-instructions.md`, system prompt, etc.). Most non-Claude agents don't support `@`-include — paste the prompt body directly.

| Agent | Instruction file |
| :--- | :--- |
| **Any agent that reads `AGENTS.md`** (Codex CLI, Windsurf, Zed, Cursor secondary) | `AGENTS.md` at repo root |
| **Cursor** | `.cursor/rules/*.mdc` (current) — legacy: `.cursorrules` |
| **GitHub Copilot** | `.github/copilot-instructions.md` |
| **Windsurf** | `.windsurfrules` or `AGENTS.md` |
| **Antigravity** | `_agents/rules/` |
| **Aider / Gemini CLI / generic** | Rules file or system prompt |

TDQS

A3.9/5.0

Scored across 28 tools

Disambiguation4/5

Each tool targets a precise operation scope (whole function, body, signature, snippet, import, key, array, comment), and the descriptions include explicit 'Don't use when' pointers that make most distinctions clear. A few adjacent tools (add_top_level vs insert_sibling, edit_leading_comment vs replace_docstring) could still be confused at a glance, but the usage guidance resolves the ambiguity.

Naming Consistency4/5

The dominant verb_noun pattern (add_*, remove_*, replace_*, delete_*, read_*, list_*) is consistent and easy to predict. Minor deviations like edit_leading_comment and insert_sibling, plus the in_body/prepositional variants, keep it from being perfectly uniform.

Tool Count2/5

With 28 tools, the server is above the 25-tool threshold and presents a heavy selection surface for an agent. Although the tools are individually purposeful, several could plausibly be consolidated (e.g., comment/docstring editing or top-level insertion variants) without losing capability.

Completeness4/5

The toolkit covers reading structure, editing function signatures/bodies, managing imports, class members, top-level symbols, config keys/arrays, and comments/docstrings quite thoroughly. The main gap is the lack of a file-scoped rename or update-all-references operation, so multi-occurrence renames require manual per-function edits.

Maintenance

ActivityMaintained
ResponsivenessNo issues