Releases: cortexkit/aft
Releases · cortexkit/aft
v0.9.0
v0.9.0
🌐 New Language Support
- C, C++, Zig, C# — full tree-sitter grammar support for outline, zoom, and AST search/replace
- C++ captures classes, functions, methods, namespaces, and templates
- Supported extensions:
.c,.h,.cc,.cpp,.cxx,.hpp,.hh,.zig,.cs
⚡ Search Performance (2-16x faster)
- memchr fast path — literal queries skip the regex engine entirely, using SIMD-accelerated byte scanning
- Parallel file verification —
rayonparallelizes candidate file reading when >10 candidates - Early termination — stops reading files once
max_resultsis reached - Sorted Vec merge — replaces BTreeSet intersection for 2-5x faster candidate narrowing
- Lazy line_starts — only computes line offsets after first match in a file
- Shared
Arc<PathBuf>— reduces per-match allocation overhead - Byte-level file reads — skips binary checks and UTF-8 validation for indexed files
- Posting list sort optimization — conditional sort only when out-of-order, not after every file
Benchmarks (vs ripgrep): 7-236x faster on indexed projects, with the largest gains on rare queries in large codebases (Chromium/base: 236x for WebContents).
📦 Symbol Table Cache
- Parsed symbol tables cached by
(path, mtime)— repeated outline/zoom calls skip re-parsing - Background pre-warm populates the cache during search index build
- Cache invalidation via mtime checks on watcher events
🔄 Stale Index Recovery
- On cold start with matching git HEAD, verifies all file mtimes against disk
- Re-indexes only files that changed (uncommitted edits, stash pop, manual changes while closed)
- Detects and indexes new files not in the stored cache
🎨 Unified Output Format
- Removed
compress_tool_outputconfig — single optimized format for all users - Grep: relative paths,
filepath\n65: text, no decorators, 200-char truncation, 5 matches/file cap with overflow - Glob: relative paths, directory grouping for >20 files, header with match count
📊 Stats
- 835 tests (649 Rust + 186 TypeScript)
- Supported languages: TypeScript, JavaScript, TSX, JSX, Python, Rust, Go, Markdown, C, C++, Zig, C# (12 total)
v0.8.2
v0.8.2
Search Index Fixes
- Fixed trigram false negatives — indexed grep now correctly finds all matches (was silently dropping results due to bloom filter check in
postings_for_trigram) - Added
search_ms— self-reported search engine timing in grep response for benchmarking - Absolute paths in grep results — matches OpenCode's output format
- "Found N matches" header — raw grep output now matches OpenCode's exact format
- mtime-descending sort — grep and glob results sorted by modification time like OpenCode
Binary Safety
- Versioned binary cache — never runs from
node_modules(prevents corruption when npm updates while running) replaceBinaryno longer kills running bridges — just updates path for new sessions- Legacy flat cache removed — only versioned paths used
Other
- Version logged after bridge configure
- Bash grep/rg hint when agents use shell for search
- Compressed output default changed to
true - Search benchmark harness (
benchmarks/search-bench-v2.py)
v0.8.1
v0.8.1
Bug Fixes
- Stable search index cache key — Git repos now use
sha256(root_commit_hash)as the cache key instead ofsha256(directory_path + root_commit). This means/repo,/repo/src, and/repo/crates/aftall share the same index cache entry. Previously, each subdirectory session created a separate cache, causing hundreds of stale entries. - Non-git projects use
sha256(canonical_path)as before.
v0.8.0
v0.8.0
🔍 Indexed Search (Experimental)
- Trigram-indexed
grepandglob— hoists opencode's built-in tools whenexperimental_search_index: true - Background thread builds the index at session start, persists to disk with git HEAD tracking for fast cold starts
- File watcher keeps the index fresh incrementally
- Falls back to direct scanning when the index isn't ready
- Out-of-project paths shell out to ripgrep matching opencode's exact flags
- Compressed output mode (
compress_tool_output: true) — RTK-style grouping, line truncation, overflow summarization for up to 80% token reduction
🎯 Grep/Glob Parity (12 fixes)
- Hidden files now included (
.env,.eslintrc, etc.) - Glob returns all matching files, not just text files
- One match per line (deduplicated, matching ripgrep)
- Results sorted by modification time (newest first)
- Default cap at 100 matches/files (matching opencode)
- Glob returns absolute paths
- Raw grep lines truncated at 2000 chars
total_matchesreports true pre-truncation countpathparameter scopes search to subdirectory- Reliable ripgrep JSON parsing (no more pipe-in-filename bugs)
- Cross-platform binary resolution (no Unix
whichdependency) - Atomic disk writes for index cache (temp file + rename)
🔒 Security Hardening (25 audit fixes)
validate_pathadded to ALL commands including reads (outline, zoom, grep, glob, callgraph, LSP)- Commands use the validated
PathBufreturn value instead of discarding it - Symlink traversal hardened — walks existing ancestors to resolve symlinks
- Security regression tests in
security_test.rs - 50MB file size guard in read command
- Regex DoS prevention via
RegexBuilder::size_limit() - Dry-run permission bypass removed — dryRun still requires permission
- Glob edit_match now runs syntax validation
restore_latest()writes before popping — backup survives write failure- Bridge restart counter resets after 5 minutes of stable operation
- JSONC trailing-comma stripper is now string-aware
prepare_exported_symbolhandlesexport default,export {,export *- Transaction edit_match requires explicit
replacementfield - move_symbol UTF-8 boundary check before slicing
- Dead code cleanup in
rewrite_consumer_imports
🛠 Other Improvements
- Formatter resolution fix — explicit overrides now resolve through
node_modules/.bin - Bash grep/rg hint — nudges agents toward the built-in grep tool
- Bridge stderr formatting cleanup
- Watcher/index drain order fix — prevents lost updates during background rebuild
- Cache key includes project_root path alongside root commit hash
📊 Test Coverage
- 814 tests (628 Rust + 186 TypeScript)
- New: search parity tests, indexed search tests, security regression tests
v0.7.7
Bug Fixes
- Cleaner log formatting — Rust binary stderr now shows as
[aft] messageinstead of[aft-plugin] stderr: [aft] message - README typo — Fixed
"plugins"→"plugin"in installation example - JSONC parser removed — Self-contained JSONC stripper replaces
jsonc-parserdependency (fixes ESM/CJS crash from v0.7.4)
v0.7.6
Critical Bug Fix
- Replaced jsonc-parser with self-contained JSONC stripper —
jsonc-parser's UMD bundle breaks under both ESM named imports (Node.js strict ESM in OpenCode's runtime) andcreateRequire(Bun/OpenCode hybrid). Replaced with a zero-dependency comment stripper + trailing comma removal +JSON.parse. This was the root cause of plugin loading failures since v0.7.2.
Full Changelog: v0.7.5...v0.7.6
v0.7.5
Bug Fixes
Version Mismatch Loop Prevention
- Fixed infinite configure loop — When the binary version is older than the plugin, the version mismatch handler now fires only once per binary version instead of re-triggering on every bridge respawn. Prevents the
Failed to configure bridge after 3 attemptserror users were hitting. - Tracker doesn't reset prematurely — Per Oracle audit, the upgrade tracker stays set after replacement so a still-outdated downloaded binary doesn't re-trigger the loop. Resets naturally on plugin reload.
User Experience
- Log file path in errors —
Failed to configure bridge after 3 attemptsandMax restarts reachednow include the path to$TMPDIR/aft-plugin.logso users can find diagnostic logs. - Empty param handling —
lsp_diagnosticstreats empty stringfilePath/directoryas absent instead of throwing a false mutual-exclusivity error.
Full Changelog: v0.7.4...v0.7.5
v0.7.3
Bug Fixes
- lsp_diagnostics empty string handling — Empty string
filePathordirectoryparams are now treated as absent instead of triggering the mutual-exclusivity error. Fixes'filePath' and 'directory' are mutually exclusivewhen agents sendfilePath: ""with adirectoryvalue.
Full Changelog: v0.7.2...v0.7.3
v0.7.2
Improvements & Bug Fixes (Council Audit Round 2)
⚡ Performance
- Event-driven LSP diagnostics — Replaced blocking
thread::sleep(1.5-10s) withrecv_timeoutpolling loop. Post-edit diagnostics now return immediately when the language server responds instead of waiting the full timeout (#10) - Callgraph memory reduction — Reduced
.clone()calls from 80+ to 52 inbuild_reverse_indexby usingArc<PathBuf>/Arc<str>for shared caller data. Hot loop no longer clones entireFileCallDatastructs (#7)
🐛 Bug Fixes
- Backup error misidentification —
restore_latestnow reports the actual I/O error (permission denied, disk full) instead of incorrectly claiming "file not found" via newIoErrorvariant (#12) - Bridge crash orphan processes — Crash recovery timer now checks
isAlive()before spawning, preventing duplicate child processes whenensureSpawned()already created one (#14) - LSP workspace/configuration dispatch — Server requests are now dispatched by method:
workspace/configurationreturns a correctly-typed array instead ofnull, so language servers receive proper config responses (#19)
🔒 Security
- Mandatory checksum verification — Binary auto-downloader now aborts if checksums.sha256 is unavailable or missing an entry, preventing MITM attacks that block the checksum request (#16)
📝 Documentation
- unwrap/expect audit note — Documented in
lib.rsthat remaining.unwrap()calls are in tree-sitter query operations (compile-time grammar constants) and test code, not production error paths (#2) - TypeScript
asassertion note — Documented inbridge.tsthatascasts are guarded bysuccess === falseerror checks on all 16 tool handlers (#6)
Full Changelog: v0.7.1...v0.7.2
v0.7.1
Bug Fixes (Council Audit P0/P1)
🔒 Security
- Path validation coverage — Added
validate_path()to 6 write-capable commands that were missing it:transaction,ast_replace,lsp_rename, globedit_match,checkpoint, andrestore_checkpoint - Path traversal hardening —
validate_path()canonicalization fallback now normalizes..components for non-existent paths, preventingroot/../outside/filebypass
🐛 Correctness
- CRLF byte offset fix —
line_col_to_bytenow scans raw bytes instead of using.lines(), fixing byte offset drift on Windows/CRLF files from line 2 onward - Code deduplication — Consolidated 5 duplicate
line_col_to_byteimplementations into one shared function - Transaction rollback visibility — Failed rollback file paths now appear in the error response under
rollback_failuresinstead of being silently logged at debug level - move_file false success — Returns
moved: falsewith a warning when source file deletion fails after copy, instead of claiming success
🔧 Robustness
- LSP stderr deadlock prevention — Language server stderr pipe switched to
Stdio::null()to prevent blocking after ~64KB of server logs - Bridge recursion guard —
send()now has a 3-attempt depth limit to prevent infinite recursion on repeated version mismatches - Bridge configured flag race — Flag is now set after configure succeeds (not before the await), and reset on failure
- Format logging fix — Successful auto-format now logs at
infolevel instead oferror
Full Changelog: v0.7.0...v0.7.1