# v0.25.1

`v0.25.0` shipped to npm but failed to publish to crates.io, and its binaries reported themselves as `aft 0.24.0`. `v0.25.1` is the corrected release of that work — the actual release notes follow below. (Technical details on what went wrong are at the bottom.)

New languages, atomic operation undo, and recursive directory delete with first-class safety guardrails. Every change applies to both `@cortexkit/aft-opencode` and `@cortexkit/aft-pi`.

## JSON and Scala outlines

`aft_outline` now understands two more languages.

**JSON** — top-level object keys outline as Variable symbols with their key span as line range. Works on `package.json`, `tsconfig.json`, `biome.json`, lockfiles, RTK filter manifests, anything. Directory-mode outlines no longer fill `skipped_files` with `unsupported_language: *.json` entries.

**Scala** — classes, objects, traits, defs, vals, vars, case classes, and type aliases now outline with accurate kinds and line ranges. Scala 3 `enum` types outline as Class, and `enum`-contained methods are correctly scoped (e.g. `Color.describe`). Named `given` definitions outline as Variable; anonymous givens are skipped. `aft_zoom` works on Scala symbols. AST search/replace is not supported for Scala.

## One tool call = one undo

`aft_safety undo` now restores the entire last mutation operation atomically when called without a `filePath`.

Every mutating tool (`aft_delete`, `ast_grep_replace`, `apply_patch`, `aft_refactor move`, `aft_move`, multi-file `edit` transactions, etc.) now tags every file it touches with a single operation id. `aft_safety undo` with no arguments looks up the most recent operation and reverses every file in it as one transaction. `aft_safety undo` with an explicit `filePath` still does the existing per-file pop — backwards compatible.

The restore path is properly transactional: AFT preflights every file write to memory, creates any missing parent directories, and only commits the in-memory undo history changes after every write succeeds. If a write fails midway (permission denied, ENOSPC, etc.), AFT rolls back any files already written to their pre-restore content, removes any directories it created, leaves the undo history untouched, and returns the original error with a `partial_rollback` indicator. You can retry without losing history.

The backup store schema bumped v2 → v3 with seamless migration: legacy v2 backups load with `op_id: None` and remain per-file undo-able (the old behavior). New backups carry op_ids.

## Recursive directory delete with safety guardrails

`aft_delete files: [...]` now accepts directories when called with `recursive: true`. It walks the tree, backs every file up under a single op_id (see above), then removes the directory. A single `aft_safety undo` afterward restores the entire directory tree — files, parent dirs, and all — in one call.

Before deleting, AFT validates the tree contains nothing it can't reliably restore. If the tree contains **any symlink** or **any empty directory**, the delete is refused with a `unsupported_directory_contents` error that names the offending paths. The filesystem is untouched in the rejection case. This is a deliberate guardrail — symlinks could resolve outside the tree on restore (writing arbitrary files), and empty dirs aren't currently representable in the backup format. Both cases will be supported in a future release with proper node-type metadata.

Without `recursive: true`, directory paths return `invalid_request` with a clear message pointing to the flag.

## Stop orphaning LSP child processes

Fixes the long-standing `killall biome` workaround. AFT now puts each LSP server in its own process group at spawn and SIGKILLs the entire group on shutdown. Previously only the npm shim wrapper PID was killed, leaving the real server (e.g. `@biomejs/cli-darwin-arm64 biome lsp-proxy`) orphaned to PID 1 and accumulating across restarts.

Applies to all LSP servers that use a wrapper-and-child structure — biome, eslint, prettier, and similar npm-distributed servers. On Windows, the equivalent fix uses `taskkill /F /T` to kill the entire process tree.

## Other

- **RPC status timeout warnings gone** — between bridge spawn and the first push-frame transition, the plugin's status cache was empty, so the TUI sidebar's 1.5s poll would fall through to a bridge call that raced the in-flight eager configure and aborted at 5s. AFT now seeds the cache directly from the eager configure response so the first poll always hits warm cache.

- **CI** — workflows bumped to `actions/checkout@v5` and `actions/setup-node@v5`, removing Node 20 deprecation warnings.

## Why v0.25.1 (technical detail)

The `v0.25.0` tag was placed on a commit where `Cargo.toml` and `package.json` files still said `0.24.0`. The release workflow then built platform binaries from that stale `Cargo.toml`, so `aft --version` reported `0.24.0` (because `CARGO_PKG_VERSION` is baked in at compile time). `cargo publish` tried to publish `agent-file-tools@0.24.0` to crates.io, got "already exists", and a graceful fallback masked the mismatch as success. The npm publish step had its own version-sync that ran from the tag, so the npm packages did go out at `0.25.0`. Net result: npm got 0.25.0 binaries that reported themselves as 0.24.0, and crates.io got nothing new.

Fixed for future releases: `version-sync.mjs --from-tag` now runs in `publish-crates` and in every `build-*` job (not just the npm publish step). The crates.io "already exists" fallback now only treats success if `Cargo.toml`'s post-sync version matches the tag.

Workflow architecture also refactored: both `tests.yml` (PR-time) and `release.yml` (tag-push) now call a single reusable `_unit-suite.yml` for unit-level coverage (Linux, macOS, Windows cargo, Windows bash e2e). Removes ~400 lines of duplicated job logic and ensures PR-time and release-time unit jobs can't drift. The reusable workflow takes a `strict` boolean: PR mode keeps Windows jobs non-blocking (`continue-on-error: true`); release mode makes ALL four unit jobs gate the publish flow. A half-published `v0.25.0` is exactly the state the new strict gate refuses to ship.
