# v0.27.0

AFT now has a single shared CortexKit storage root with SQLite-backed state, end-to-end bash compression accounting visible in the status UI, and seven new tree-sitter grammars. The CortexKit migration runs once on first launch and is a one-way move; expect a brief "AFT is migrating its data" message during that first start.

## New storage root: CortexKit

All AFT persistent state has moved out of the per-harness `opencode/storage/plugin/aft/` and `pi/storage/plugin/aft/` directories into a single shared root at `~/.local/share/cortexkit/aft/` (or `%APPDATA%\cortexkit\aft\` on Windows). Search indexes, semantic indexes, symbol caches, backups, ONNX Runtime, bash task spill files, and the RPC port directory are all unified under one path that both `@cortexkit/aft-opencode` and `@cortexkit/aft-pi` share.

On first launch, the plugin spawns a blocking `aft migrate-storage` step that moves and merges your existing legacy data into the new layout. The migration is content-hash safe (no data is duplicated), idempotent (re-running is a no-op), and shows a brief "AFT is migrating its data" notice in OpenCode while it runs. Typical migration finishes in seconds; large semantic indexes can take up to a minute.

## SQLite-backed bash tasks, backups, and state

Bash task records, `aft_safety` backup history, the `warned_tools` notification dedupe, and migration markers now live in `cortexkit/aft/db/aft.sqlite` alongside the existing JSON/disk files (dual-write during v0.27 for safety; DB becomes the source of truth in a future release). The schema is versioned and migrations run automatically on configure.

Practical effect: `bash_status` lookups now work across bridge restarts, session changes, and concurrent project layouts that the JSON-only paths could miss.

## Bash output compression now accounted

The bash compression pipeline that's been compressing tool output since v0.22 was completely invisible — there was no way to see whether it was actually saving tokens or how much. v0.27 adds:

- A new SQLite `compression_events` table that records original vs compressed token counts per terminal bash task, keyed on harness + session + task_id (idempotent insert).
- Aggregates surfaced in the `status` response and rendered in OpenCode sidebar, `/aft-status` dialog, and Pi status overlay:

  ```
  Compression
    Session
      Tokens Saved        6,419
      Compression Ratio     19%
    Project
      Tokens Saved        7,026
      Compression Ratio     20%
  ```

- Tokenization uses a Claude-compatible BPE tokenizer ported from `ai-tokenizer` (new `aft-tokenizer` crate, ~7ms per 128KiB output, linear scaling).
- Foreground bash with `notify_on_completion=false` (the OpenCode/Pi `bash` tool default) now records compression events — previously this path skipped the database write entirely, so >99% of real-world bash usage contributed zero to the aggregate. This was the single biggest gap in the compression telemetry.
- Large outputs that exceed the in-memory cap now tokenize the most recent tail bytes instead of being silently skipped, so build logs, test runs, and other high-volume bash tasks contribute their full reduction count.

## Seven new tree-sitter grammars

`aft_outline`, `aft_zoom`, and `ast_grep_search`/`ast_grep_replace` now work on **Java**, **Ruby**, **Kotlin**, **Swift**, **PHP**, **Lua**, and **Perl**. Total grammar count: 23. Each grammar ships a hand-written symbol query verified against the installed crate's actual node-type names, an extract function that handles classes/interfaces/methods/functions/fields with proper scope chains, and integration coverage for outline behavior.

## Filesystem locks

A new `fs_lock` module provides crash-safe filesystem locks used by the migration runner, cache writers, and concurrent worktree initialization paths. Replaces ad-hoc lock files that could leak across crashes. No agent-visible change; matters if you ran into "lock file already exists" errors after an AFT process was force-killed.

## Pi — `restrict_to_project_root` now respected for external-directory prompts

When `restrict_to_project_root: false` was set in `aft.jsonc`, Pi's hoisted `write`/`edit`/`grep` tools were still showing a `ui.confirm` dialog on every absolute or out-of-project path. The flag's documented intent is "don't gate operations on project membership", so the per-call dialog defeated its purpose. The prompt now fires only when the user opts INTO restriction (`restrict_to_project_root: true`); the Pi default (`false`, for parity with Pi's built-in tools) skips the dialog entirely.
