10. Smart-coding tools

The following tools are available within the workspace by default; they coordinate to let the assistant "work" in your repo.

10.1 Read files

Supports line-based or byte-based reads, auto-chunked for large files. Reads include a content fingerprint, so subsequent edits are based on the version "just read" — no race-condition overwrites.

10.2 Write files / edit

  • Overwrite write: full rewrite of a file, with a "read first" precondition.
  • Patch edit: precise replacement by content fingerprint; the AI must read before changing — no hallucinated overwrites.

10.3 List directories / find files

  • Directory listing supports filters and time-sorted output.
  • Glob-based filename matching, automatically skipping .git/ / node_modules/ etc.

10.4 Search content

  • Regex (RE2) plus glob filename filter.
  • Auto-skips binary files using a heuristic, with smart encoding detection.

10.5 Shell execution

  • Synchronous: runs once, blocks for the result, pipes output into the conversation.
  • Background: long tasks (builds, load tests) go background; pull live output, kill at will.

Every shell call is governed by tool permissions: allow / ask / deny.

10.6 Version control

Basic Git: status query, new / switch branch, init repo, plus staging / commit / merge / rebase write tools; pushes are still done by the assistant through the shell-execution tool. Like every other tool these default to "Allow" (no extra prompt); for risky operations such as merges and pushes, set the corresponding tools to "Ask" or "Deny" per work mode in the agent manager → "Configure tools" (see §13.1).

10.7 Web search (optional)

  • web.bing: searches via the Bing RSS endpoint, returning title / snippet / URL.
  • web.fetch: fetches a URL and cleans it into Markdown / text / HTML.

Sources appear as citation cards in the message stream.

10.8 Time and context

  • time.now: current time (with timezone); useful for the AI to stamp logs, TODOs, filenames.
  • TodoWrite: write / update the todo list, up to 50 items per call; see Chapter 16.

10.9 Context truncation

Reads and searches have soft + hard limits per call; output beyond is truncated and tagged so the AI can continue-read without flooding the LLM context.

Slimmer tool results, cheaper on quota: workspace tools' return payloads are trimmed — file paths are shown relative to the workspace root, error messages strip the long root-path prefix, duplicate line hashes are removed, and pagination bookkeeping is only emitted when the result is truncated. This cuts wasted tokens (cheaper on long tasks) and no longer leaks the absolute root path into results.

10.10 Code-intelligence tools (code.*, LSP-based)

A new toolset that lets the AI understand your code like an IDE does. Based on the industry-standard Language Server Protocol (LSP), it exposes "go-to-definition / find references / rename / compiler diagnostics" and more — so the AI is more precise, fewer back-and-forths, no more guessing with regex.

Tool Use
code.definition Jump to definition: locate a symbol's true definition (file + line)
code.references Find references: list every call site of a function / type / variable
code.hover Signature / type / docs — same as your IDE's mouse hover
code.symbols File outline: functions / types / methods in a file
code.workspace_symbols Workspace-wide symbol search by name
code.diagnostics Compile errors / warnings: compiler-level reports, not log-scraping
code.call_hierarchy Call graph: who calls a function / what does it call
code.repo_map Project skeleton: file tree + top-level symbols per file, token-friendly whole-repo overview
code.rename Cross-file safe rename — true rename, not text substitution
code.code_action Quick fixes: list / apply quick fixes from the language server; pairs with code.diagnostics for a see-error → fix-error self-healing loop
code.lsp_status Live state of the LSP server pool in this workspace
code.implementation Find implementations: jump from an interface / abstract method to each of its implementations
code.type_definition Go to type definition: jump from a variable or expression to where its type is declared
code.document_highlight Highlight within the file: every read, write and reference of a symbol in the current file — lighter than a whole-repo reference search
code.completion Completion candidates: code-completion suggestions at a given cursor position
code.signature_help Function signature hint: the parameter list of a call, with the current argument marked
code.formatting Format the whole file: format per the language server's rules and write it back (no write when nothing changes)

Position parameters are 1-based (matching fs.read line mode / cat -n) — no mental offset arithmetic needed.

Degrades automatically to what the language server actually supports: not every language's server implements all of these. Support is decided by what the call actually returns rather than by what the server claims, so a capability it does have isn't wrongly refused; once something is found unsupported it's remembered, and the same server isn't probed again in later sessions.

10.10.1 22 built-in languages + on-demand auto-download

Built-in coverage of mainstream languages: Go, TypeScript / JavaScript, Python, C / C++, Rust, Java, Lua, Bash, YAML, PHP, Ruby, Vue, Zig, Dart, Kotlin, Clojure, Elixir, Haskell, F# / C#, Gleam, Astro — ~22 in total.

  • Missing language servers download on demand (safely hosted, with a kill switch); progress shown via toast.
  • Settings → Code intelligence / LSP shows running servers and install state.

10.10.2 Override defaults via lsp.json / lsp.yaml

Drop an lsp.yaml (preferred; legacy lsp.json still works) in the workspace root or ~/.avlcode/ to override defaults — change a launch command, change root markers, disable a language, or add a custom server. Changes hot-reload — no workspace restart needed. Crashed server processes / dropped SSH sessions auto-restart.

10.10.3 Remote workspaces, too

LSP runs on the SSH remote: any missing language server is auto-downloaded on the remote; cross-system paths and reads/writes are handled correctly — code intelligence on a remote workspace is on par with local.

10.10.4 Python virtual-environment (venv) auto-detection

When the AI runs Python commands in a workspace, AVL Code auto-detects and uses the workspace virtual environment — no need to activate every time:

  • Coverage: venv / .venv, conda (including Windows Scripts/python.exe layouts), poetry (including envs outside the project directory), pipenv.
  • Mechanism: rewrites the first command to the absolute path inside the venv and injects VIRTUAL_ENV / PATH for toolchain consistency; if no venv is detected, behavior is unchanged (the command runs as-is).
  • Caching: out-of-tree discoveries (poetry / pipenv) cache for 5 minutes with a 15-second probe timeout, so we don't fork every turn.
  • Escape hatch: set AVLCODE_DISABLE_VENV_AUTODETECT to anything to globally disable.
  • pyright (LSP) is venv-aware too — both locally and on SSH remote — so the type inference in code intelligence reflects your real virtual environment.

10.10.5 Whole-repo code search and Q&A (code.search / code.ask)

Beyond pinpoint lookups like go-to-definition / find-references, two new whole-repo semantic search tools answer questions like "where is the login logic and how does it flow":

Tool Use
code.search Search symbols (functions / types / methods) across the whole project by natural language / keyword, returning ranked hits with file:line
code.ask On top of code.search, synthesizes a plain-language answer + file:line citations from the retrieved snippets; says so plainly when nothing is found, never fabricates

Characteristics:

  • Local, zero-vector / zero-embedding, air-gap capable: BM25 + identifier tokenization (auto-splits camelCase / snake_case / kebab-case) + CJK bigram tokenization (so Chinese comments and Chinese questions match too), no model or network needed.
  • Explainable hits: every result gives file:line and the matched keywords, so you can see why it matched.
  • Index keeps up automatically: built on first call, then incrementally refreshed by file mtime (edit / add / delete). The local index lives in the workspace's .avlcode/rag/. Index-build and language-server startup progress show as a single in-place progress bar (no longer a screenful of toasts).
  • Very large repositories are reported honestly: the index covers at most 4000 files. Beyond that you're told exactly how many files were left out, instead of quietly searching only part of the repo — so "no hits" is unambiguous: either it really isn't there, or it wasn't covered.
  • Structure-aware rerank: combines centrality and call relations to rank "called more = more central" symbols higher (code.search enables it explicitly with expand=true; code.ask now does this smart rerank by default, so answers about code stay more focused). The incremental-refresh scope is also corrected, so it re-indexes more accurately after file changes — no over- or under-refresh.
  • Works remotely too: SSH remote workspaces are supported (remote enumeration / read, index in memory); code.ask reuses the current session's model.

These two are complementary to the LSP code.* tools above: use code.definition / code.references for exact location, and code.search / code.ask for "where is this / how is it implemented".

10.11 Test runner test.run

The AI can now run tests itself, parse failures, and fix them, forming a "run tests → see failures → edit code → re-run" loop.

  • Framework auto-detect: by project file — go.mod → Go / pyproject.toml·pytest.ini → pytest / Cargo.toml → cargo / package.json → Node (vitest, jest). You can also override with framework or fully override the command with command.
  • Structured failures: output is parsed into failures[] (per-entry: test name / file / line / message), up to 50 entries + a small raw tail — instead of dumping the whole test log into the conversation. Token-thrifty.
  • Unknown-framework fallback: parsing failure degrades to exit code + tail, so you always get some signal.
  • Scope: pass path to scope (Go package pattern ./pkg/..., pytest dir / file, cargo package name).
  • Remote too: local + SSH remote workspaces both supported.
  • Disabled in plan: running tests = executing project code, which has write side-effects, at odds with a "think, don't do" stage. Available in assess — acceptance is exactly when you want the tests actually run.

10.11.1 Self-check gate (opt-in, four checker types)

The self-check gate is a generalisation of the test gate: when enabled, the AI runs a set of checks at the end of every turn; if any fail, the structured failures are fed back in and the AI is forced to keep fixing until everything is green — no more nagging.

Six checker types (pick any, combine freely):

Checker Purpose Backing / Tool
test Run tests test.run (Go / pytest / cargo / Node)
diagnostics Compile / type errors code.diagnostics (LSP, no extra tools needed)
lint Style / latent bugs lint.run (golangci-lint / eslint / ruff)
sast Security static analysis sast.run (semgrep)
testgen Edits without matching tests → auto-add (opt-in) fs.glob via the zMCP gateway for convention paths (Go / Python / JS / TS)
judge LLM end-of-turn self-review (opt-in, defaults to report) Independent fresh-context model call looking only at "goal + this turn's diff" — catches logic / semantic / security-intent issues machine checks miss

Per-checker knobs:

  • mode: block (default — inject failures, force fix) / report (notify only, don't block)
  • submode:
    • test: changed (default, only changed files) / full (everything)
    • lint: strict (warnings count as red) / lenient
    • sast: quick (caps large files for speed) / deep (full)
  • scope: changed (default, files changed this turn) / workspace (whole repo)
  • path / linters / config: fine-grained overrides

testgen / judge aren't in the default set: you have to explicitly add testgen / judge to checks. testgen only judges languages with clear test conventions (Go / Python / JS / TS), skips test files themselves and generated artifacts (e.g. .pb.go), and shares the 3-injection hard cap.

judge never false-blocks: model gibberish / dirty JSON / timeout are treated as "non-authoritative" and pass through; in the gate ordering, judge is placed after all cheap checkers and only runs when every block-mode checker is green (saves one model call and avoids piling subjective opinions on top of objective failures). Few-shot examples plus the current turn's diagnostics are injected as background — sharper judgment, fewer false positives.

Why an independent model call: judge reviews in a completely fresh context, without the agent's own conversation history, eliminating the self-rationalisation bias of "checking your own work" — this is exactly what distinguishes LLM-as-judge from "asking the agent to self-reflect".

Cheap: only runs when files were actually edited in the turn; turns without edits skip the whole pass. Structured UI: failures show in a structured frontend panel — not a wall of raw JSON. No infinite loop: hard cap on consecutive injections in one session (default 3) — beyond that the gate disengages so flaky / unfixable checks can't trap the AI; real user intervention resets the counter.

10.11.2 Three-level "inherit + override"

Layer Source Effect
Global default Settings → Tests & Checks tab (~/.config/avlcode/checkgate-default.yaml) Default check set when a workspace has no config
Workspace default Workspace .avlcode/testgate.yaml (sidebar / Header workspace menu toggle + Strip full-parameter editor) Default for new sessions; per-work-mode (plan / prepare / execute / assess) override available
Session override Session menu's per-check toggle list Each session can toggle individual checkers on/off — workspace-on can still per-session-off, and vice versa

Both the session and workspace menus show the actually-in-effect state, WYSIWYG.

10.11.3 CheckGateStrip — full editor above the input box

When the workspace self-check gate is on, a CheckGateStrip appears above the input box:

  • One row of chips shows each checker's current state (mode / submode / scope).
  • Click a chip to expand a full editor for mode / submode / scope / path / linters / config / timeout — changes write back to the workspace config immediately.
  • Disabled gate hides the strip so it doesn't clutter the view.

10.11.4 Tool install status

The Settings → Tests & Checks page also shows the install status of external tools (visibility from the main process's PATH):

Category Tool Install hint
test go / pytest / npm / cargo Install the toolchain
lint golangci-lint / eslint / ruff go install … / npm i … / pip install ruff
sast semgrep pip install semgrep or brew install semgrep

On macOS, the GUI process's PATH may not include shell-installed paths (pip / npm via .zshrc). This is a best-effort visibility check; the actual run happens via the workspace interpreter and may still succeed — don't be alarmed by a ✗ here.

10.11.5 lint.run and sast.run — also callable by the AI directly

lint.run and sast.run aren't only self-check-gate backends — the AI can also call them directly in plan / assess (read-only) modes for static auditing. They are read-only analyses (don't modify source, don't execute project code) and aren't on the read-only denylist.

lint.run:

  • Linter auto-detect: go.mod → golangci-lint / .eslintrc·eslint.config → eslint / pyproject.toml·ruff.toml → ruff; or override via linter / command.
  • Structured findings: findings[] (per-entry {file, line, rule, severity, message}), same shape as test.run.
  • submode: strict (warnings count as red) / lenient.

sast.run:

  • Backend: semgrep (--config auto default — requires network for rule pull; offline, pass config pointing to a local .semgrep.yml).
  • Structured findings: findings[] same shape.
  • submode: quick (caps large files for speed) / deep (full).

10.12 Tool introspection — tools.list / tools.describe / tools.load

The AI can enumerate the tools currently available to it and their usage, reducing "did I even have that capability?" guesswork:

Tool Use
tools.list List the tools callable in this session (name + description); prefix filters by namespace (e.g. fs / code / test)
tools.describe Full schema (description + parameter definitions) for a given tool
tools.load Load tools by category; with no argument, returns the category directory — each category with a one-line capability blurb and its tool names

The listed results are trimmed to what is actually available in this session (affected by the three-state tool permissions and by category lazy loading), with a note that "not seeing it in the current list doesn't mean it doesn't exist — load the matching category first".