# 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_zoom` — `targets: []` 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/DSa65w8wuf
