Skip to content

Releases: cortexkit/aft

v0.36.1

08 Jun 15:23

Choose a tag to compare

v0.36.1: Honest edits, compact inspect output, and safer pipes

A correctness-focused patch. Failed edits and patches now report failure instead of a misleading success, aft_inspect returns a compact text summary instead of a JSON dump, and the bash pipe handling added in v0.36.0 is now careful about commands where stripping a pipe would change what you asked for.

Edits and patches report failure honestly

A failed edit (no match, ambiguous match, syntax rollback) or a failed apply_patch hunk could come back looking like it succeeded — edit returned Edited (+0/-0) and apply_patch reported the file as written. Both now surface the underlying error. For apply_patch specifically, a move hunk now confirms the destination write succeeded before removing the source, so a failed write can never delete the original, and a partially-applied patch is reported as "Partially applied (N of M)" listing only the hunks that actually landed.

edit with replaceAll now refuses to run when its matches overlap (which could happen on repeating multi-line patterns), instead of applying them and corrupting the file.

Compact aft_inspect output

aft_inspect returned a full JSON payload that the agent had to parse. It now returns a compact, line-oriented summary of the actionable findings — duplicates, dead code, unused exports, todos, and diagnostics — cutting the per-call output roughly in half. When a category's background scan is stale, the last-known counts are shown (flagged stale) instead of being dropped, so the summary stays consistent with the status bar.

Safer bash pipe handling

v0.36.0 started running a test/build runner without its trailing filter (so bun test | grep fail doesn't hide failures). This release makes that careful: it no longer strips when doing so would lose a redirected file (... | grep FAIL > out.txt), change backgrounding, or misread a subshell or command substitution, and it only applies to read-only test/build invocations — never a stateful command like make deploy test. It now also recognizes more runners (gradle, maven, dotnet, rspec, phpunit, swift, deno, and others), and tells you on every path when a pipe was dropped.

Fixes

  • move_symbol no longer scans or rewrites build artifacts: the consumer search now honors .gitignore/.aftignore and skips node_modules, target, dist, build, .venv, and the like, so it won't rewrite imports inside compiled output or crawl huge irrelevant trees.
  • Multi-name Java static imports generate one valid import static Class.member; per name instead of a single invalid statement that dropped the members.
  • Glob edit reports its real counts (Edited N files (M replacements)) instead of a misleading +0/-0.
  • Edit diffs render reliably in the UI again after a metadata-handling change (#96).
  • The status sidebar reconnects more reliably and no longer briefly shows another project's data in multi-window setups.
  • Leaner output: grep result footers and bun test banners are trimmed of low-value noise.

Join our Discord

v0.36.0

07 Jun 18:35

Choose a tag to compare

v0.36.0: Persisted call graph, leaner bash output, and sharper code search

The call graph now lives in a persisted, incremental store instead of being rebuilt from scratch on demand. That removes the file-count limit that capped dead-code analysis on large repositories, makes aft_callgraph queries resolve from disk, and improves edge accuracy across more languages. This release also stops shell pipes from hiding test failures, steers agents toward aft_search for code discovery, and fixes a handful of crashes and papercuts.

Persisted call graph (and the end of the file-count cap)

aft_callgraph and aft_inspect's dead-code analysis used to build a whole-project call graph in memory, re-parsing every file on demand. That build was capped at lsp.max_callgraph_files (5000) to avoid the multi-core CPU spikes reported in #86, which meant dead-code analysis simply turned off above that size.

The call graph is now a persisted SQLite store, built once in the background and updated incrementally as files change. Queries read from disk, so callers/callees/impact/trace return without rebuilding anything, and the cold build reuses the symbol cache instead of re-parsing. On this repo the cold build is ~14s; on a 5,700-file TypeScript repo ~13s, after which updates are incremental.

The lsp.max_callgraph_files cap is gone for the call-graph store. Dead-code analysis runs on large repositories again. The cold-build's parsing pass is bounded to half your cores so it can't monopolize the machine while it runs.

More accurate call edges

The store resolves method calls more precisely. It infers receiver types (for self/Self, parameters, and common bindings in Rust, Java, Kotlin, and C++) to produce exact method-dispatch edges, falls back to name matching when a type can't be resolved, and applies a standard-library denylist so calls like .expect() or .map() don't generate thousands of spurious edges. Call extraction now also covers C, C++, C#, and Zig.

Bash output stops hiding failures

Piping a test or build command through a filter, like bun test | grep fail or cargo test | grep -A3 FAILED, used to strip the output down before AFT's compressor ran, which often threw away the failure detail and summary you actually wanted. When output compression is on, AFT now recognizes that pattern, runs the command without the trailing filter, and lets the compressor keep the failures and summary. This also works through a leading cd <dir> && ... prefix. Pass compressed: false to keep your pipe verbatim.

Sharper code search steering

Agents reach for grep/rg/find in bash out of habit even when AFT's indexed search is faster and better ranked. Workflow guidance and the per-command hints now steer code discovery to aft_search and the other aft_* tools, run in parallel, and when aft_search is available the grep-rewrite footer points there instead of suggesting the grep tool.

Status bar accuracy

The E/W (errors/warnings) counts in the agent status bar are now filtered to the same build membership aft_inspect and tsc use, so files excluded from your tsconfig (test files, build output) no longer inflate the count with diagnostics that your compiler would never report.

Fixes

  • foreground_wait_window_ms had no effect (#102): a small timeout passed to a foreground bash call collapsed the wait window and could kill the command almost immediately. A foreground timeout below the wait window is now ignored (the command runs to the normal window, then promotes to background), while an explicit background: true task still honors a short timeout as a real kill cap.
  • aft_delete and aft_safety checkpoint could crash on a mistyped argument: when a host delivered the files array as a bare string or a JSON-encoded string, the tool threw inputs.map is not a function before doing anything. The argument is now coerced at the boundary, so it works or returns a clear error instead of crashing.
  • Trailing newline is preserved in compressed terminal output instead of being dropped from the last line.
  • Call-graph lookups no longer show as tool errors in the UI when a symbol isn't found or the store is still building; they return a normal result.
  • Semantic index no longer hammers a slow embedding backend with fixed-interval retries; refresh retries back off and trip a circuit breaker.

Thanks to @de-flandres (#102, pinpointing the wait-window override).

Join our Discord

v0.35.4

05 Jun 09:05

Choose a tag to compare

v0.35.4: Large-repo CPU relief and concurrent-work stability

A patch release that stops two high-CPU situations on large repositories and under concurrent activity, fixes a crash on deeply-nested files, and hardens the semantic index, watcher, and background bash against transient failures.

High CPU on large repositories (#86)

On large repositories, aft_inspect's dead-code analysis built a whole-project call graph by re-parsing every file and resolving cross-file edges with no size limit, and it did so proactively shortly after the bridge started. On a repository with tens of thousands of files this pegged most CPU cores for a long time, even when no one asked for an inspection. Reported in #86.

The dead-code call-graph build now respects the same lsp.max_callgraph_files limit (default 5000) that the interactive aft_callgraph tools already use. Above that limit, dead-code analysis reports as unavailable instead of running the unbounded build. Duplicate detection and unused-export analysis are unaffected. Raise lsp.max_callgraph_files if you want dead-code analysis to run on a larger repository. A persisted, incremental call graph that lifts this limit entirely is in progress.

Embedding storm under concurrent activity

When the embedding backend returned a transient error during an incremental index refresh (for example a local LM Studio or Ollama server briefly overloaded by several projects indexing at once), AFT treated it as fatal and fell back to re-embedding the entire corpus. That full rebuild hit the already-busy backend, failed again, and cascaded into a sustained multi-core (and, with a GPU backend, multi-bridge) storm.

A transient refresh failure now keeps the existing index and retries only the changed files later, instead of dropping the cache and rebuilding everything. Only a genuine permanent error (such as an embedding-dimension change) triggers a rebuild.

Crash on deeply-nested files

A follow-on to the v0.35.3 crash fix: the call-extraction walkers used during inspection could still overflow the worker stack on a pathologically deep file (minified bundles, generated code, very long chained expressions), aborting the AFT process. Those walkers are now depth-bounded, so deep files are handled safely.

Resilience fixes

  • The semantic index recovers from a brief embedding-backend outage instead of parking in a failed state, and detects the local runtime version more reliably.
  • The watcher no longer leaves the index status stuck on "refreshing" after a transient hiccup, and sidebar status updates are pushed promptly.
  • Background bash keeps its worker bridge alive across a transport timeout rather than tearing it down, so long-running tasks survive.

Join our Discord

v0.35.3

04 Jun 13:51

Choose a tag to compare

v0.35.3: Inspect crash fix, semantic self-heal, Code Health in the sidebar

A patch release fixing a background-scan crash and a stuck semantic index, surfacing codebase health in the TUI sidebar, and fixing a silent LSP auto-install failure.

Inspect background scan no longer crashes on deep files

The duplicate-detection scanner walked each file's syntax tree recursively with no depth limit. A pathologically deep file (a minified bundle, generated code, or a very long operator/promise chain) could overflow the background worker's stack and abort the whole AFT process. The scanner now bounds its recursion and runs on a larger worker stack, so deep files are handled safely.

Semantic index recovers from transient backend blips

If the embedding backend was briefly unavailable when an index build started (for example restarting a local LM Studio or Ollama server mid-build, or a model still loading), the index parked in a permanent failed state and only a restart would retry it. Transient failures, including the HTTP 400 a local backend returns while a model is loading or being unloaded, are now treated as temporary: the index stays in a loading state, retries with backoff, and recovers on its own once the backend answers. A genuine misconfiguration (auth, dimension mismatch) still fails fast with a clear message.

Code Health in the TUI sidebar

The codebase-health counts that the agent sees in its status bar now also appear in the OpenCode TUI sidebar and the /aft-status view: live LSP errors and warnings, plus duplicate and TODO counts. The collapsed sidebar shows them as three at-a-glance traffic lights. Dead-code and unused-export counts are intentionally held back from this view for now while their accuracy on real TS/JS codebases is improved.

LSP servers install reliably again

AFT installs language servers with npm install --no-save into its own cache directory. When that directory has no package.json, npm walks up the tree and, if any parent (for example a package.json in your home directory) has one, installs into that package instead. The cache directory ends up empty while npm still exits successfully, so the install silently failed and you saw a recurring "LSP binary missing" warning even with lsp.auto_install enabled. AFT now writes a small anchoring package.json into the cache directory before installing, so the server lands where AFT expects it. Affects both OpenCode and Pi.

README

Fixed the YAML row in the Supported Languages table (wrong column count) and a missing blank line that made a horizontal rule render as a heading.

Thanks to @rustybret (#92, diagnosing the npm walk-up cause) and @Suknna (#93/#94, the README fix).

Join us on Discord: https://discord.gg/DSa65w8wuf

v0.35.2

03 Jun 21:12

Choose a tag to compare

v0.35.2 — Leaner edit output, GUI-launch npm fixes, and a background-task leak fix

A focused patch: edit results are now a compact line instead of a JSON blob, auto-update and LSP install work when OpenCode is launched from the dock with a stripped PATH, and background bash no longer leaves zombie processes behind.

Leaner edit output

edit now returns a single compact line — Edited (+3/-1)., Edited (+2/-0, 2 replacements)., or Applied edits to 2 files. — instead of the raw JSON envelope it used to hand the agent. The full diff still renders in the editor/TUI; the agent just gets the summary, which trims roughly 90 tokens off every edit, the most frequent operation. Rollbacks now say so in plain language, and both OpenCode and Pi return the same shape.

Leaner aft_search output

aft_search results are tighter too. The redundant JSON dump that followed the formatted results is gone, results are ordered by relevance, and snippet detail now scales with rank — the top hit shows the most context, lower-ranked hits show less — so a search costs fewer tokens without losing the signal that matters.

GUI-launch npm resolution

Launching OpenCode from the macOS dock (or any GUI) starts it with a stripped PATH that omits version-manager directories like mise, nvm, volta, and asdf. AFT's auto-updater and LSP auto-installer spawned a bare npm, which then failed — so the plugin could silently stay on an old version, or skip installing a language server. AFT now resolves npm from PATH, the active Node install, and the common version-manager locations before spawning. doctor --fix also repairs a plugin that fell behind because of this.

The auto-updater no longer removes the installed package before it has confirmed npm can reinstall it, so an interrupted update can't leave the plugin uninstalled.

Background bash zombie fix

A background bash task that ran short-lived child commands (for example a quick mv) could leave the child as a zombie process after it exited. The task lifecycle now reaps the child on its terminal path, so background commands no longer accumulate defunct processes.

Other fixes

  • File tools (read, write, edit, aft_outline, aft_zoom) now expand a leading ~ in paths, matching the search tools — ~/Work/... resolves to your home directory instead of being treated as a literal folder.
  • The TUI sidebar now updates while a semantic index is building instead of appearing stuck, showing progress as it goes.

Join us on Discord: https://discord.gg/DSa65w8wuf

v0.35.1

03 Jun 10:01

Choose a tag to compare

v0.35.1

A patch release fixing three setup and configuration bugs.

Fixes

  • Config files with comments no longer silently disable all settings. A // or /* */ comment placed inside a nested object in aft.jsonc (for example inside lsp.servers) could throw during validation and make the entire config fall back to defaults, silently turning off search index, semantic search, and everything else with no error shown. JSONC comments are now stripped before validation, so commented config loads correctly. Reported in #88.
  • npx @cortexkit/aft setup / doctor / --version no longer spawn runaway processes. When the native aft binary was not yet cached (a fresh install, or mid auto-download), the CLI's binary lookup could resolve aft on PATH to its own npx script shim and re-invoke itself, spawning opencode --version and pi --version in a loop. The resolver now accepts only real native executables from a PATH lookup, and the host version probes have a timeout, so this cannot happen.
  • aft setup now detects OpenCode Desktop. Detection previously required the opencode CLI on PATH, so users running only OpenCode Desktop (which does not add a CLI to PATH) saw "no supported harness detected" and setup bailed. Setup now recognizes OpenCode from its config directory and app bundle, not just the CLI.

Join us on Discord: https://discord.gg/DSa65w8wuf

v0.35.0

03 Jun 06:42

Choose a tag to compare

v0.35.0 — Leaner semantic indexing, agent status bar, sharper aft_inspect

This release substantially cuts the CPU and memory that local semantic indexing uses, adds an IDE-style status bar that surfaces codebase health on tool results without the agent asking, makes aft_inspect substantially more accurate (dead code, duplicates) and keeps its background scans fresh during long sessions, and makes the TUI sidebar collapsible. It also adds npx @cortexkit/aft --version, session-scoped doctor --issue, and an actionable hint when a rust-analyzer-style toolchain component is missing.

Leaner semantic indexing

Local semantic indexing now uses roughly half the CPU and a fraction of the peak memory, with no change to search results and no re-index required.

  • CPU is bounded. Embedding previously ran on every core, which pegged the machine and competed with the agent for the duration of a build. It now caps its worker threads at half the available cores. In our measurements that is about 40% less CPU and about 24% faster, because oversubscribing the cores was hurting throughput as well — on an 18-core machine the indexer now uses ~9 cores instead of all 18.
  • Memory is bounded. A token budget now bounds each embedding pass so a batch that happens to contain very long symbols can no longer spike memory. On a 20,000-file repository the worst-case peak dropped from ~4.9 GB to ~1.3 GB, which keeps large-repo indexing safe on 8–16 GB machines.
  • Existing indexes keep working. The reworked embedder produces vectors that match the previous backend, so your current semantic index continues to serve queries unchanged — no rebuild and no quality change.

Faster startup on large repositories

Files that contain no top-level symbols were being re-parsed on every bridge startup — visible in logs as a recurring "N new files" churn on large repositories. They are now cached like any other file and skipped on subsequent starts, reducing repeated startup work.

Agent status bar

Tool results now end with a compact one-line health bar — an IDE status-bar analog the agent sees as it works, instead of having to call a tool to check:

[AFT E0 W0 | D333 U221 C1160 | T8]

E/W are live LSP errors and warnings for files touched this session — a universal compile-error signal across every language with a language server, not just tsc/cargo. D/U/C are the aft_inspect Tier-2 counts (dead code, unused exports, clone/duplicate groups) and T is the TODO count. A ~ before D marks the Tier-2 counts as predating the latest edit, with current numbers a single aft_inspect call away.

The bar is emitted on change rather than on every call, so the transcript doesn't accumulate identical lines, and the latest bar always reflects current health. The legend is taught once in the system prompt, so each appearance costs only the compact values. Available on both OpenCode and Pi.

aft_inspect is more accurate and stays fresh

Dead-code accuracy. Reachability now propagates liveness through methods reached only by dispatch and through type references, recognizes real entry points from Cargo.toml and package.json (binaries, main/module/exports, script targets) instead of a filename heuristic, and resolves dist/ public-API entries and NodeNext .js import specifiers back to their src/ TypeScript sources. Together these remove large clusters of false positives — command handlers, barrel re-exports, and dispatch-live helpers that earlier versions flagged as dead.

Duplicate collapse. Overlapping and nested duplicate fragments now collapse to their maximal outer span instead of each sub-fragment counting as its own group, so the duplicate count reflects distinct duplicated regions rather than every nested slice.

Ranked drill-down. Findings are ranked by signal tier — product code before tests and tooling — and the summary now carries a cost-ranked top-N preview so the highest-value items surface first.

Background freshness. A watcher-driven refresh scheduler now keeps the Tier-2 scans current during a working session — debounced after edits, with a maximum-staleness ceiling so continuous activity still triggers a refresh — rather than waiting for an idle window that a busy session never reaches. This runs in the binary, so it works the same on OpenCode and Pi.

Collapsible sidebar (OpenCode TUI)

The AFT sidebar panel is now collapsible. Click the ▼ AFT header to collapse it to a condensed view — Search Index and Semantic Index status dots plus a compact compression line (7.7M / 64%) — and click ▶ AFT to expand it again.

Languages

  • SCSS (.scss) is now a recognized language for outline, symbol zoom, and AST search.
  • .inc files are treated as PHP, so the common PHP include extension gets full parsing, outline, and import support.

CLI

  • npx @cortexkit/aft --version reports the CLI, binary, and per-harness host and plugin versions in one command.
  • doctor --issue can scope a report to a specific session. It lists your recent OpenCode/Pi sessions; choosing one filters the bundled logs to that session, while "General" keeps everything. Secret and path redaction still apply.
  • doctor --fix adds the config $schema URL when it is missing, so editor validation and autocomplete light up.
  • The harness picker for doctor is now single-select; setup stays multi-select.

Fixes

  • Deleted files no longer leave phantom diagnostics. When a file is removed (aft_delete, rm, a branch switch, a failed rebase), its cached LSP errors and warnings are cleared instead of lingering in the warm set and inflating the status bar and aft_inspect counts.
  • Missing toolchain language servers now get an actionable hint. When a server on PATH is a toolchain proxy whose component is not installed — most commonly rust-analyzer — AFT reports rustup component add rust-analyzer instead of the raw Unknown binary error.
  • Sidebar no longer flickers blank. A transient pre-initialized status snapshot during a binary swap or session-directory change no longer overwrites a good one and blanks the panel; the last good snapshot is kept until real data returns.
  • Oversized YAML/Kubernetes manifests no longer break semantic indexing. A manifest packing a large inline script (a CronJob/Job command: body) into one symbol could exceed the embedding backend's per-request token limit and abort the entire index build, silently degrading every search to lexical-only. Embedding text is now clamped so a single oversized symbol can't take down the build.
  • edit no longer drops a trailing newline on a fuzzy match. When a whitespace-tolerant match landed on the last lines of a file and the replacement text had no trailing newline, the final replaced line could be joined to the following line. The trailing newline is now preserved.
  • Partial overrides of a built-in LSP server work again. Setting lsp.servers.<id> to tweak a single field of a built-in server (for example pointing rust at a custom binary) no longer requires re-specifying extensions and binary; the built-in's values are inherited, and the whole lsp config section is no longer silently dropped when they are omitted.
  • Call-graph queries are now timed in the logs. aft_callgraph callers on a large repository logs how long parsing and edge resolution took, so a slow first call (or a project_too_large rejection) is attributable instead of appearing as an opaque stall.

Pi

  • The aft_conflicts and grep bash-output hints — which nudge toward aft_conflicts after a failed merge and toward the indexed grep tool — now fire on Pi as well as OpenCode.

Thanks to @M0Rf30 (#79, semantic-indexing fix for oversized YAML/Kubernetes manifests) for the contribution to this release.

Join us on Discord: https://discord.gg/DSa65w8wuf

v0.34.0

01 Jun 07:10

Choose a tag to compare

v0.34.0 — Imports for 12 more languages, YAML/Kubernetes, cross-repo conflicts

This release extends aft_import from 5 languages to 17, adds YAML and Kubernetes/CRD-aware code intelligence, lets aft_conflicts inspect any repository or worktree, adds an .aftignore file and ripgrep-style explicit-file grep, makes aft_zoom call-graph annotations opt-in, and turns two previously-hardcoded limits into configuration.

aft_import now supports 12 more languages

aft_import (add / remove / organize) previously covered TypeScript, JavaScript, Python, Rust, and Go. It now also handles Solidity, Java, C#, PHP, Kotlin, Scala, Swift, Ruby, Lua, Perl, C/C++, and Vue — 17 languages total.

Each engine understands its language's real import shape rather than treating imports as plain text:

  • Require-style languages (Ruby require/require_relative, Lua require(...)) preserve binding and call style.
  • Swift supports @testable and kind-imports (import struct Foo.Bar); Java supports static imports; Solidity supports named, namespace (* as), and aliased forms.
  • Scala 2 wildcard (_) and rename (=>) syntax is preserved verbatim when organizing, so organize never rewrites valid imports into invalid ones.
  • C/C++ #include groups system (<...>) before local ("...") and accepts modules with or without delimiters.
  • Vue Single-File Components rewrite imports inside the <script> block, leaving <template> and <style> untouched.

New optional parameters cover the forms these languages need: namespace, alias, modifiers, and importKind.

Adding an import to a file that has a header but no existing imports now inserts after the header — after a Go package, a Solidity SPDX/pragma, a PHP <?php, or a Java/Kotlin package declaration — instead of at the top of the file. For a PHP file wrapped in a braced namespace Foo { ... }, the import is placed inside the namespace body, and a script starting with a #! shebang keeps the shebang on the first line.

YAML and Kubernetes/CRD support

aft_outline, aft_zoom, aft_search, and ast_grep_* now understand YAML (.yaml / .yml), and YAML files are embedded in the semantic index. Kubernetes and CRD manifests — any document carrying apiVersion + kind — become a single rich symbol named <namespace>/<Kind>/<name>, with high-signal spec fields folded into the embedding so intent queries match: container images and ports, CPU/memory limits, volume mounts, replicas, env var names, RBAC verbs/resources, and Argo Workflow entrypoint/templates/schedule. Plain YAML (docker-compose, CI config, Helm values.yaml) surfaces its top-level keys as symbols, and multi-document streams are handled per document.

aft_conflicts can inspect any repository or worktree

aft_conflicts now takes an optional path parameter, so you can point it at the repository or git worktree where a rebase or merge is actually running instead of only the session's project root. Discovery is anchored at that path's git top level (so it works from any subdirectory), and it unions unmerged-index files with a working-tree scan for conflict markers — so a file that was git added but still carries markers is reported instead of silently missed. The output now names the checked repository root, so a "No merge conflicts found" result is never ambiguous about which repository it actually examined.

.aftignore

AFT now honors an optional .aftignore file, layered on top of .gitignore. It uses the same syntax, is hierarchical, and works in non-git projects.

Use it to exclude paths from every AFT walk — trigram index, semantic index, call graph, aft_inspect, scoped diagnostics, and the ripgrep fallback — that you can't put in .gitignore, most commonly git submodules you don't want indexed. Edits under an .aftignored path also stop triggering reindexing, and editing the .aftignore file itself refreshes the indexes so newly-ignored files drop out without a restart.

grep searches explicitly-named files even when ignored

Naming a single file in grep (for example path: "captures/log.txt") now searches that file even when it is gitignored or .aftignored, matching ripgrep. Previously an explicitly-named ignored file returned 0 matches across 0 files, which read as "no matches" rather than "never searched". Directory searches still honor .gitignore and .aftignore as before.

aft_zoom call-graph annotations are now opt-in

aft_zoom no longer appends call-graph annotations (calls-out / called-by) by default. The default output is now just the symbol source, keeping zoom focused and token-light. Pass callgraph: true to include the annotations. For full cross-file call relationships, use aft_callgraph.

Configurable limits

  • semantic.max_files (default 20000, raised from a hardcoded 10000) caps how many files the semantic index embeds. Semantic search now works on monorepos that previously hit the 10k limit. Raise it further when using a remote embedding backend that embeds server-side, or lower it on memory-constrained machines.
  • bash.foreground_wait_window_ms (default 8000, minimum 5000) controls how long a foreground bash command runs before it is promoted to a background task. The previous window was a fixed 5 seconds.

Fixes

  • C/C++ includes added with a delimiter (#include <string>) are no longer double-wrapped into invalid #include <<string>>.
  • Import failures that roll back now report the failure instead of returning a success with no change.
  • Backup loading no longer logs a per-entry warning for every legacy backup directory on startup; stale directories from older path-hash schemes are skipped quietly.
  • aft_inspect dead-code analysis resolves cross-file, barrel re-export, and namespace-import (import * as ns) liveness more accurately, reducing false positives.
  • aft_import rejects module paths with .. traversal segments or absolute paths for C/C++, Solidity, and PHP, and aft_import remove/organize_imports report no_op when nothing changed.
  • ONNX Runtime detection now finds the library in a lib/ subdirectory, so manual installs of Microsoft's release archive are detected instead of reported as missing.
  • Formatter and type-checker lookup now searches system locations that GUI-launched editors miss from PATH/usr/local/go/bin (official Go installer), /usr/bin, and /snap/bin — so tools like gofmt, go, and gopls are found when installed system-wide.
  • aft doctor --issue no longer files automatically. It writes a sanitized report, asks you to review and edit it, and only files after you confirm. Redaction now also strips the project directory path and credential-shaped values (tokens, API keys, Authorization headers, URL credentials), and non-interactive terminals never auto-file.
  • ONNX Runtime archive extraction rejects symlinks that escape the staging directory, including across Windows drive letters.
  • Formatter/checker warnings after configure now only appear when formatting or validation is actually enabled (format_on_edit / validate_on_edit or an explicit per-language formatter/checker), instead of firing from a project config file even when those features are off.
  • Configure warnings are delivered as idle-time notifications and no longer switch the session's model or agent (or stall the first tool turn) when they fire.
  • Windows tool resolution finds Biome, Ruff, and other formatters/checkers installed via npm that are present on PATH, instead of reporting them missing.

Pi

  • AFT's namespaced tools in the Pi plugin (aft_import, aft_refactor, aft_safety, aft_transform, ast_grep_replace, aft_delete, aft_move) now prompt for external-directory access when restrict_to_project_root is enabled, consistent with Pi's hoisted file tools.

Thanks to @M0Rf30 (#73, YAML/Kubernetes support), @Zireael (#72, configure-warning gating and Windows tool resolution), and @Suknna (#75, unified configure-time tool detection) for their contributions to this release.

Join us on Discord: https://discord.gg/DSa65w8wuf

v0.33.0

30 May 06:31

Choose a tag to compare

v0.33.0

This release adds aft_inspect — a single codebase-health view for agents — and reworks how diagnostics reach the agent. It also renames aft_navigate to aft_callgraph and lands a large batch of aft_search correctness fixes.

aft_inspect — codebase health at a glance

aft_inspect gives an agent the "open the project and see what's wrong" view in one call, instead of stitching together greps and separate tools. One call returns a summary across categories; pass sections to drill into any of them.

Categories:

  • diagnostics — compile/type errors from language servers
  • metrics — file count, lines of code, symbol count
  • todos — TODO/FIXME/HACK/XXX/BUG markers with file, line, and author
  • dead_code — exported symbols with no reachable caller
  • unused_exports — exported symbols nothing imports
  • duplicates — near-identical code blocks across files

Treat dead_code as a hint, not an authority: reachability is call-based, so symbols reached only through method dispatch (receiver.method()) or referenced only in type position can still appear as false positives. Verify before deleting.

It runs in tiers. Metrics and todos return synchronously from cache. dead_code, unused_exports, and duplicates run as background scans on session idle; calls return cached results immediately (marked stale when a refresh is pending) rather than blocking. Results persist to disk per project, so they survive restarts.

Options:

  • sections — one category or an array; all for everything; omit for summary-only
  • scope — restrict to a file or directory
  • topK — cap drill-down items per category

Scan results persist across restarts

Tier-2 scan results are stored on disk. When you reopen a project after editing files, aft_inspect serves the last completed scan immediately (flagged stale) while a fresh scan runs in the background, instead of showing nothing until the rescan finishes.

Diagnostics now flow through aft_inspect

Diagnostics no longer arrive automatically after every edit. Previously edit, write, and apply_patch waited on language-server diagnostics inline on each call. That work moved into aft_inspect, which an agent runs after a batch of edits and before tests or a commit. The result is faster edits and a single deliberate point to check the codebase.

  • The standalone lsp_diagnostics tool is removed; its capability is part of aft_inspect's diagnostics category.
  • A new lsp.diagnostics_on_edit config option (default off) restores per-edit diagnostics for users who want them.
  • aft_inspect diagnostics are build-authoritative: TypeScript/JavaScript files excluded by tsconfig.json (files/include/exclude, following extends) are skipped, so results match a real tsc run instead of reporting phantom errors on excluded files.
  • Scoped diagnostics on a single file wait briefly for a cold language server to start and publish, so the result arrives in one call rather than requiring a re-run. Scopes whose file types have no registered or applicable server report a terminal no_server status instead of waiting forever.

aft_inspect diagnostics are a fast checkpoint, not the project-wide authority — a clean tsc / cargo check / pyright run remains the real gate.

aft_navigate is now aft_callgraph

The call-graph tool is renamed from aft_navigate to aft_callgraph. The new name matches what the tool does — answer code-relationship questions (callers, call tree, impact/blast radius, execution paths, data flow) from a real call graph — and is the term models reliably reach for. All operations (callers, impact, call_tree, trace_to, trace_to_symbol, trace_data) are unchanged.

The Rust call graph now resolves cross-file callers. Calls through crate::, super::, self::, a workspace crate name (e.g. a binary's mycrate::module::func(...)), and use-imported short names now resolve to the right definition. Previously only same-file callers were found, so callers/impact on a Rust symbol returned far too few results and dead_code over-reported. Liveness also now flows through private intermediary functions, so handlers reached only via a private router are no longer flagged dead.

aft_search correctness

A batch of fixes to the unified aft_search shipped in v0.32:

  • Disabled vs. unavailable. With semantic_search: false, a natural-language query now returns a clear "not enabled" result instead of silently degrading to a lexical scan. The degraded lexical fallback is reserved for when semantic is enabled but temporarily building or unavailable.
  • Truthful routing. interpreted_as reports the lane that actually ran (lexical when the semantic lane was skipped), not the lane that was requested.
  • Result provenance. Each result carries the lane that produced it plus a hybrid_boosted flag, and more_available now accounts for engine caps and semantic overflow so "more results exist" is accurate.
  • Quoted phrases. Surrounding quotes on a literal query ("was not found on PATH") are stripped before matching, matching rg -F / code-search behavior, instead of searching for the quote characters.
  • Classifier routing. A sentence-final ? no longer forces regex mode; a query mixing natural language with an error code routes correctly; bare escape sequences route to regex while Windows-path-shaped queries stay exempt.
  • No hidden truncation. Lexical candidates beyond the old internal cap are no longer dropped silently before fusion.

Edits no longer echo back to the agent

edit, write, and apply_patch were returning unnecessary diff bytes back to LLM. The agent-facing result is now minimal (success plus added/removed line counts).

Bug fixes

Editing and refactoring:

  • aft_safety undo history no longer lost on restart. The first edit to a previously-backed-up file after a restart could overwrite that file's entire on-disk undo history (and reuse a backup id). Prior history is now hydrated before the new snapshot is appended.
  • apply_patch pure-insertion hunks insert at the resolved context line instead of end-of-file.
  • aft_transform add_member on a generic impl<T> Foo<T> block now inserts methods into the impl block, not the struct body.
  • aft_refactor move no longer adds a spurious import when a parameter or local variable shadows the moved symbol's name.
  • aft_import organize correctly handles Rust nested use trees (a::{b::{c, d}, e}) without corrupting them.
  • aft_zoom no longer raises spurious mutual-exclusion errors when a model sends empty or empty-content targets/symbols parameters.

Search and indexing:

  • The semantic index verifies per-file freshness strictly and uses canonical paths for watcher invalidation, so edits refresh the right entries and large files aren't downgraded to a weaker freshness check.
  • aft_outline accepts JSON registry/package URLs and outlines their top-level keys.
  • URL fetching for aft_outline/aft_zoom retries transient connect/transport failures and now runs entirely in Rust.

Reliability and security:

  • The worker bridge stays alive through transient starvation timeouts and only escalates to a kill on a genuine hang, so a busy bridge no longer drops unrelated requests.
  • SSRF protection on URL fetches now blocks RFC 6598 (CGNAT, 100.64.0.0/10) and RFC 2544 (198.18.0.0/15) ranges, including their IPv4-mapped IPv6 forms.
  • Windows hardening: validated LoadLibraryExW ONNX Runtime loading, NuGet cache scanning, storage directory creation, and a binary-replacement retry (contributed in #66/#69).
  • Killing a PTY-mode background task on Windows now finalizes reliably instead of hanging, so bash_kill and the polling that waits on it always return.
  • LSP and formatter "not installed" warnings no longer re-fire on every session, and synthetic notifications preserve the active model and variant.

Join us on Discord: https://discord.gg/F2uWxjGnU

v0.32.0

27 May 02:36

Choose a tag to compare

v0.32.0 — Unified aft_search and queryable-during-refresh semantic

The headline change: aft_search is now a single tool that handles every code-search shape — exact identifiers, anchored regex, error messages, natural-language descriptions, and file/URL paths. It auto-routes between regex, literal, semantic, and hybrid lanes based on query shape, with a hint parameter for explicit overrides. Output adapts per mode — grep-style lines for regex/literal, symbol-blocks with provenance for semantic/hybrid. The semantic index now also stays queryable through edits instead of falling back to lexical-only after every save.

Unified aft_search

aft_search replaces the previous split between concept search and grep-style lookup. One query parameter, automatic mode detection, one consistent response shape per mode.

  • Classification before status check. Regex queries succeed even when the semantic backend is unavailable; the lexical lane is always available when grep is registered.
  • Pre-Tier path/URL exemption. Queries shaped like file paths (src/lib/main.rs), Windows paths (C:\new\test), URLs (https://api.github.com), and filenames with metacharacters (is_valid?.ts, Cargo.lock) stay in hybrid mode instead of misrouting to regex.
  • Sequence-based regex detection. Sequences like .*, .+, \d+, and [A-Z] correctly trigger regex routing while bare punctuation that commonly appears in code (map.get(), foo(), bar?.baz) stays hybrid.
  • hint override. Pass hint: "regex", hint: "literal", or hint: "semantic" to force a specific lane. Short literals (under three bytes) honor hint: "literal" with a full scan instead of silently rerouting to semantic.
  • Adaptive output per query mode. Regex and literal modes return grep-style file:line: text matches. Semantic and hybrid modes return symbol-blocks with source: "semantic" | "lexical" | "hybrid" provenance per result. The interpreted_as field tells callers which shape to expect.
  • Response flags reflect engine limits. more_available, engine_capped, and fully_degraded replace the previous total_matches field, which conflicted with the engine's caps. humanize_degraded_reasons translates internal codes to user prose.
  • Tier D rejections. Lookaround, backreferences, and other regex features the engine doesn't support return explicit errors with rewrite guidance instead of silent zero-match.

The two plugin layers use the same query classification before mutual-exclusion permission checks, so OpenCode and Pi behave identically.

Semantic index stays queryable through edits

Previously, aft_search fell back to lexical-only after every file save because the watcher invalidation set SemanticIndexStatus to Building. The in-memory index still held fresh embeddings for every unchanged file, but the query gate matched on Building regardless of stage and refused the semantic lane.

SemanticIndexStatus::Ready now carries a refreshing: Vec<PathBuf> list. Watcher invalidations append the changed file to that list without leaving Ready. The query path runs the normal semantic lane and adds a soft warning when files are mid-refresh. Building is now reserved for cold builds and fingerprint changes (model, embedding dimension, or base URL changed).

User-visible effects:

  • aft_search returns real semantic results immediately after edits, with a warning like "1 file(s) refreshing; results for those files may be temporarily missing".
  • The TUI sidebar and /aft-status dialog show Ready (N file(s) refreshing) as a small dim line instead of Rebuilding…. Above 20 refreshing files it collapses to Ready (many files refreshing).
  • The status RPC adds refreshing_count to the semantic block. Existing fields are preserved.

Workflow hints promote aft_search

The system prompt's code-exploration section now teaches aft_search as the primary code-search tool, with grep framed as the specialized fallback for exhaustive enumeration (every TODO, every import of X) or strict path-scoped search. Users running with semantic_search: false continue to see the grep-primary hint unchanged.

Bare escape sequences route to regex

Bare \n, \t, and \r queries now correctly route to regex mode. They were missing from both tier_a_regex_signal and the path-exemption guards in the v0.32 classifier. Path-shaped queries containing those escapes (Windows C:\new\test) remain exempt and stay hybrid.

Empty params no longer mislead the agent

GPT-family models often send empty strings, empty arrays, and empty objects ("", [], {}) instead of omitting optional parameters. Previously, that triggered misleading mutual-exclusion errors like 'targets' is mutually exclusive with 'filePath', 'url', and 'symbols' when the agent only meant to pass filePath. The plugin now normalizes empties to undefined before mutual-exclusion checks.

Affected tools across both plugins:

  • aft_zoomtargets: [] and symbols: "" no longer trigger spurious exclusion errors.
  • aft_refactor — required-field validation rejects empty strings for symbol, destination, and name instead of accepting them and crashing downstream.
  • ast_grep_search / ast_grep_replace — empty paths and globs arrays no longer round-trip to Rust as "scope present" when the agent meant whole project.

OpenCode's tool-call header also now stringifies array and object args into the rendered metadata so users can see what the agent actually sent in the call.

Join us on Discord: https://discord.gg/F2uWxjGnU