AFT v0.27.1 fixes two GitHub-reported regressions and ships several polish items on top of v0.27.0.

## Fixes

### OpenCode LSP auto-install now uses npm (GitHub #46)

`@vue/language-server` and other newer LSP servers were never installing for users without `bun` on PATH, even with `lsp.auto_install: true`. The OpenCode plugin previously spawned `bun add` to install LSP packages, which silently `ENOENT`-failed when `bun` wasn't available. By the next configure, the failed binary was no longer in flight, so Rust correctly emitted `Install vue-language-server and ensure it is on PATH` — and the cycle repeated on every startup.

OpenCode now spawns `npm install --no-save` instead, matching the Pi plugin's existing behavior. `npm` is guaranteed to be present whenever the plugin reaches the user through the standard OpenCode distribution flows. After upgrading, missing LSP servers like Vue, Astro, and Svelte install automatically on next startup and the recurring warning disappears.

### Honest `no_op` reporting for byte-identical writes (GitHub #45)

`edit` and `write` calls that resulted in byte-identical file content (e.g. `oldString === newString`, or a formatter that normalized the change back to the original) reported `+0/-0` with no explanation, which agents read as "the tool is broken."

Rust now sets `no_op: true` on the response whenever the post-write file matches the pre-write state. The match was satisfied (`replacements: 1`), but agents and the TUI now see an explicit note:

- Pi sidebar/dialog renders `+0/-0 (no net change)` in muted text instead of bare `+0/-0`
- Pi tool result text appends `no net file change — newString may be identical to oldString or formatting normalized the result`
- OpenCode `edit` / `write` output appends the same explanation

Applies across all 4 write/edit paths: find/replace, append, symbol replace, full-file write.

### Per-project bridge config in OpenCode Desktop

OpenCode Desktop runs one plugin instance for many open projects. The plugin previously loaded AFT config once at startup from whichever directory OpenCode happened to launch from, then froze it for every project. If you opened a session in Project A whose `.opencode/aft.jsonc` set `bash.background: false`, the bridge for Project A still used Project B's config because Project B's was the one loaded at init.

Per-project AFT config now loads at bridge spawn time. Project-scoped fields take effect immediately when opening a session in that project:

- `experimental.bash.{rewrite, compress, background}`, `experimental.lsp_ty`
- `format_on_edit`, `formatter_timeout_secs`, `validate_on_edit`, `formatter` / `checker` per-language
- `restrict_to_project_root`, `search_index`, `semantic_search`
- `max_callgraph_files`, `semantic.*`, project-safe `lsp.*` fields

Plugin-init-time decisions (`tool_surface`, `disabled_tools`, `hoist_builtin_tools`, ONNX runtime setup) stay global because they affect tool registration, not per-bridge configure. Pi is one process per session so this never affected Pi.

## Polish

### v0.27 startup announcement

OpenCode and Pi now surface a once-per-version dialog summarizing v0.27's headline changes — CortexKit storage migration, SQLite-backed bash task state, bash output compression rendering in `/aft-status`, and the new Discord link — so users who skipped the v0.27.0 release notes still see what changed.

### JSON Schema for `aft.jsonc`

`aft setup` now writes a `$schema` URL pointing at `assets/aft.schema.json` in fresh `aft.jsonc` files, so editors with JSON Schema support (VS Code, IntelliJ, neovim with jsonls) get autocomplete and inline validation for AFT config keys. Existing config files are unchanged.

### Quieter logs

Two routine plugin-log lines were demoted from INFO to DEBUG/silent:

- The per-bash `compression event recorded for ... (N → M tokens)` line, which fires on every bash invocation
- The `bash task replay DB miss for session __default__; falling back to disk` line, which fires once per cold-start on a session-less configure (eager warm, anonymous protocol calls) and means nothing actionable

WARN remains for real lookup errors. Migration paths still log INFO when disk actually returns tasks worth surfacing.

## Upgrade notes

No config changes required. Existing project bridges keep working unchanged. The npm-vs-bun LSP install fix takes effect on next plugin startup. The per-project config fix takes effect on next bridge spawn for each project.
